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.



237
238
239
# File 'lib/activefacts/api/entity.rb', line 237

def created_instances
  @created_instances
end

#identification_inherited_fromObject

Returns the value of attribute identification_inherited_from.



235
236
237
# File 'lib/activefacts/api/entity.rb', line 235

def identification_inherited_from
  @identification_inherited_from
end

#overrides_identification_ofObject

Returns the value of attribute overrides_identification_of.



236
237
238
# File 'lib/activefacts/api/entity.rb', line 236

def overrides_identification_of
  @overrides_identification_of
end

Instance Method Details

#assert_instance(constellation, args) ⇒ Object



359
360
361
362
363
364
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
# File 'lib/activefacts/api/entity.rb', line 359

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.



291
292
293
294
295
296
297
298
# File 'lib/activefacts/api/entity.rb', line 291

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



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/activefacts/api/entity.rb', line 267

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



257
258
259
260
261
262
263
264
265
# File 'lib/activefacts/api/entity.rb', line 257

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.



419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
# File 'lib/activefacts/api/entity.rb', line 419

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:



240
241
242
243
244
245
246
# File 'lib/activefacts/api/entity.rb', line 240

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



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
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# File 'lib/activefacts/api/entity.rb', line 307

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



248
249
250
251
252
253
254
255
# File 'lib/activefacts/api/entity.rb', line 248

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:



402
403
404
405
406
407
408
409
410
411
412
413
414
# File 'lib/activefacts/api/entity.rb', line 402

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:



444
445
446
447
448
449
# File 'lib/activefacts/api/entity.rb', line 444

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



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

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