lib/activefacts/api/role.rb in activefacts-0.7.0 vs lib/activefacts/api/role.rb in activefacts-0.7.1

- old
+ new

@@ -1,59 +1,70 @@ -## The ActiveFacts Runtime API Concept class -# Copyright (c) 2008 Clifford Heath. Read the LICENSE file. # +# ActiveFacts API +# Role class. +# Each accessor method created on an instance corresponds to a Role object in the instance's class. +# Binary fact types construct a Role at each end. +# +# Copyright (c) 2009 Clifford Heath. Read the LICENSE file. +# module ActiveFacts module API # A Role represents the relationship of one object to another (or to a boolean condition). # Relationships (or binary fact types) have a Role at each end; one is declared using _has_one_ # or _one_to_one_, and the other is created on the counterpart class. Each Concept class maintains # an array of the roles it plays. class Role - attr_accessor :name + attr_accessor :owner # The Concept to which this role belongs + attr_accessor :name # The name of the role (a Symbol) + attr_accessor :counterpart_concept # A Concept Class (may be temporarily a Symbol before the class is defined) attr_accessor :counterpart # All roles except unaries have a binary counterpart - attr_accessor :player # May be a Symbol, which will be converted to a Class/Concept - attr_accessor :unique - attr_accessor :mandatory - attr_accessor :value_restriction + attr_accessor :unique # Is this role played by at most one instance, or more? + attr_accessor :mandatory # In a valid fact population, is this role required to be played? + attr_accessor :value_restriction # Counterpart Instances playing this role must meet these restrictions + attr_reader :is_identifying # Is this an identifying role for owner? - def initialize(player, counterpart, name, mandatory = false, unique = true) - @player = player + def initialize(owner, counterpart_concept, counterpart, name, mandatory = false, unique = true) + @owner = owner + @counterpart_concept = counterpart_concept @counterpart = counterpart @name = name @mandatory = mandatory @unique = unique + @is_identifying = @owner.respond_to?(:identifying_role_names) && @owner.identifying_role_names.include?(@name) end + # Is this role a unary (created by maybe)? If so, it has no counterpart def unary? # N.B. A role with a forward reference looks unary until it is resolved. counterpart == nil end - def resolve_player(vocabulary) #:nodoc: - return @player if Class === @player # Done already - klass = vocabulary.concept(@player) # Trigger the binding - raise "Cannot resolve role player #{@player.inspect} for role #{name} in vocabulary #{vocabulary.basename}; still forward-declared?" unless klass - @player = klass # Memoize a successful result + def resolve_counterpart(vocabulary) #:nodoc: + return @counterpart_concept if @counterpart_concept.is_a?(Class) # Done already + klass = vocabulary.concept(@counterpart_concept) # Trigger the binding + raise "Cannot resolve role counterpart_concept #{@counterpart_concept.inspect} for role #{name} in vocabulary #{vocabulary.basename}; still forward-declared?" unless klass + @counterpart_concept = klass # Memoize a successful result end def adapt(constellation, value) #:nodoc: # If the value is a compatible class, use it (if in another constellation, clone it), # else create a compatible object using the value as constructor parameters. - if @player === value # REVISIT: may be a non-primary subtype of player + if value.is_a?(@counterpart_concept) # REVISIT: may be a non-primary subtype of counterpart_concept + value = value.__getobj__ if RoleProxy === value # Check that the value is in a compatible constellation, clone if not: if constellation && (vc = value.constellation) && vc != constellation value = value.clone # REVISIT: There's sure to be things we should reset/clone here, like non-identifying roles end value.constellation = constellation if constellation else value = [value] unless Array === value - raise "No parameters were provided to identify an #{@player.basename} instance" if value == [] + raise "No parameters were provided to identify an #{@counterpart_concept.basename} instance" if value == [] if constellation - value = constellation.send(@player.basename.to_sym, *value) + value = constellation.send(@counterpart_concept.basename.to_sym, *value) else - value = @player.new(*value) + value = @counterpart_concept.new(*value) end end value end end @@ -63,32 +74,7 @@ class RoleCollection < Hash #:nodoc: def verbalise keys.sort_by(&:to_s).inspect end end - - # A RoleValueArray is an array with all mutating methods hidden. - # We use these for the "many" side of a 1:many relationship. - # Only "replace" and "delete" are actually used (so far!). - # - # Don't rely on this implementation, as it must change to support - # persistence. - # - class RoleValueArray < Array #:nodoc: - [ :"<<", :"[]=", :clear, :collect!, :compact!, :concat, :delete, - :delete_at, :delete_if, :fill, :flatten!, :insert, :map!, :pop, - :push, :reject!, :replace, :reverse!, :shift, :shuffle!, :slice!, - :sort!, :uniq!, :unshift - ].each{|s| - begin - alias_method("__#{s}", s) - rescue NameError # shuffle! is in 1.9 only - end - } - - def verbalise - "["+map{|e| e.verbalise}*", "+"]" - end - end - end end