lib/activefacts/api/instance.rb in activefacts-api-0.8.12 vs lib/activefacts/api/instance.rb in activefacts-api-0.9.1

- old
+ new

@@ -22,12 +22,103 @@ 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 + 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 + + # 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 + end + + !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 = []) + 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 + 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 @@ -46,10 +137,14 @@ send role.setter, nil 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| - v.send(counterpart.setter, nil) + if counterpart.is_identifying + v.retract + else + v.send(counterpart.setter, nil) + end end end end end end