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

#all_roles, #check_identifying_role_has_valid_cardinality, #has_one, #maybe, #one_to_one, #realise_role, #roles, #subtypes, #supertypes, #supertypes_transitive, #vocabulary

Instance Attribute Details

#created_instancesObject

Returns the value of attribute created_instances.



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

def created_instances
  @created_instances
end

#identification_inherited_fromObject

Returns the value of attribute identification_inherited_from.



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

def identification_inherited_from
  @identification_inherited_from
end

#overrides_identification_ofObject

Returns the value of attribute overrides_identification_of.



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

def overrides_identification_of
  @overrides_identification_of
end

Instance Method Details

#assert_instance(constellation, args) ⇒ Object



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
375
376
# File 'lib/activefacts/api/entity.rb', line 337

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.roles(k)
    unless role.is_identifying && role.object_type == self
      value =
	if v == nil
	  nil
	elsif role.is_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.



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

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



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

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.roles(role.name)
      new_key = role.counterpart.object_type.identifying_role_values(instance.constellation, [new_value])
      # REVISIT: Check that the next line actually gets hit, otherwise strip it out
      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



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

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.



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

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:



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

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



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
334
335
# File 'lib/activefacts/api/entity.rb', line 285

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|
    roles(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.is_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.is_unary)
    else
      value = nil
    end

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

    value
  end
end

#identifying_rolesObject



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

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(constellation, instance) ⇒ Object

:nodoc:



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

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:



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

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



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

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