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:

Constant Summary

Constants included from ObjectType

ObjectType::CHECKED_IDENTIFYING_ROLE, ObjectType::SKIP_MUTUAL_PROPAGATION

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, #subtypes_transitive, #supertypes, #supertypes_transitive, #vocabulary

Instance Attribute Details

#created_instancesObject

Returns the value of attribute created_instances.



243
244
245
# File 'lib/activefacts/api/entity.rb', line 243

def created_instances
  @created_instances
end

#identification_inherited_fromObject

Returns the value of attribute identification_inherited_from.



241
242
243
# File 'lib/activefacts/api/entity.rb', line 241

def identification_inherited_from
  @identification_inherited_from
end

#overrides_identification_ofObject

Returns the value of attribute overrides_identification_of.



242
243
244
# File 'lib/activefacts/api/entity.rb', line 242

def overrides_identification_of
  @overrides_identification_of
end

Instance Method Details

#assert_instance(constellation, args) ⇒ Object



365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# File 'lib/activefacts/api/entity.rb', line 365

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
      constellation.when_admitted {
        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.



297
298
299
300
301
302
303
304
# File 'lib/activefacts/api/entity.rb', line 297

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



273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/activefacts/api/entity.rb', line 273

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



263
264
265
266
267
268
269
270
271
# File 'lib/activefacts/api/entity.rb', line 263

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.



425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
# File 'lib/activefacts/api/entity.rb', line 425

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:



246
247
248
249
250
251
252
# File 'lib/activefacts/api/entity.rb', line 246

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



313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
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
# File 'lib/activefacts/api/entity.rb', line 313

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



254
255
256
257
258
259
260
261
# File 'lib/activefacts/api/entity.rb', line 254

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:



408
409
410
411
412
413
414
415
416
417
418
419
420
# File 'lib/activefacts/api/entity.rb', line 408

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:



450
451
452
453
454
455
# File 'lib/activefacts/api/entity.rb', line 450

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



458
459
460
# File 'lib/activefacts/api/entity.rb', line 458

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