lib/activefacts/api/instance.rb in activefacts-api-0.9.3 vs lib/activefacts/api/instance.rb in activefacts-api-0.9.4
- old
+ new
@@ -9,142 +9,104 @@
module ActiveFacts
module API
# Every Instance of a ObjectType (A Value type or an Entity type) includes the methods of this module:
module Instance
# What constellation does this Instance belong to (if any):
- attr_accessor :constellation
+ attr_reader :constellation
def initialize(args = []) #:nodoc:
unless (self.class.is_entity_type)
begin
super(*args)
+ rescue TypeError => e
+ if trace(:debug)
+ p e; puts e.backtrace*"\n\t"; debugger; true
+ end
rescue ArgumentError => e
e.message << " constructing a #{self.class}"
raise
end
end
end
- # Detect inconsistencies within constellation if this entity was updated
- # with the specified role/value pair.
- def detect_inconsistencies(role, value)
- if duplicate_identifying_values?(role, value)
- exception_data = {
- :value => value,
- :role => role,
- :class => self.class
- }
-
- raise DuplicateIdentifyingValueException.new(exception_data)
- end
+ def is_a? klass
+ super || self.class.supertypes_transitive.include?(klass)
end
- # Checks if instance have duplicate values within its constellation.
- #
- # Only works on identifying roles.
- def duplicate_identifying_values?(role, value)
- @constellation && role.is_identifying && !is_unique?(:role => role, :value => value)
- end
+ # If this instance's role is updated to the new value, does that cause a collision?
+ # We need to check each superclass that has a different identification pattern
+ def check_value_change_legality(role, value)
+ return unless @constellation && role.is_identifying
- # Checks if instance would still be unique if it was updated with
- # args.
- #
- # args should be a hash containing the role and value to update
- # and the name of the identifying value as the key.
- #
- # For example, if a Person is identified by name and family_name:
- # updated_values = { :name => "John" }
- # Would merge this hash with the one defining the current instance
- # and verify in our constellation if it exists.
- #
- # The uniqueness of the entity will also be checked within its supertypes.
- #
- # An Employee -subtype of a Person- identified by its employee_id would
- # collide with a Person if it has the same name. But `name` may not be
- # an identifying value for the Employee identification scheme.
- def is_unique?(args)
- duplicate = ([self.class] + self.class.supertypes_transitive).detect do |klass|
- old_identity = identity_by(klass)
- if klass.identifying_roles.include?(args[:role])
- new_identity = old_identity.merge(args[:role].getter => args[:value])
- @constellation.instances[klass].include?(new_identity)
- else
- false
- end
+ klasses = [self.class] + self.class.supertypes_transitive
+ last_identity = nil
+ last_irns = nil
+ duplicate = klasses.detect do |klass|
+ next false unless klass.identifying_roles.include?(role)
+ irns = klass.identifying_role_names
+ if last_irns != irns
+ last_identity = identifying_role_values(klass)
+ role_position = irns.index(role.name)
+ last_identity[role_position] = value
+ end
+ @constellation.instances[klass][last_identity]
end
- !duplicate
+ raise DuplicateIdentifyingValueException.new(self.class, role.name, value) if duplicate
end
- # List entities which reference the current one.
- #
- # Once an entity is found, it will also search for
- # related entities of this instance.
- def related_entities(instances = [])
+ # List entities which have an identifying role played by this object.
+ def related_entities(indirectly = true, instances = [])
+ # Check all roles of this instance
self.class.roles.each do |role_name, role|
- instance_index_counterpart(role).each do |irv, instance|
- if instance.class.is_entity_type && instance.is_identified_by?(self)
- if !instances.include?(instance)
- instances << instance
- instance.related_entities(instances)
- end
- end
- end
+ # If the counterpart role is not identifying for its object type, skip it
+ next unless c = role.counterpart and c.is_identifying
+
+ identified_instances = Array(self.send(role.getter))
+ instances.concat(identified_instances)
+ identified_instances.each do |instance|
+ instance.related_entities(indirectly, instances) if indirectly
+ end
end
instances
end
- # Determine if entity is an identifying value
- # of the current instance.
- def is_identified_by?(entity)
- self.class.identifying_roles.detect do |role|
- send(role.getter) == entity
- end
- end
-
def instance_index
@constellation.send(self.class.basename.to_sym)
end
- def instance_index_counterpart(role)
- if @constellation && role.counterpart
- @constellation.send(role.counterpart.object_type.basename.to_sym)
- else
- []
- end
- end
-
- # Verbalise this instance
- # REVISIT: Should it raise an error if it was not redefined ?
- def verbalise
- # REVISIT: Should it raise an error if it was not redefined ?
- # This method should always be overridden in subclasses
- end
-
# De-assign all functional roles and remove from constellation, if any.
def retract
- # Delete from the constellation first, while it remembers our identifying role values
- @constellation.__retract(self) if @constellation
+ # Delete from the constellation first, while we remember our identifying role values
+ @constellation.deindex_instance(self) if @constellation
# Now, for all roles (from this class and all supertypes), assign nil to all functional roles
# The counterpart roles get cleared automatically.
- ([self.class]+self.class.supertypes_transitive).each do |klass|
+ klasses = [self.class]+self.class.supertypes_transitive
+ klasses.each do |klass|
klass.roles.each do |role_name, role|
next if role.unary?
counterpart = role.counterpart
- if role.unique
- # puts "Nullifying mandatory role #{role.name} of #{role.object_type.name}" if counterpart.mandatory
- send role.setter, nil
+ # Objects being created do not have to have non-identifying mandatory roles,
+ # so we allow retracting to the same state.
+ if role.unique
+ if counterpart.is_identifying && counterpart.mandatory
+ i = send(role.name) and i.retract
+ else
+ send role.setter, nil
+ end
else
# puts "Not removing role #{role_name} from counterpart RoleValues #{counterpart.name}"
# Duplicate the array using to_a, as the RoleValues here will be modified as we traverse it:
- send(role.name).to_a.each do |v|
- if counterpart.is_identifying
- v.retract
+ counterpart_instances = send(role.name)
+ counterpart_instances.to_a.each do |counterpart_instance|
+ # These actions deconstruct the RoleValues as we go:
+ if counterpart.is_identifying && counterpart.mandatory
+ counterpart_instance.retract
else
- v.send(counterpart.setter, nil)
+ counterpart_instance.send(counterpart.setter, nil)
end
end
end
end
end
@@ -153,10 +115,10 @@
module ClassMethods #:nodoc:
include ObjectType
# Add Instance class methods here
end
- def Instance.included other #:nodoc:
+ def self.included other #:nodoc:
other.send :extend, ClassMethods
end
end
end
end