lib/activefacts/persistence/reference.rb in activefacts-0.7.0 vs lib/activefacts/persistence/reference.rb in activefacts-0.7.1

- old
+ new

@@ -1,6 +1,11 @@ # +# ActiveFacts Relational mapping and persistence. +# Reference from one Concept 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 1-1 or subtyping reference should be created in only one direction, and may be flipped # if needed. # @@ -9,16 +14,29 @@ # # Reference objects update each concept's list of the references *to* and *from* that concept. # # Copyright (c) 2008 Clifford Heath. Read the LICENSE file. # -# REVISIT: References need is_mandatory -# REVISIT: Need to index References by to_role, to help in finding PK references etc. module ActiveFacts - module Metamodel + 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. + # 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. + # Each Refererence can produce the reading of the underlying fact type. + # + # A Reference is indexed in the player's *references_from* and *references_to*, and flipping updates those. + # Finally, a Reference may be marked as absorbing the whole referenced object, and that can flip too. + # 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 @@ -44,48 +62,57 @@ @to = @to_role.concept end end end + # What type of Role did this Reference arise from? def role_type role = @from_role||@to_role role && role.role_type end + # Is this Reference covered by a mandatory constraint (implicitly or explicitly) def is_mandatory !@from_role || # All phantom roles of fact types are mandatory is_unary || # Unary fact types become booleans, which must be true or false @from_role.is_mandatory end + # Is this Reference from a unary Role? def is_unary !@to && @to_role && @to_role.fact_type.all_role.size == 1 end - # This case is the only one that cannot be used in the preferred identifier of @from + # If this Reference is to an objectified FactType, there is no *to_role* def is_to_objectified_fact + # This case is the only one that cannot be used in the preferred identifier of @from @to && !@to_role && @from_role end + # If this Reference is from an objectified FactType, there is no *from_role* def is_from_objectified_fact @to && @to_role && !@from_role end + # 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? def is_absorbing @to && @to.absorbed_via == self end + # Is this a simple reference? def is_simple_reference # It's a simple reference to a thing if that thing is a table, # or is fully absorbed into another table but not via this reference. @to && (@to.is_table or @to.absorbed_via && !is_absorbing) end + # Return the array of names for the (perhaps implicit) *to_role* of this Reference def to_names case when is_unary @to_role.fact_type.preferred_reading.reading_text.gsub(/\{[0-9]\}/,'').strip.split(/\s/) when @to && !@to_role # @to is an objectified fact type so @to_role is a phantom @@ -98,12 +125,12 @@ role_ref = @to_role.preferred_reference [role_ref.leading_adjective, @to_role.concept.name, role_ref.trailing_adjective].compact.map{|w| w.split(/\s/)}.flatten.reject{|s| s == ''} end end - # For a one-to-one (or a subtyping fact type), reverse the direction: - def flip + # 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 if @to.absorbed_via == self @@ -116,132 +143,140 @@ @to_role, @from_role = @from_role, @to_role tabulate end - def tabulate + def tabulate #:nodoc: # Add to @to and @from's reference lists @from.references_from << self @to.references_to << self if @to # Guard against self-values debug :references, "Adding #{to_s}" self end - def detabulate + def detabulate #:nodoc: # Remove from @to and @from's reference lists if present return unless @from.references_from.delete(self) @to.references_to.delete self if @to # Guard against self-values debug :references, "Dropping #{to_s}" self end - def to_s + def to_s #:nodoc: "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 end - def inspect; to_s; end + def inspect #:nodoc: + to_s + end end + end + module Metamodel #:nodoc: class Concept # 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 - attr_writer :is_table # The two Concept subclasses provide the reader + attr_accessor :tentative #:nodoc: + attr_writer :is_table # The two Concept subclasses provide the attr_reader method - def show_tabular + def show_tabular #:nodoc: (tentative ? "tentatively " : "") + (is_table ? "" : "not ")+"a table" end - def definitely_table + def definitely_table #:nodoc: @is_table = true @tentative = false end - def definitely_not_table + def definitely_not_table #:nodoc: @is_table = false @tentative = false end - def probably_table + def probably_table #:nodoc: @is_table = true @tentative = true end - def probably_not_table + def probably_not_table #:nodoc: @is_table = false @tentative = true end + # References from this Concept def references_from @references_from ||= [] end + # References to this Concept def references_to @references_to ||= [] end - def has_references + # True if this Concept has any References (to or from) + def has_references #:nodoc: @references_from || @references_to end - def clear_references + def clear_references #:nodoc: # Clear any previous references: @references_to = nil @references_from = nil end - def populate_references + def populate_references #:nodoc: all_role.each do |role| populate_reference role end end - def populate_reference role + def populate_reference role #:nodoc: role_type = role.role_type debug :references, "#{name} has #{role_type} role in '#{role.fact_type.describe}'" case role_type when :many_one - Reference.new(self, role).tabulate # A simple reference + ActiveFacts::Persistence::Reference.new(self, role).tabulate # A simple reference when :one_many if role.fact_type.entity_type == self # A Role of this objectified FactType - Reference.new(self, role).tabulate # A simple reference; check that + ActiveFacts::Persistence::Reference.new(self, role).tabulate # A simple reference; check that else # Can't absorb many of these into one of those #debug :references, "Ignoring #{role_type} reference from #{name} to #{Reference.new(self, role).to.name}" end when :unary - Reference.new(self, role).tabulate # A simple reference + ActiveFacts::Persistence::Reference.new(self, role).tabulate # A simple reference when :supertype # A subtype absorbs a reference to its supertype when separate, or all when partitioned # REVISIT: Or when partitioned if role.fact_type.subtype.is_independent debug :references, "supertype #{name} doesn't absorb a reference to separate subtype #{role.fact_type.subtype.name}" else - r = Reference.new(self, role) + r = ActiveFacts::Persistence::Reference.new(self, role) r.to.absorbed_via = r debug :references, "supertype #{name} absorbs subtype #{r.to.name}" r.tabulate end when :subtype # This object is a supertype, which can absorb the subtype unless that's independent if is_independent # REVISIT: Or when partitioned - Reference.new(self, role).tabulate + ActiveFacts::Persistence::Reference.new(self, role).tabulate # If partitioned, the supertype is absorbed into *each* subtype; a reference to the supertype needs to know which else # debug :references, "subtype #{name} is absorbed into #{role.fact_type.supertype.name}" end when :one_one - r = Reference.new(self, role) + r = ActiveFacts::Persistence::Reference.new(self, role) # Decide which way the one-to-one is likely to go; it will be flipped later if necessary. # Force the decision if just one is independent: r.tabulate and return if is_independent and !r.to.is_independent return if !is_independent and r.to.is_independent @@ -279,11 +314,11 @@ end end end class EntityType - def populate_references + 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 end @@ -291,10 +326,10 @@ super end end class Vocabulary - def populate_all_references + def populate_all_references #:nodoc: debug :references, "Populating all concept references" do all_feature.each do |feature| next unless feature.is_a? Concept feature.clear_references feature.is_table = nil # Undecided; force an attempt to decide