Class: ActiveFacts::Metamodel::ObjectType
- Inherits:
-
Object
- Object
- ActiveFacts::Metamodel::ObjectType
- Defined in:
- lib/activefacts/persistence/columns.rb,
lib/activefacts/mapping/rails.rb,
lib/activefacts/persistence/index.rb,
lib/activefacts/vocabulary/metamodel.rb,
lib/activefacts/persistence/reference.rb,
lib/activefacts/vocabulary/extensions.rb,
lib/activefacts/generate/helpers/rails.rb,
lib/activefacts/persistence/foreignkey.rb,
lib/activefacts/generate/transform/surrogate.rb
Overview
The ObjectType 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
-
#injected_surrogate_role ⇒ Object
readonly
Placeholder for the surrogate transform.
-
#is_table ⇒ Object
writeonly
The two ObjectType 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
- #add_surrogate(type_name = 'Auto Counter', suffix = 'ID') ⇒ Object
-
#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 ObjectType’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.
- #foreign_keys_to ⇒ Object
-
#has_references ⇒ Object
True if this ObjectType has any References (to or from).
-
#indices ⇒ Object
An array of each Index for this table.
- #is_separate ⇒ Object
-
#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:.
- #rails_class_name ⇒ Object
- #rails_name ⇒ Object
- #rails_singular_name ⇒ Object
-
#references_from ⇒ Object
References from this ObjectType.
-
#references_to ⇒ Object
References to this ObjectType.
-
#show_tabular ⇒ Object
:nodoc:.
- #wipe_columns ⇒ Object
Instance Attribute Details
#injected_surrogate_role ⇒ Object (readonly)
Placeholder for the surrogate transform
413 414 415 |
# File 'lib/activefacts/vocabulary/extensions.rb', line 413 def injected_surrogate_role @injected_surrogate_role end |
#is_table=(value) ⇒ Object (writeonly)
The two ObjectType subclasses provide the attr_reader method
245 246 247 |
# File 'lib/activefacts/persistence/reference.rb', line 245 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
244 245 246 |
# File 'lib/activefacts/persistence/reference.rb', line 244 def tentative @tentative end |
Instance Method Details
#add_surrogate(type_name = 'Auto Counter', suffix = 'ID') ⇒ Object
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/activefacts/generate/transform/surrogate.rb', line 14 def add_surrogate type_name = 'Auto Counter', suffix = 'ID' # Find or assert the surrogate value type auto_counter = vocabulary.valid_value_type_name(type_name) || constellation.ValueType(:vocabulary => vocabulary, :name => type_name, :concept => :new) # Create a subtype to identify this entity type: vt_name = self.name + ' '+suffix my_id = @vocabulary.valid_value_type_name(vt_name) || constellation.ValueType(:vocabulary => vocabulary, :name => vt_name, :concept => :new, :supertype => auto_counter) # Create a fact type = constellation.FactType(:concept => :new) my_role = constellation.Role(:concept => :new, :fact_type => , :ordinal => 0, :object_type => self) @injected_surrogate_role = my_role id_role = constellation.Role(:concept => :new, :fact_type => , :ordinal => 1, :object_type => my_id) # Create a reading (which needs a RoleSequence) reading = constellation.Reading( :fact_type => , :ordinal => 0, :role_sequence => [:new], :text => "{0} has {1}" ) constellation.RoleRef(:role_sequence => reading.role_sequence, :ordinal => 0, :role => my_role) constellation.RoleRef(:role_sequence => reading.role_sequence, :ordinal => 1, :role => id_role) # Create two uniqueness constraints for the one-to-one. Each needs a RoleSequence (two RoleRefs) one_id = constellation.PresenceConstraint( :concept => :new, :vocabulary => vocabulary, :name => self.name+'HasOne'+suffix, :role_sequence => [:new], :is_mandatory => true, :min_frequency => 1, :max_frequency => 1, :is_preferred_identifier => false ) @constellation.RoleRef(:role_sequence => one_id.role_sequence, :ordinal => 0, :role => my_role) one_me = constellation.PresenceConstraint( :concept => :new, :vocabulary => vocabulary, :name => self.name+suffix+'IsOfOne'+self.name, :role_sequence => [:new], :is_mandatory => false, :min_frequency => 0, :max_frequency => 1, :is_preferred_identifier => true ) @constellation.RoleRef(:role_sequence => one_me.role_sequence, :ordinal => 0, :role => id_role) end |
#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
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/activefacts/persistence/foreignkey.rb', line 96 def all_absorbed_foreign_key_reference_path references_from.inject([]) do |array, ref| if ref.is_simple_reference if TypeInheritance === ref.fact_type # Ignore references to secondary supertypes, when absorption is through primary. next array if absorbed_via && TypeInheritance === absorbed_via.fact_type # Ignore the case where a subtype is absorbed elsewhere: # REVISIT: Disabled, as this should never happen. # next array if ref.to.absorbed_via != ref.fact_type end ref.fk_jump = true array << [ref] elsif ref.is_absorbing or (ref.to && !ref.to.is_table) trace :fk, "getting fks absorbed into #{name} via #{ref}" do ref.to.all_absorbed_foreign_key_reference_path.each do |aref| array << aref.insert(0, ref) end end end array end end |
#clear_indices ⇒ Object
:nodoc:
110 111 112 113 |
# File 'lib/activefacts/persistence/index.rb', line 110 def clear_indices #:nodoc: # Clear any previous indices @indices = nil end |
#clear_references ⇒ Object
:nodoc:
287 288 289 290 291 |
# File 'lib/activefacts/persistence/reference.rb', line 287 def clear_references #:nodoc: # Clear any previous references: @references_to = nil @references_from = nil end |
#columns ⇒ Object
The array of columns for this ObjectType’s table
251 252 253 |
# File 'lib/activefacts/persistence/columns.rb', line 251 def columns @columns || populate_columns end |
#definitely_not_table ⇒ Object
:nodoc:
257 258 259 260 |
# File 'lib/activefacts/persistence/reference.rb', line 257 def definitely_not_table #:nodoc: @is_table = false @tentative = false end |
#definitely_table ⇒ Object
:nodoc:
252 253 254 255 |
# File 'lib/activefacts/persistence/reference.rb', line 252 def definitely_table #:nodoc: @is_table = true @tentative = false end |
#foreign_keys ⇒ Object
Return an array of all the foreign keys from this table
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 180 181 182 183 184 |
# File 'lib/activefacts/persistence/foreignkey.rb', line 124 def foreign_keys # Get the ForeignKey object for each absorbed reference path @foreign_keys ||= begin fk_ref_paths = all_absorbed_foreign_key_reference_path fk_ref_paths.map do |fk_ref_path| trace :fk, "\nFK: " + fk_ref_path.map{|fk_ref| fk_ref.reading }*" and " do from_columns = (columns||all_columns({})).select{|column| column.references[0...fk_ref_path.size] == fk_ref_path } trace :fk, "from_columns = #{from_columns.map { |column| column.name }*", "}" # Figure out absorption on the target end: to = fk_ref_path.last.to if to.absorbed_via trace :fk, "Reference target #{fk_ref_path.last.to.name} is absorbed via:" do while (r = to.absorbed_via) m = r.reversed trace :fk, "#{m.reading}" fk_ref_path << m to = m.from == to ? m.to : m.from end trace :fk, "Absorption ends at #{to.name}" end end # REVISIT: This test may no longer be necessary raise "REVISIT: #{fk_ref_path.inspect} is bad" unless to and to.columns # REVISIT: This fails for absorbed subtypes having their own identification. # Check the CompanyDirectorEmployee model for example, EmployeeManagerNr -> Person (should reference EmployeeNr) # Need to use the absorbed identifier_columns of the subtype, # not the columns of the supertype that absorbs it. # But in general, that isn't going to work because in most DBMS # there's no suitable uniquen index on the subtype's identifier_columns to_columns = fk_ref_path[-1].to.identifier_columns # Put the column pairs in the correct order. They MUST be in the order they appear in the primary key froms, tos = from_columns.zip(to_columns).sort_by { |pair| to_columns.index(pair[1]) }.transpose fk = ActiveFacts::Persistence::ForeignKey.new(self, to, fk_ref_path, froms, tos) to.foreign_keys_to << fk fk end end. sort_by do |fk| # Put the foreign keys in a defined order: # debugger if !fk.to_columns || fk.to_columns.include?(nil) || !fk.from_columns || fk.from_columns.include?(nil) [ fk.to.name, fk.to_columns.map{|col| col.name(nil).sort}, fk.from_columns.map{|col| col.name(nil).sort} ] end end end |
#foreign_keys_to ⇒ Object
119 120 121 |
# File 'lib/activefacts/persistence/foreignkey.rb', line 119 def foreign_keys_to @foreign_keys_to ||= [] end |
#has_references ⇒ Object
True if this ObjectType has any References (to or from)
283 284 285 |
# File 'lib/activefacts/persistence/reference.rb', line 283 def has_references #:nodoc: @references_from || @references_to end |
#indices ⇒ Object
An array of each Index for this table
106 107 108 |
# File 'lib/activefacts/persistence/index.rb', line 106 def indices @indices || populate_indices end |
#is_separate ⇒ Object
415 416 417 |
# File 'lib/activefacts/vocabulary/extensions.rb', line 415 def is_separate is_independent or concept.all_concept_annotation.detect{|ca| ca.mapping_annotation == 'separate'} end |
#populate_columns ⇒ Object
:nodoc:
255 256 257 258 |
# File 'lib/activefacts/persistence/columns.rb', line 255 def populate_columns #:nodoc: @columns = all_columns({}) end |
#populate_indices ⇒ Object
:nodoc:
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 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/activefacts/persistence/index.rb', line 115 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 = trace :index2, "Indexing columns by ref_path" do columns.inject({}) do |hash, column| trace :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 trace :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 # trace :index2, "Considering #{ref_path.map(&:to_s)*" and "} yielding columns #{all_column_by_ref_path[ref_path].map{|c| c.name('.')}*", "}" 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 trace :index, "All Indices in #{name}:" do @indices = columns_by_unique_constraint.map do |uc, columns_with_ordinal| trace :index, "Index due to uc #{uc.concept.guid} on #{name} over (#{columns_with_ordinal.sort_by{|onc|onc[0]}.map{|ca| 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: 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) next nil end index = ActiveFacts::Persistence::Index.new( uc, self, over, columns, uc.is_preferred_identifier ) trace :index, index index end. compact. sort_by do |index| # Put the indices in a defined order: index.columns.map(&:name)+['', index.over.name] end end si = self_index @indices.unshift(si) if si @indices end |
#populate_reference(role) ⇒ Object
:nodoc:
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 |
# File 'lib/activefacts/persistence/reference.rb', line 306 def populate_reference role #:nodoc: role_type = role.role_type trace :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 #trace :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 raise "Internal error, expected TypeInheritance" unless role.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance) counterpart_role = (role.fact_type.all_role.to_a-[role])[0] if role.fact_type.assimilation or counterpart_role.object_type.is_separate trace :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 trace :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 role.fact_type.assimilation or is_separate 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 # trace :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: # REVISIT: Decide whether supertype assimilation can affect this r.tabulate and return if is_separate and !r.to.is_separate return if !is_separate and r.to.is_separate 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} trace :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} trace :references, "EntityType #{name} identifies EntityType #{r.to.name}, so absorbs it" r.to.absorbed_via = r # We can't be absorbed into our supertype! # REVISIT: We might need to flip all one-to-ones as well r.to.references_to.clone.map{|q|q.flip if q.to_role.role_type == :subtype } 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, # but not frivolously; the Ruby API column name generation duplicates this logic. 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 # REVISIT: Should we implicitly objectify this fact type here and add a spanning UC? raise "Role #{role.object_type.name} in '#{role.fact_type.default_reading}' lacks a uniqueness constraint" end end |
#populate_references ⇒ Object
:nodoc:
293 294 295 296 297 298 299 300 301 302 303 304 |
# File 'lib/activefacts/persistence/reference.rb', line 293 def populate_references #:nodoc: all_role.each do |role| # 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?(LinkFactType) or # REVISIT: dafuq? Is this looking for a constraint over a derivation? This looks wrong. role.fact_type.preferred_reading.role_sequence.all_role_ref.to_a[0].play or # This is not yet actually set, and wouldn't handle constraint derivations anyhow: role.variable_as_projection populate_reference role end end |
#probably_not_table ⇒ Object
:nodoc:
267 268 269 270 |
# File 'lib/activefacts/persistence/reference.rb', line 267 def probably_not_table #:nodoc: @is_table = false @tentative = true end |
#probably_table ⇒ Object
:nodoc:
262 263 264 265 |
# File 'lib/activefacts/persistence/reference.rb', line 262 def probably_table #:nodoc: @is_table = true @tentative = true end |
#rails_class_name ⇒ Object
127 128 129 |
# File 'lib/activefacts/mapping/rails.rb', line 127 def rails_class_name ActiveSupport::Inflector.camelize(name.gsub(/\s+/, '_')) end |
#rails_name ⇒ Object
119 120 121 |
# File 'lib/activefacts/mapping/rails.rb', line 119 def rails_name Persistence::rails_plural_name(name) end |
#rails_singular_name ⇒ Object
123 124 125 |
# File 'lib/activefacts/mapping/rails.rb', line 123 def rails_singular_name Persistence::rails_singular_name(name) end |
#references_from ⇒ Object
References from this ObjectType
273 274 275 |
# File 'lib/activefacts/persistence/reference.rb', line 273 def references_from @references_from ||= [] end |
#references_to ⇒ Object
References to this ObjectType
278 279 280 |
# File 'lib/activefacts/persistence/reference.rb', line 278 def references_to @references_to ||= [] end |
#show_tabular ⇒ Object
:nodoc:
247 248 249 250 |
# File 'lib/activefacts/persistence/reference.rb', line 247 def show_tabular #:nodoc: (tentative ? "tentatively " : "") + (is_table ? "" : "not ")+"a table" end |
#wipe_columns ⇒ Object
260 261 262 |
# File 'lib/activefacts/persistence/columns.rb', line 260 def wipe_columns @columns = nil end |