lib/activefacts/persistence/reference.rb in activefacts-0.8.9 vs lib/activefacts/persistence/reference.rb in activefacts-0.8.10

- old
+ new

@@ -1,32 +1,32 @@ # # ActiveFacts Relational mapping and persistence. -# Reference from one Concept to another, used to decide the relational mapping. +# Reference from one ObjectType to another, used to decide the relational mapping. # # Copyright (c) 2009 Clifford Heath. Read the LICENSE file. # -# A Reference from one Concept to another is created for each many-1 or 1-1 relationship -# (including subtyping), and also for a unary role (implicitly to Boolean concept). +# A Reference from one ObjectType to another is created for each many-1 or 1-1 relationship +# (including subtyping), and also for a unary role (implicitly to Boolean object_type). # A 1-1 or subtyping reference should be created in only one direction, and may be flipped # if needed. # -# A reference to a concept that's a table or is fully absorbed into a table will -# become a foreign key, otherwise it will absorb all that concept's references. +# A reference to a object_type that's a table or is fully absorbed into a table will +# become a foreign key, otherwise it will absorb all that object_type's references. # -# Reference objects update each concept's list of the references *to* and *from* that concept. +# Reference objects update each object_type's list of the references *to* and *from* that object_type. # # Copyright (c) 2008 Clifford Heath. Read the LICENSE file. # module ActiveFacts module Persistence # This class contains the core data structure used in composing a relational schema. # - # A Reference is *from* one Concept *to* another Concept, and relates to the *from_role* and the *to_role*. - # When either Concept is an objectified fact type, the corresponding role is nil. - # When the Reference from_role is of a unary fact type, there's no to_role or to Concept. + # A Reference is *from* one ObjectType *to* another ObjectType, and relates to the *from_role* and the *to_role*. + # When either ObjectType is an objectified fact type, the corresponding role is nil. + # When the Reference from_role is of a unary fact type, there's no to_role or to ObjectType. # The final kind of Reference is a self-reference which is added to a ValueType that becomes a table. # # When the underlying fact type is a one-to-one (including an inheritance fact type), the Reference may be flipped. # # Each Reference has a name; an array of names in fact, in case of adjectives, etc. @@ -38,11 +38,11 @@ class Reference attr_reader :from, :to # A "from" instance is related to one "to" instance attr_reader :from_role, :to_role # For objectified facts, one role will be nil (a phantom) attr_reader :fact_type - # A Reference is created from a concept in regard to a role it plays + # A Reference is created from a object_type in regard to a role it plays def initialize(from, role) @from = from return unless role # All done if it's a self-value reference for a ValueType @fact_type = role.fact_type if @fact_type.all_role.size == 1 @@ -50,18 +50,18 @@ @to_role = role @to = role.fact_type.entity_type # nil unless the unary is objectified elsif (role.fact_type.entity_type == @from) # role is in "from", an objectified fact type @from_role = nil # Phantom role @to_role = role - @to = @to_role.concept + @to = @to_role.object_type else @from_role = role @to = role.fact_type.entity_type # If set, to_role is a phantom unless @to raise "Illegal reference through >binary fact type" if @fact_type.all_role.size >2 @to_role = (role.fact_type.all_role-[role])[0] - @to = @to_role.concept + @to = @to_role.object_type end end end # What type of Role did this Reference arise from? @@ -96,11 +96,11 @@ # Is this reference an injected role as a result a ValueType being a table? def is_self_value !@to && !@to_role end - # Is the *to* concept fully absorbed through this reference? + # Is the *to* object_type fully absorbed through this reference? def is_absorbing @to && @to.absorbed_via == self end # Is this a simple reference? @@ -125,14 +125,35 @@ @from.name.camelwords + ["Value"] when @to_role.role_name # Named role @to_role.role_name.camelwords else # Use the name from the preferred reading role_ref = @to_role.preferred_reference - [role_ref.leading_adjective, @to_role.concept.name, role_ref.trailing_adjective].compact.map{|w| w.camelwords}.flatten.reject{|s| s == ''} + [role_ref.leading_adjective, @to_role.object_type.name, role_ref.trailing_adjective].compact.map{|w| w.camelwords}.flatten.reject{|s| s == ''} end end + # Return the array of names for the (perhaps implicit) *from_role* of this Reference + def from_names + case + when is_unary + if @from && @from.fact_type + @from.name.camelwords + else + @from_role.fact_type.preferred_reading.text.gsub(/\{[0-9]\}/,'').strip.camelwords + end + when @from && !@from_role # @from is an objectified fact type so @from_role is a phantom + @from.name.camelwords + when !@from_role # Self-value role of an independent ValueType + @from.name.camelwords + ["Value"] + when @from_role.role_name # Named role + @from_role.role_name.camelwords + else # Use the name from the preferred reading + role_ref = @from_role.preferred_reference + [role_ref.leading_adjective, @from_role.object_type.name, role_ref.trailing_adjective].compact.map{|w| w.camelwords}.flatten.reject{|s| s == ''} + end + end + # For a one-to-one (or a subtyping fact type), reverse the direction. def flip #:nodoc: raise "Illegal flip of #{self}" unless @to and [:one_one, :subtype, :supertype].include?(role_type) detabulate @@ -170,25 +191,25 @@ "reference from #{@from.name}#{@to ? " to #{@to.name}" : ""}" + (@fact_type ? " in '#{@fact_type.default_reading}'" : "") end # The reading for the fact type underlying this Reference def reading - is_self_value ? "#{from.name} has value" : @fact_type.default_reading([], true) # Include role name defn's + is_self_value ? "#{from.name} has value" : @fact_type.default_reading end def inspect #:nodoc: to_s end end end module Metamodel #:nodoc: - class Concept + class ObjectType # Say whether the independence of this object is still under consideration # This is used in detecting dependency cycles, such as occurs in the Metamodel attr_accessor :tentative #:nodoc: - attr_writer :is_table # The two Concept subclasses provide the attr_reader method + attr_writer :is_table # The two ObjectType subclasses provide the attr_reader method def show_tabular #:nodoc: (tentative ? "tentatively " : "") + (is_table ? "" : "not ")+"a table" end @@ -211,21 +232,21 @@ def probably_not_table #:nodoc: @is_table = false @tentative = true end - # References from this Concept + # References from this ObjectType def references_from @references_from ||= [] end - # References to this Concept + # References to this ObjectType def references_to @references_to ||= [] end - # True if this Concept has any References (to or from) + # True if this ObjectType has any References (to or from) def has_references #:nodoc: @references_from || @references_to end def clear_references #:nodoc: @@ -234,11 +255,15 @@ @references_from = nil end def populate_references #:nodoc: all_role.each do |role| - populate_reference role unless role.fact_type.is_a?(ImplicitFactType) + # It's possible that this role is in an implicit or derived fact type. Skip it if so. + next if role.fact_type.is_a?(ImplicitFactType) or + role.fact_type.preferred_reading.role_sequence.all_role_ref.to_a[0].join_role + + populate_reference role end end def populate_reference role #:nodoc: role_type = role.role_type @@ -319,16 +344,16 @@ unless r.from.name.downcase < r.to.name.downcase or (r.from == r.to && references_to.detect{|ref| ref.to_role == role}) # one-to-one self reference, done already r.tabulate end else - raise "Illegal role type, #{role.fact_type.describe(role)} no uniqueness constraint" + raise "Role #{role.object_type.name} in '#{role.fact_type.default_reading}' lacks a uniqueness constraint" end end end - class EntityType < Concept + class EntityType < ObjectType def populate_references #:nodoc: if fact_type && fact_type.all_role.size > 1 # NOT: fact_type.all_role.each do |role| # Place roles in the preferred order instead: fact_type.preferred_reading.role_sequence.all_role_ref.map(&:role).each do |role| populate_reference role # Objectified fact role, handled specially @@ -338,26 +363,26 @@ end end class Vocabulary def populate_all_references #:nodoc: - debug :references, "Populating all concept references" do - all_concept.each do |concept| - concept.clear_references - concept.is_table = nil # Undecided; force an attempt to decide - concept.tentative = true # Uncertain + debug :references, "Populating all object_type references" do + all_object_type.each do |object_type| + object_type.clear_references + object_type.is_table = nil # Undecided; force an attempt to decide + object_type.tentative = true # Uncertain end - all_concept.each do |concept| - debug :references, "Populating references for #{concept.name}" do - concept.populate_references + all_object_type.each do |object_type| + debug :references, "Populating references for #{object_type.name}" do + object_type.populate_references end end end - debug :references, "Finished concept references" do - all_concept.each do |concept| - next unless concept.references_from.size > 0 - debug :references, "#{concept.name}:" do - concept.references_from.each do |ref| + debug :references, "Finished object_type references" do + all_object_type.each do |object_type| + next unless object_type.references_from.size > 0 + debug :references, "#{object_type.name}:" do + object_type.references_from.each do |ref| debug :references, "#{ref}" end end end end