Module: ActiveFacts::API::Entity::ClassMethods

Includes:
Instance::ClassMethods
Defined in:
lib/activefacts/api/entity.rb

Overview

All classes that become Entity types receive the methods of this class as class methods:

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from ObjectType

#detect_fact_type_collision, #has_one, #is_a?, #maybe, #one_to_one, #realise_role, #roles, #subtypes, #supertypes, #supertypes_transitive, #vocabulary

Instance Attribute Details

#created_instancesObject

Returns the value of attribute created_instances.



135
136
137
# File 'lib/activefacts/api/entity.rb', line 135

def created_instances
  @created_instances
end

#identification_inherited_fromObject

Returns the value of attribute identification_inherited_from.



133
134
135
# File 'lib/activefacts/api/entity.rb', line 133

def identification_inherited_from
  @identification_inherited_from
end

#overrides_identification_ofObject

Returns the value of attribute overrides_identification_of.



134
135
136
# File 'lib/activefacts/api/entity.rb', line 134

def overrides_identification_of
  @overrides_identification_of
end

Instance Method Details

#assert_instance(constellation, args) ⇒ Object

REVISIT: This method should verify that all identifying roles (including those required to identify any superclass) are present (if mandatory) and are unique… BEFORE it creates any new object(s) This is a hard problem because it’s recursive.



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/activefacts/api/entity.rb', line 211

def assert_instance(constellation, args) #:nodoc:
  # Build the key for this instance from the args
  # The key of an instance is the value or array of keys of the identifying values.
  # The key values aren't necessarily present in the constellation, even after this.
  key = identifying_role_values(*args)

  # Find and return an existing instance matching this key
  instances = constellation.instances[self]   # All instances of this class in this constellation
  instance = instances[key]
  # REVISIT: This ignores any additional attribute assignments
  if instance
    # raise "Additional role values are ignored when asserting an existing instance" if args[-1].is_a? Hash and !args[-1].empty?
    assign_additional_roles(instance, args[-1]) if args[-1].is_a? Hash and !args[-1].empty?
    return instance, key      # A matching instance of this class
  end

  # Now construct each of this object's identifying roles
  irns = identifying_role_names
  @created_instances ||= []

  has_hash = args[-1].is_a?(Hash)
  if args.size == 1+(has_hash ? 1 : 0) and args[0].is_a?(self)
    # We received a single argument of a compatible type
    # With a secondary supertype or a type having separate identification,
    # we would get the wrong identifier from arg.identifying_role_values:
    key = 
      values = identifying_role_values(args[0])
    values = values + [arg_hash = args.pop] if has_hash
  else
    args, arg_hash = ActiveFacts::extract_hash_args(irns, args)
    roles_and_values = irns.map{|role_sym| roles(role_sym)}.zip(args)
    key = []    # Gather the actual key (AutoCounters are special)
    values = roles_and_values.map do |role, arg|
        if role.unary?
          # REVISIT: This could be absorbed into a special counterpart.object_type.assert_instance
          value = role_key = arg ? true : arg   # Preserve false and nil
        elsif !arg
          value = role_key = nil
        else
          if role.counterpart.object_type.is_entity_type
            add = !constellation.send(role.counterpart.object_type.basename.to_sym).include?([arg])
          else
            add = !constellation.send(role.counterpart.object_type.basename.to_sym).include?(arg)
          end
          value, role_key = role.counterpart.object_type.assert_instance(constellation, Array(arg))
          @created_instances << [role.counterpart, value] if add
        end
        key << role_key
        value
      end
    values << arg_hash if arg_hash and !arg_hash.empty?
  end

  #trace :assert, "Constructing new #{self} with #{values.inspect}" do
  values << { :constellation => constellation }
  instance = new(*values)
  #end

  assign_additional_roles(instance, arg_hash)

  return *index_instance(instance, key, irns)

rescue DuplicateIdentifyingValueException
  @created_instances.each do |role, v|
    if !v.respond_to?(:retract)
      v = constellation.send(role.object_type.basename.to_sym)[[v]]
    end
    v.retract if v
  end
  @created_instances = []
  raise
end

#assign_additional_roles(instance, arg_hash) ⇒ Object



284
285
286
287
288
289
290
291
292
293
294
# File 'lib/activefacts/api/entity.rb', line 284

def assign_additional_roles(instance, arg_hash)
  # Now assign any extra args in the hash which weren't identifiers (extra identifiers will be assigned again)
  (arg_hash ? arg_hash.entries : []).each do |role_name, value|
    role = roles(role_name)

    if !instance.instance_index_counterpart(role).include?(value)
      @created_instances << [role, value]
    end
    instance.send(role.setter, value)
  end
end

#find_inherited_role(role_name) ⇒ Object



155
156
157
158
159
160
161
162
163
# File 'lib/activefacts/api/entity.rb', line 155

def find_inherited_role(role_name)
  if !superclass.is_entity_type
    false
  elsif superclass.roles.has_key?(role_name)
    superclass.roles[role_name]
  else
    superclass.find_inherited_role(role_name)
  end
end

#identified_by(*args) ⇒ Object

A object_type that isn’t a ValueType must have an identification scheme, which is a list of roles it plays. The identification scheme may be inherited from a superclass.



320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
# File 'lib/activefacts/api/entity.rb', line 320

def identified_by(*args) #:nodoc:
  raise "You must list the roles which will identify #{self.basename}" unless args.size > 0

  # Catch the case where we state the same identification as our superclass:
  inherited_role_names = identifying_role_names
  if !inherited_role_names.empty?
    self.overrides_identification_of = superclass
    while from = self.overrides_identification_of.identification_inherited_from
      self.overrides_identification_of = from
    end
  end
  return if inherited_role_names == args
  self.identification_inherited_from = nil

  # @identifying_role_names here are the symbols passed in, not the Role
  # objects we should use.  We'd need late binding to use Role objects...
  @identifying_role_names = args
end

#identifying_role_namesObject

Return the array of Role objects that define the identifying relationships of this Entity type:



138
139
140
141
142
143
144
# File 'lib/activefacts/api/entity.rb', line 138

def identifying_role_names
  if identification_inherited_from
    superclass.identifying_role_names
  else
    @identifying_role_names ||= []
  end
end

#identifying_role_values(*args) ⇒ Object

Convert the passed arguments into an array of raw values (or arrays of values, transitively) that identify an instance of this Entity type:



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/activefacts/api/entity.rb', line 167

def identifying_role_values(*args)
  irns = identifying_role_names

  # If the single arg is an instance of the correct class or a subclass,
  # use the instance's identifying_role_values
  has_hash = args[-1].is_a?(Hash)
  if (args.size == 1+(has_hash ? 1 : 0) and (arg = args[0]).is_a?(self))
    # With a secondary supertype or a subtype having separate identification,
    # we would get the wrong identifier from arg.identifying_role_values:
    return irns.map do |role_name|
        # Use the identifier for the class expected, not the actual:
        value = arg.send(role_name)
        value && arg.class.roles(role_name).counterpart_object_type.identifying_role_values(value)
      end
  end

  args, arg_hash = ActiveFacts::extract_hash_args(irns, args)

  if args.size > irns.size
    raise "#{basename} expects only (#{irns*', '}) for its identifier, but you provided the extra values #{args[irns.size..-1].inspect}"
  end

  role_args = irns.map{|role_sym| roles(role_sym)}.zip(args)
  role_args.map do |role, arg|
    next !!arg unless role.counterpart  # Unary
    if arg.is_a?(role.counterpart.object_type)              # includes secondary supertypes
      # With a secondary supertype or a type having separate identification,
      # we would get the wrong identifier from arg.identifying_role_values:
      next role.counterpart_object_type.identifying_role_values(arg)
    end
    if arg == nil # But not false
      if role.mandatory
        raise "You must provide a #{role.counterpart.object_type.name} to identify a #{basename}"
      end
    else
      role.counterpart_object_type.identifying_role_values(*arg)
    end
  end
end

#identifying_rolesObject



146
147
148
149
150
151
152
153
# File 'lib/activefacts/api/entity.rb', line 146

def identifying_roles
  # REVISIT: Should this return nil if identification_inherited_from?
  @identifying_roles ||=
    identifying_role_names.map do |role_name|
      role = roles[role_name] || find_inherited_role(role_name)
      role
    end
end

#index_instance(instance, key = nil, key_roles = nil) ⇒ Object

:nodoc:



296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/activefacts/api/entity.rb', line 296

def index_instance(instance, key = nil, key_roles = nil) #:nodoc:
  # Derive a new key if we didn't receive one or if the roles are different:
  unless key && key_roles && key_roles == identifying_role_names
    key = (key_roles = identifying_role_names).map do |role_name|
      instance.send role_name
    end
    raise "You must pass values for #{key_roles.inspect} to identify a #{self.name}" if key.compact == []
  end

  # Index the instance for this class in the constellation
  instances = instance.constellation.instances[self]
  instances[key] = instance

  # Index the instance for each supertype:
  supertypes.each do |supertype|
    supertype.index_instance(instance, key, key_roles)
  end

  return instance, key
end

#inherited(other) ⇒ Object

:nodoc:



339
340
341
342
343
# File 'lib/activefacts/api/entity.rb', line 339

def inherited(other) #:nodoc:
  other.identification_inherited_from = self
  subtypes << other unless subtypes.include? other
  vocabulary.__add_object_type(other)
end

#verbaliseObject

verbalise this object_type



346
347
348
# File 'lib/activefacts/api/entity.rb', line 346

def verbalise
  "#{basename} is identified by #{identifying_role_names.map{|role_sym| role_sym.to_s.camelcase}*" and "};"
end