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

#add_role, #all_role, #all_role_transitive, #check_identifying_role_has_valid_cardinality, #has_one, #maybe, #one_to_one, #realise_role, #subtypes, #supertypes, #supertypes_transitive, #vocabulary

Instance Attribute Details

#created_instancesObject

Returns the value of attribute created_instances.



213
214
215
# File 'lib/activefacts/api/entity.rb', line 213

def created_instances
  @created_instances
end

#identification_inherited_fromObject

Returns the value of attribute identification_inherited_from.



211
212
213
# File 'lib/activefacts/api/entity.rb', line 211

def identification_inherited_from
  @identification_inherited_from
end

#overrides_identification_ofObject

Returns the value of attribute overrides_identification_of.



212
213
214
# File 'lib/activefacts/api/entity.rb', line 212

def overrides_identification_of
  @overrides_identification_of
end

Instance Method Details

#assert_instance(constellation, args) ⇒ Object



335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/activefacts/api/entity.rb', line 335

def assert_instance(constellation, args)
  key = identifying_role_values(constellation, args)

  # The args is now normalized to an array containing a single Hash element
  arg_hash = args[-1]

  # Find or make an instance of the class:
         instance_index = constellation.instances[self]   # All instances of this class in this constellation
  instance = constellation.has_candidate(self, key) || instance_index[key]
  if (instance)
    # Check that all assertions about supertype keys are non-contradictory
    check_supertype_identifiers_match(instance, arg_hash)
  else
    # Check that no instance of any supertype matches the keys given
    check_no_supertype_instance_exists(constellation, arg_hash)

    instance = new_instance(constellation, arg_hash)
    constellation.candidate(instance)
  end

  # Assign any extra roles that may have been passed.
  # An exception here leaves the object indexed,
  # but without the offending role (re-)assigned.
  arg_hash.each do |k, v|
    role = instance.class.all_role(k)
    unless role.is_identifying && role.object_type == self
      value =
  if v == nil
    nil
  elsif role.unary?
    (v && true)  # Preserve nil and false
  else
    role.counterpart.object_type.assert_instance(constellation, Array(v))
  end
      instance.send(:"#{k}=", value)
    end
  end

  instance
end

#check_no_supertype_instance_exists(constellation, arg_hash) ⇒ Object

all its candidate keys must match those from the arg_hash.



267
268
269
270
271
272
273
274
# File 'lib/activefacts/api/entity.rb', line 267

def check_no_supertype_instance_exists constellation, arg_hash
  supertypes_transitive.each do |supertype|
    key = supertype.identifying_role_values(constellation, [arg_hash])
    if constellation.instances[supertype][key]
      raise TypeMigrationException.new(basename, supertype, key)
    end
  end
end

#check_supertype_identifiers_match(instance, arg_hash) ⇒ Object



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/activefacts/api/entity.rb', line 243

def check_supertype_identifiers_match instance, arg_hash
  supertypes_transitive.each do |supertype|
    supertype.identifying_roles.each do |role|
      next unless arg_hash.include?(role.name)    # No contradiction here
      new_value = arg_hash[role.name]
      existing_value = instance.send(role.name.to_sym)

      # Quick check for an exact match:
      counterpart_class = role.counterpart && role.counterpart.object_type
      next if existing_value == new_value or existing_value.identifying_role_values(counterpart_class) == new_value

      # Coerce the new value to identifying values for the counterpart role's type:
      role = supertype.all_role(role.name)
      new_key = role.counterpart.object_type.identifying_role_values(instance.constellation, [new_value])
      next if existing_value == new_key   # This can happen when the counterpart is a value type

      existing_key = existing_value.identifying_role_values(counterpart_class)
      next if existing_key == new_key
      raise TypeConflictException.new(basename, supertype, new_key, existing_key)
    end
  end
end

#find_inherited_role(role_name) ⇒ Object



233
234
235
236
237
238
239
240
241
# File 'lib/activefacts/api/entity.rb', line 233

def find_inherited_role(role_name)
  if !superclass.is_entity_type
    false
  elsif superclass.all_role.has_key?(role_name)
    superclass.all_role[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.



393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
# File 'lib/activefacts/api/entity.rb', line 393

def identified_by(*args) #:nodoc:
  options = (args[-1].is_a?(Hash) ? args.pop : {})
  options.each do |key, value|
      raise UnrecognisedOptionsException.new('EntityType', basename, key) unless respond_to?(key)
    send(key, value)
  end

  raise MissingIdentificationException.new(self) 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:



216
217
218
219
220
221
222
# File 'lib/activefacts/api/entity.rb', line 216

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

#identifying_role_values(constellation, args) ⇒ Object

This method receives an array (possibly including a trailing arguments hash) from which the values of identifying roles must be coerced. Note that when a value which is not the corrent class is received, we recurse to ask that class to coerce what we do have. The return value is an array of (and arrays of) raw values, not object instances.

No new instances may be asserted, nor may any roles of objects in the constellation be changed



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/activefacts/api/entity.rb', line 283

def identifying_role_values(constellation, args)
         irns = identifying_role_names

  # Normalise positional arguments into an arguments hash (this changes the passed parameter)
  arg_hash = args[-1].is_a?(Hash) ? args.pop : {}

  # If the first parameter is an object of type self, its
  # identifying roles provide any values missing from the array/hash.
  if args[0].is_a?(self)
    proto = args.shift
  end

  # Following arguments provide identifying values in sequence; put them into the hash:
  irns.each do |role_name|
    break if args.size == 0
    arg_hash[role_name] = args.shift
  end

  # Complain if we have left-over arguments
  if args.size > 0
           raise UnexpectedIdentifyingValueException.new(self, irns, args)
  end

  # The arg_hash will be used to construct a new instance, if necessary
  args.push(arg_hash)

  irns.map do |role_name|
    all_role(role_name)
  end.map do |role|
    if arg_hash.include?(n = role.name)   # Do it this way to avoid problems where nil or false is provided
      value = arg_hash[n]
      next (value && true) if (role.unary?)
      if value
  klass = role.counterpart.object_type
  value = klass.identifying_role_values(constellation, Array(value))
      end
    elsif proto
      value = proto.send(n)
      counterpart_class = role.counterpart && role.counterpart.object_type
      value = value.identifying_role_values(counterpart_class)
      arg_hash[n] = value # Save the value for making a new instance
      next value if (role.unary?)
    else
      value = nil
    end

    raise MissingMandatoryRoleValueException.new(self, role) if value.nil? && role.mandatory

    value
  end
end

#identifying_rolesObject



224
225
226
227
228
229
230
231
# File 'lib/activefacts/api/entity.rb', line 224

def identifying_roles
  @identifying_roles ||=
    identifying_role_names.map do |role_name|
      role = all_role[role_name] || find_inherited_role(role_name)
        raise "Illegal request for identifying_roles of #{self} before they're all defined" if role == false
      role
    end.freeze
end

#index_instance(constellation, instance) ⇒ Object

:nodoc:



376
377
378
379
380
381
382
383
384
385
386
387
388
# File 'lib/activefacts/api/entity.rb', line 376

def index_instance(constellation, instance) #:nodoc:
  # Index the instance in the constellation's InstanceIndex for this class:
  instance_index = constellation.instances[self]
  key = instance.identifying_role_values(self)
  instance_index[key] = instance

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

  instance
end

#inherited(other) ⇒ Object

:nodoc:



418
419
420
421
422
423
# File 'lib/activefacts/api/entity.rb', line 418

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

#verbaliseObject

verbalise this object_type



426
427
428
# File 'lib/activefacts/api/entity.rb', line 426

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