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.
-
#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:.
Instance Attribute Details
#injected_surrogate_role ⇒ Object (readonly)
Placeholder for the surrogate transform
250 251 252 |
# File 'lib/activefacts/vocabulary/extensions.rb', line 250 def injected_surrogate_role @injected_surrogate_role end |
#is_table=(value) ⇒ Object (writeonly)
The two ObjectType subclasses provide the attr_reader method
224 225 226 |
# File 'lib/activefacts/persistence/reference.rb', line 224 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
223 224 225 |
# File 'lib/activefacts/persistence/reference.rb', line 223 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
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/activefacts/persistence/foreignkey.rb', line 97 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) debug :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:
266 267 268 269 270 |
# File 'lib/activefacts/persistence/reference.rb', line 266 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
227 228 229 |
# File 'lib/activefacts/persistence/columns.rb', line 227 def columns @columns || populate_columns end |
#definitely_not_table ⇒ Object
:nodoc:
236 237 238 239 |
# File 'lib/activefacts/persistence/reference.rb', line 236 def definitely_not_table #:nodoc: @is_table = false @tentative = false end |
#definitely_table ⇒ Object
:nodoc:
231 232 233 234 |
# File 'lib/activefacts/persistence/reference.rb', line 231 def definitely_table #:nodoc: @is_table = true @tentative = false end |
#foreign_keys ⇒ Object
Return an array of all the foreign keys from this table
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 |
# File 'lib/activefacts/persistence/foreignkey.rb', line 125 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| debug :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 } debug :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 debug :fk, "Reference target #{fk_ref_path.last.to.name} is absorbed via:" do while (r = to.absorbed_via) m = r.reversed debug :fk, "#{m.reading}" fk_ref_path << m to = m.from == to ? m.to : m.from end debug :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
120 121 122 |
# File 'lib/activefacts/persistence/foreignkey.rb', line 120 def foreign_keys_to @foreign_keys_to ||= [] end |
#has_references ⇒ Object
True if this ObjectType has any References (to or from)
262 263 264 |
# File 'lib/activefacts/persistence/reference.rb', line 262 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 |
#populate_columns ⇒ Object
:nodoc:
231 232 233 234 |
# File 'lib/activefacts/persistence/columns.rb', line 231 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 = 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('.')}*", "}" 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| debug :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 ) debug :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:
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 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 |
# File 'lib/activefacts/persistence/reference.rb', line 285 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 raise hell unless role.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance) if role.fact_type.assimilation # assimilation == 'separate' or assimilation == 'partitioned' 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 role.fact_type.assimilation 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: # REVISIT: Decide whether supertype assimilation can affect this 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 # 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 raise "Role #{role.object_type.name} in '#{role.fact_type.default_reading}' lacks a uniqueness constraint" end end |
#populate_references ⇒ Object
:nodoc:
272 273 274 275 276 277 278 279 280 281 282 283 |
# File 'lib/activefacts/persistence/reference.rb', line 272 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:
246 247 248 249 |
# File 'lib/activefacts/persistence/reference.rb', line 246 def probably_not_table #:nodoc: @is_table = false @tentative = true end |
#probably_table ⇒ Object
:nodoc:
241 242 243 244 |
# File 'lib/activefacts/persistence/reference.rb', line 241 def probably_table #:nodoc: @is_table = true @tentative = true end |
#rails_class_name ⇒ Object
117 118 119 |
# File 'lib/activefacts/mapping/rails.rb', line 117 def rails_class_name ActiveSupport::Inflector.camelize(name.gsub(/\s+/, '_')) end |
#rails_name ⇒ Object
109 110 111 |
# File 'lib/activefacts/mapping/rails.rb', line 109 def rails_name Persistence::rails_plural_name(name) end |
#rails_singular_name ⇒ Object
113 114 115 |
# File 'lib/activefacts/mapping/rails.rb', line 113 def rails_singular_name Persistence::rails_singular_name(name) end |
#references_from ⇒ Object
References from this ObjectType
252 253 254 |
# File 'lib/activefacts/persistence/reference.rb', line 252 def references_from @references_from ||= [] end |
#references_to ⇒ Object
References to this ObjectType
257 258 259 |
# File 'lib/activefacts/persistence/reference.rb', line 257 def references_to @references_to ||= [] end |
#show_tabular ⇒ Object
:nodoc:
226 227 228 229 |
# File 'lib/activefacts/persistence/reference.rb', line 226 def show_tabular #:nodoc: (tentative ? "tentatively " : "") + (is_table ? "" : "not ")+"a table" end |