Class: ActiveFacts::Metamodel::Concept
- Defined in:
- lib/activefacts/persistence/columns.rb,
lib/activefacts/persistence/index.rb,
lib/activefacts/vocabulary/metamodel.rb,
lib/activefacts/persistence/reference.rb,
lib/activefacts/persistence/foreignkey.rb
Overview
The Concept class is defined in the metamodel; full documentation is not generated. This section shows the features relevant to relational Persistence.
Direct Known Subclasses
Instance Attribute Summary collapse
-
#is_table ⇒ Object
writeonly
The two Concept subclasses provide the attr_reader method.
-
#tentative ⇒ Object
Say whether the independence of this object is still under consideration This is used in detecting dependency cycles, such as occurs in the Metamodel.
Instance Method Summary collapse
-
#all_absorbed_foreign_key_reference_path ⇒ Object
When an EntityType is fully absorbed, its foreign keys are too.
-
#clear_indices ⇒ Object
:nodoc:.
-
#clear_references ⇒ Object
:nodoc:.
-
#columns ⇒ Object
The array of columns for this Concept’s table.
-
#definitely_not_table ⇒ Object
:nodoc:.
-
#definitely_table ⇒ Object
:nodoc:.
-
#foreign_keys ⇒ Object
Return an array of all the foreign keys from this table.
-
#has_references ⇒ Object
True if this Concept has any References (to or from).
-
#indices ⇒ Object
An array of each Index for this table.
-
#populate_columns ⇒ Object
:nodoc:.
-
#populate_indices ⇒ Object
:nodoc:.
-
#populate_reference(role) ⇒ Object
:nodoc:.
-
#populate_references ⇒ Object
:nodoc:.
-
#probably_not_table ⇒ Object
:nodoc:.
-
#probably_table ⇒ Object
:nodoc:.
-
#references_from ⇒ Object
References from this Concept.
-
#references_to ⇒ Object
References to this Concept.
-
#show_tabular ⇒ Object
:nodoc:.
Instance Attribute Details
#is_table=(value) ⇒ Object (writeonly)
The two Concept subclasses provide the attr_reader method
185 186 187 |
# File 'lib/activefacts/persistence/reference.rb', line 185 def is_table=(value) @is_table = value end |
#tentative ⇒ Object
Say whether the independence of this object is still under consideration This is used in detecting dependency cycles, such as occurs in the Metamodel
184 185 186 |
# File 'lib/activefacts/persistence/reference.rb', line 184 def tentative @tentative end |
Instance Method Details
#all_absorbed_foreign_key_reference_path ⇒ Object
When an EntityType is fully absorbed, its foreign keys are too. Return an Array of Reference paths for such absorbed FKs
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/activefacts/persistence/foreignkey.rb', line 36 def all_absorbed_foreign_key_reference_path references_from.inject([]) do |array, ref| if ref.is_simple_reference # This catches references that would be created to secondary supertypes, when absorption is through primary. # There might be other cases where an exclusion like this is needed, but I can't reason it out. next array if TypeInheritance === ref.fact_type && absorbed_via && TypeInheritance === absorbed_via.fact_type array << [ref] elsif ref.is_absorbing ref.to.all_absorbed_foreign_key_reference_path.each{|aref| array << aref.insert(0, ref) } end array end end |
#clear_indices ⇒ Object
:nodoc:
85 86 87 88 |
# File 'lib/activefacts/persistence/index.rb', line 85 def clear_indices #:nodoc: # Clear any previous indices @indices = nil end |
#clear_references ⇒ Object
:nodoc:
227 228 229 230 231 |
# File 'lib/activefacts/persistence/reference.rb', line 227 def clear_references #:nodoc: # Clear any previous references: @references_to = nil @references_from = nil end |
#columns ⇒ Object
The array of columns for this Concept’s table
192 193 194 |
# File 'lib/activefacts/persistence/columns.rb', line 192 def columns @columns end |
#definitely_not_table ⇒ Object
:nodoc:
197 198 199 200 |
# File 'lib/activefacts/persistence/reference.rb', line 197 def definitely_not_table #:nodoc: @is_table = false @tentative = false end |
#definitely_table ⇒ Object
:nodoc:
192 193 194 195 |
# File 'lib/activefacts/persistence/reference.rb', line 192 def definitely_table #:nodoc: @is_table = true @tentative = false end |
#foreign_keys ⇒ Object
Return an array of all the foreign keys from this table
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/activefacts/persistence/foreignkey.rb', line 54 def foreign_keys fk_ref_paths = all_absorbed_foreign_key_reference_path # Get the ForeignKey object for each absorbed reference path fk_ref_paths.map do |fk_ref_path| debug :fk, "\nFK: " + fk_ref_path.map{|fk_ref| fk_ref.reading }*" and " do from_columns = columns.select{|column| column.references[0...fk_ref_path.size] == fk_ref_path } debug :fk, "from_columns = #{from_columns.map { |column| column.name }*", "}" absorption_path = [] to = fk_ref_path.last.to # REVISIT: There should be a better way to find where it's absorbed (especially since this fails for absorbed subtypes having their own identification!) while (r = to.absorbed_via) absorption_path << r to = r.to == to ? r.from : r.to end raise "REVISIT: #{fk_ref_path.inspect} is bad" unless to and to.columns unless absorption_path.empty? debug :fk, "Reference target #{fk_ref_path.last.to.name} is absorbed into #{to.name} via:" do debug :fk, "#{absorption_path.map(&:reading)*" and "}" end end debug :fk, "Looking at absorption depth of #{absorption_path.size} in #{to.name} for to_columns for #{from_columns.map(&:name)*", "}:" to_supertypes = to.supertypes_transitive to_columns = from_columns.map do |from_column| debug :fk, "\tLooking for counterpart of #{from_column.name}: #{from_column.comment}" do target_path = absorption_path + from_column.references[fk_ref_path.size..-1] debug :fk, "\tcounterpart MUST MATCH #{target_path.map(&:reading)*" and "}" c = to.columns.detect do |column| debug :fk, "Considering #{column.references.map(&:reading) * " and "}" debug :fk, "exact match: #{column.name}: #{column.comment}" if column.references == target_path # Column may be inherited into "to", in which case target_path is too long. cr = column.references allowed_type = fk_ref_path.last.to #debug :fk, "Check for absorption, need #{allowed_type.name}" if cr != target_path cr == target_path or cr == target_path[-cr.size..-1] && !target_path[0...-cr.size].detect do |ref| ft = ref.fact_type next true if allowed_type.absorbed_via != ref # Problems if it doesn't match allowed_type = ref.from false end end raise "REVISIT: Failed to find conterpart column for #{from_column.name}" unless c c end end debug :fk, "to_columns in #{to.name}: #{to_columns.map { |column| column ? column.name : "OOPS!" }*", "}" # Put the column pairs in a defined order, sorting key pairs by to-name: froms, tos = from_columns.zip(to_columns).sort_by { |pair| pair[1].name(nil) }.transpose ActiveFacts::Persistence::ForeignKey.new(self, to, fk_ref_path[-1], froms, tos) end end. sort_by do |fk| # Put the foreign keys in a defined order: [ fk.to.name, fk.to_columns.map{|col| col.name(nil).sort}, fk.from_columns.map{|col| col.name(nil).sort} ] end end |
#has_references ⇒ Object
True if this Concept has any References (to or from)
223 224 225 |
# File 'lib/activefacts/persistence/reference.rb', line 223 def has_references #:nodoc: @references_from || @references_to end |
#indices ⇒ Object
An array of each Index for this table
83 |
# File 'lib/activefacts/persistence/index.rb', line 83 def indices; @indices; end |
#populate_columns ⇒ Object
:nodoc:
196 197 198 199 |
# File 'lib/activefacts/persistence/columns.rb', line 196 def populate_columns #:nodoc: @columns = all_columns({}) end |
#populate_indices ⇒ Object
:nodoc:
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/activefacts/persistence/index.rb', line 90 def populate_indices #:nodoc: # The absorption path of a column indicates how it came to be in this table. # It might be a direct many:one valuetype relationship, or it might be in such # a relationship to an entity that was absorbed into this table (and so on). # The reference path is the set of absorption references and one past it. # Stopping here means we don't dig into the definitions of FK column counterparts. # Note that many columns of an object may have the same ref_path. # # REVISIT: # Note also that this produces columns ordered for each refpath the same as the # order of the columns, not the same as the columns in the PK for which they might be an FK. all_column_by_ref_path = debug :index2, "Indexing columns by ref_path" do @columns.inject({}) do |hash, column| debug :index2, "References in column #{name}.#{column.name}" do ref_path = column.absorption_references raise "No absorption_references for #{column.name} from #{column.references.map(&:to_s)*" and "}" if !ref_path || ref_path.empty? (hash[ref_path] ||= []) << column debug :index2, "#{column.name} involves #{ref_path.map(&:to_s)*" and "}" end hash end end columns_by_unique_constraint = {} all_column_by_role_ref = all_column_by_ref_path. keys. # Go through all refpaths and find uniqueness constraints inject({}) do |hash, ref_path| ref_path.each do |ref| next unless ref.to_role #debug :index2, "Considering #{ref_path.map(&:to_s)*" and "} yielding columns #{all_column_by_ref_path[ref_path].map{|c| c.name(".")}*", "}" #debugger if name == 'VehicleIncident' && ref.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance) ref.to_role.all_role_ref.each do |role_ref| all_pcs = role_ref.role_sequence.all_presence_constraint #puts "pcs over #{ref_path.map{|r| r.to_names}.flatten*"."}: #{role_ref.role_sequence.all_presence_constraint.map(&:describe)*"; "}" if all_pcs.size > 0 pcs = all_pcs. reject do |pc| !pc.max_frequency or # No maximum freq; cannot be a uniqueness constraint pc.max_frequency != 1 or # maximum is not 1 # Constraint is not over a unary fact type role (NORMA does this) pc.role_sequence.all_role_ref.size == 1 && ref_path[-1].to_role.fact_type.all_role.size == 1 end next unless pcs.size > 0 # The columns for this ref_path support the UCs in "pcs". pcs.each do |pc| ref_columns = all_column_by_ref_path[ref_path] ordinal = role_ref.ordinal # Position in priority order ref_columns.each_with_index do |column, index| #puts "Adding index column #{column.name} in rank[#{ordinal},#{index}]" # REVISIT: the "index" here might be a duplicate in some cases: change sort_by below to just sort and run the SeparateSubtypes CQL model for example. (columns_by_unique_constraint[pc] ||= []) << [ordinal, index, column] end end hash[role_ref] = all_column_by_ref_path[ref_path] end end hash end debug :index, "All Indices in #{name}:" do @indices = columns_by_unique_constraint.map do |uc, columns_with_ordinal| #puts "Index on #{name} over (#{columns_with_ordinal.sort.map{|ca| [ca[0], ca[1], ca[2].name].inspect}})" columns = columns_with_ordinal.sort_by{|ca| [ca[0,2], ca[2].name]}.map{|ca| ca[2]} absorption_level = columns.map(&:absorption_level).min over = columns[0].references[absorption_level].from # Absorption through a one-to-one forms a UC that we don't need to enforce using an index: next nil if over != self and over.absorbed_via == columns[0].references[absorption_level-1] and (rr = uc.role_sequence.all_role_ref.single) and over.absorbed_via.fact_type.all_role.include?(rr.role) index = ActiveFacts::Persistence::Index.new( uc, self, over, columns, uc.is_preferred_identifier ) debug :index, index index end. compact. sort_by do |index| # Put the indices in a defined order: index.columns.map(&:name) end end end |
#populate_reference(role) ⇒ Object
:nodoc:
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 |
# File 'lib/activefacts/persistence/reference.rb', line 239 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 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 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 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 = 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 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 = 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 if is_a?(ValueType) # Never absorb an entity type into a value type return if r.to.is_a?(EntityType) # Don't tabulate it else if r.to.is_a?(ValueType) r.tabulate # Always absorb a value type into an entity type return end # Force the decision if one EntityType identifies another: if preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role == r.to_role} debug :references, "EntityType #{name} is identified by EntityType #{r.to.name}, so gets absorbed elsewhere" return end if r.to.preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role == role} debug :references, "EntityType #{name} identifies EntityType #{r.to.name}, so absorbs it" r.to.absorbed_via = r r.tabulate return end end # Either both EntityTypes, or both ValueTypes. # Make an arbitrary (but stable) decision which way to go. We might flip it later. unless r.from.name < r.to.name 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" end end |
#populate_references ⇒ Object
:nodoc:
233 234 235 236 237 |
# File 'lib/activefacts/persistence/reference.rb', line 233 def populate_references #:nodoc: all_role.each do |role| populate_reference role end end |
#probably_not_table ⇒ Object
:nodoc:
207 208 209 210 |
# File 'lib/activefacts/persistence/reference.rb', line 207 def probably_not_table #:nodoc: @is_table = false @tentative = true end |
#probably_table ⇒ Object
:nodoc:
202 203 204 205 |
# File 'lib/activefacts/persistence/reference.rb', line 202 def probably_table #:nodoc: @is_table = true @tentative = true end |
#references_from ⇒ Object
References from this Concept
213 214 215 |
# File 'lib/activefacts/persistence/reference.rb', line 213 def references_from @references_from ||= [] end |
#references_to ⇒ Object
References to this Concept
218 219 220 |
# File 'lib/activefacts/persistence/reference.rb', line 218 def references_to @references_to ||= [] end |
#show_tabular ⇒ Object
:nodoc:
187 188 189 190 |
# File 'lib/activefacts/persistence/reference.rb', line 187 def show_tabular #:nodoc: (tentative ? "tentatively " : "") + (is_table ? "" : "not ")+"a table" end |