Class: ActiveFacts::Metamodel::Vocabulary
- Inherits:
-
Object
- Object
- ActiveFacts::Metamodel::Vocabulary
- Defined in:
- lib/activefacts/persistence/columns.rb,
lib/activefacts/persistence/index.rb,
lib/activefacts/persistence/tables.rb,
lib/activefacts/vocabulary/metamodel.rb,
lib/activefacts/persistence/reference.rb,
lib/activefacts/vocabulary/extensions.rb
Overview
The Vocabulary class is defined in the metamodel; full documentation is not generated. This section shows the features relevant to relational Persistence.
Constant Summary collapse
- @@relational_transforms =
[]
Class Method Summary collapse
Instance Method Summary collapse
-
#check_valid_nonexistent_object_type_name(name) ⇒ Object
This name does not yet exist (at least not as we expect it to).
-
#decide_tables ⇒ Object
:nodoc:.
- #finalise ⇒ Object
-
#finish_schema ⇒ Object
Make schema transformations like adding ValueType self-value columns (and later, Rails-friendly ID fields).
-
#populate_all_columns ⇒ Object
:nodoc:.
-
#populate_all_indices ⇒ Object
:nodoc:.
-
#populate_all_references ⇒ Object
:nodoc:.
- #show_all_references ⇒ Object
-
#tables ⇒ Object
return an Array of ObjectTypes that will have their own tables.
-
#valid_entity_type_name(name) ⇒ Object
If this entity type exists, ok, otherwise check it’s ok to add it.
- #valid_object_type_name(name) ⇒ Object
-
#valid_value_type_name(name) ⇒ Object
If this entity type exists, ok, otherwise check it’s ok to add it.
- #wipe_existing_mapping ⇒ Object
Class Method Details
.relational_transform(&block) ⇒ Object
183 184 185 186 187 188 |
# File 'lib/activefacts/persistence/tables.rb', line 183 def self.relational_transform &block # Add this block to the additional transformations which will be applied # to the relational schema after the initial absorption. # For example, to perform injection of surrogate keys to replace composite keys... @@relational_transforms << block end |
Instance Method Details
#check_valid_nonexistent_object_type_name(name) ⇒ Object
This name does not yet exist (at least not as we expect it to). If it in fact does exist (but as the wrong type), complain. If it doesn’t exist, but its name would cause existing fact type readings to be re-interpreted to a different meaning, complain. Otherwise return nil.
25 26 27 28 29 |
# File 'lib/activefacts/vocabulary/extensions.rb', line 25 def check_valid_nonexistent_object_type_name name if ot = valid_object_type_name(name) raise "Cannot redefine #{ot.class.basename} #{name}" end end |
#decide_tables ⇒ Object
:nodoc:
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 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 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 |
# File 'lib/activefacts/persistence/tables.rb', line 198 def decide_tables #:nodoc: # Strategy: # 1) Populate references for all ObjectTypes # 2) Decide which ObjectTypes must be and must not be tables # a. ObjectTypes labelled is_independent are tables (See the is_table methods above) # b. Entity types having no references to them must be tables # c. subtypes are not tables unless marked with assimilation = separate or partitioned # d. ValueTypes are never tables unless they independent or can have references (to other ValueTypes) # e. An EntityType having an identifying AutoInc field must be a table unless it has exactly one reference # f. An EntityType whose only reference is through its single preferred_identifier role gets absorbed # g. An EntityType that must has references other than its PI must be a table (unless it has exactly one reference to it) # h. supertypes are elided if all roles are absorbed into subtypes: # - partitioned subtype exhaustion # - subtype extension where supertype has only PI roles and no AutoInc # 3) any ValueType that has references from it must become a table if not already wipe_existing_mapping populate_all_references debug :absorption, "Calculating relational composition" do # Evaluate the possible independence of each object_type, building an array of object_types of indeterminate status: undecided = all_object_type.select do |object_type| object_type.is_table # Ask it whether it thinks it should be a table object_type.tentative # Selection criterion end if debug :absorption, "Generating tables, #{undecided.size} undecided, already decided ones are" (all_object_type-undecided).each {|object_type| next if ValueType === object_type && !object_type.is_table # Skip unremarkable cases debug :absorption do debug :absorption, "#{object_type.name} is #{object_type.is_table ? "" : "not "}a table#{object_type.tentative ? ", tentatively" : ""}" end } end pass = 0 begin # Loop while we continue to make progress pass += 1 debug :absorption, "Starting composition pass #{pass} with #{undecided.size} undecided tables" possible_flips = {} # A hash by table containing an array of references that can be flipped finalised = # Make an array of things we finalised during this pass undecided.select do |object_type| debug :absorption, "Considering #{object_type.name}:" do debug :absorption, "refs to #{object_type.name} are from #{object_type.references_to.map{|ref| ref.from.name}*", "}" if object_type.references_to.size > 0 debug :absorption, "refs from #{object_type.name} are to #{object_type.references_from.map{|ref| ref.to ? ref.to.name : ref.fact_type.default_reading}*", "}" if object_type.references_from.size > 0 # Always absorb an objectified unary into its role player: if object_type.fact_type && object_type.fact_type.all_role.size == 1 debug :absorption, "Absorb objectified unary #{object_type.name} into #{object_type.fact_type.entity_type.name}" object_type.definitely_not_table next object_type end # If the PI contains one role only, played by an entity type that can absorb us, do that. pi_roles = object_type.preferred_identifier.role_sequence.all_role_ref.map(&:role) debug :absorption, "pi_roles are played by #{pi_roles.map{|role| role.object_type.name}*", "}" first_pi_role = pi_roles[0] pi_ref = nil if pi_roles.size == 1 and object_type.references_to.detect{|ref| pi_ref = ref if ref.from_role == first_pi_role && ref.from.is_a?(EntityType)} debug :absorption, "#{object_type.name} is fully absorbed along its sole reference path into entity type #{pi_ref.from.name}" object_type.definitely_not_table next object_type end # If there's more than one absorption path and any functional dependencies that can't absorb us, it's a table = object_type.references_from.reject{|ref| pi_roles.include?(ref.to_role) } debug :absorption, "#{object_type.name} has #{.size} non-identifying functional roles" =begin # This is kinda arbitrary. We need a policy for evaluating optional flips, so we can decide if they "improve" things. # The flipping that occurs below always eliminates a table by absorption, but this doesn't. # If all non-identifying functional roles are one-to-ones that can be flipped, do that: if non_identifying_refs_from.all? { |ref| ref.role_type == :one_one && (ref.to.is_table || ref.to.tentative) } debug :absorption, "Flipping references from #{object_type.name}" do non_identifying_refs_from.each do |ref| debug :absorption, "Flipping #{ref}" ref.flip end end non_identifying_refs_from = [] end =end if object_type.references_to.size > 1 and .size > 0 debug :absorption, "#{object_type.name} has non-identifying functional dependencies so 3NF requires it be a table" object_type.definitely_table next object_type end absorption_paths = ( .reject do |ref| !ref.to or ref.to.absorbed_via == ref end+object_type.references_to ).reject do |ref| next true if !ref.to.is_table or !ref.is_one_to_one # Don't absorb an object along a non-mandatory role (otherwise if it doesn't play that role, it can't exist either) from_is_mandatory = !!ref.is_mandatory to_is_mandatory = !ref.to_role || !!ref.to_role.is_mandatory bad = !(ref.from == object_type ? from_is_mandatory : to_is_mandatory) debug :absorption, "Not absorbing #{object_type.name} through non-mandatory #{ref}" if bad bad end # If this object can be fully absorbed, do that (might require flipping some references) if absorption_paths.size > 0 debug :absorption, "#{object_type.name} is fully absorbed through #{absorption_paths.inspect}" absorption_paths.each do |ref| debug :absorption, "Flipping #{ref} so #{object_type.name} can be absorbed" ref.flip if object_type == ref.from end object_type.definitely_not_table next object_type end if .size == 0 # and (!object_type.is_a?(EntityType) || # # REVISIT: The roles may be collectively but not individually mandatory. # object_type.references_to.detect { |ref| !ref.from_role || ref.from_role.is_mandatory }) debug :absorption, "#{object_type.name} is fully absorbed in #{object_type.references_to.size} places: #{object_type.references_to.map{|ref| ref.from.name}*", "}" object_type.definitely_not_table next object_type end false # Failed to decide about this entity_type this time around end end undecided -= finalised debug :absorption, "Finalised #{finalised.size} this pass: #{finalised.map{|f| f.name}*", "}" end while !finalised.empty? # A ValueType that isn't explicitly a table and isn't needed anywhere doesn't matter, # unless it should absorb something else (another ValueType is all it could be): all_object_type.each do |object_type| if (!object_type.is_table and object_type.references_to.size == 0 and object_type.references_from.size > 0) if !object_type.references_from.detect{|r| !r.is_one_to_one || !r.to.is_table} debug :absorption, "Flipping references from #{object_type.name}; they're all to tables" object_type.references_from.map(&:flip) else debug :absorption, "Making #{object_type.name} a table; it has nowhere else to go and needs to absorb things" object_type.probably_table end end end # Now, evaluate all possibilities of the tentative assignments # Incomplete. Apparently unnecessary as well... so far. We'll see. if debug :absorption undecided.each do |object_type| debug :absorption, "Unable to decide independence of #{object_type.name}, going with #{object_type.show_tabular}" end end end @tables = all_object_type. select { |f| f.is_table }. sort_by { |table| table.name } end |
#finalise ⇒ Object
10 11 12 13 14 15 16 17 18 |
# File 'lib/activefacts/vocabulary/extensions.rb', line 10 def finalise constellation.FactType.values.each do |fact_type| if c = fact_type.check_and_add_spanning_uniqueness_constraint debug :constraint, "Checking for existence of at least one uniqueness constraint over the roles of #{fact_type.default_reading.inspect}" fact_type.check_and_add_spanning_uniqueness_constraint = nil c.call end end end |
#finish_schema ⇒ Object
Make schema transformations like adding ValueType self-value columns (and later, Rails-friendly ID fields). Override this method to change the transformations
385 386 387 388 389 |
# File 'lib/activefacts/persistence/columns.rb', line 385 def finish_schema all_object_type.each do |object_type| object_type.self_value_reference if object_type.is_a?(ActiveFacts::Metamodel::ValueType) && object_type.is_table end end |
#populate_all_columns ⇒ Object
:nodoc:
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 |
# File 'lib/activefacts/persistence/columns.rb', line 391 def populate_all_columns #:nodoc: # REVISIT: Is now a good time to apply schema transforms or should this be more explicit? finish_schema debug :columns, "Populating all columns" do all_object_type.each do |object_type| next if !object_type.is_table debug :columns, "Populating columns for table #{object_type.name}" do object_type.populate_columns end end end debug :columns, "Finished columns" do all_object_type.each do |object_type| next if !object_type.is_table debug :columns, "Finished columns for table #{object_type.name}" do object_type.columns.each do |column| debug :columns, "#{column}" end end end end end |
#populate_all_indices ⇒ Object
:nodoc:
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/activefacts/persistence/index.rb', line 213 def populate_all_indices #:nodoc: debug :index, "Populating all object_type indices" do all_object_type.each do |object_type| object_type.clear_indices end all_object_type.each do |object_type| next unless object_type.is_table debug :index, "Populating indices for #{object_type.name}" do object_type.populate_indices end end end debug :index, "Finished object_type indices" do all_object_type.each do |object_type| next unless object_type.is_table next unless object_type.indices.size > 0 debug :index, "#{object_type.name}:" do object_type.indices.each do |index| debug :index, index end end end end end |
#populate_all_references ⇒ Object
:nodoc:
383 384 385 386 387 388 389 390 391 392 |
# File 'lib/activefacts/persistence/reference.rb', line 383 def populate_all_references #:nodoc: debug :references, "Populating all object_type references" do all_object_type.each do |object_type| debug :references, "Populating references for #{object_type.name}" do object_type.populate_references end end end show_all_references end |
#show_all_references ⇒ Object
394 395 396 397 398 399 400 401 402 403 404 405 406 407 |
# File 'lib/activefacts/persistence/reference.rb', line 394 def show_all_references if debug :references 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 end end |
#tables ⇒ Object
return an Array of ObjectTypes that will have their own tables
177 178 179 180 181 |
# File 'lib/activefacts/persistence/tables.rb', line 177 def tables decide_tables if !@tables @@relational_transforms.each{|tr| tr.call(self)} @tables end |
#valid_entity_type_name(name) ⇒ Object
If this entity type exists, ok, otherwise check it’s ok to add it
57 58 59 60 |
# File 'lib/activefacts/vocabulary/extensions.rb', line 57 def valid_entity_type_name name @constellation.EntityType[[, name]] or check_valid_nonexistent_object_type_name name end |
#valid_object_type_name(name) ⇒ Object
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/activefacts/vocabulary/extensions.rb', line 31 def valid_object_type_name name # Raise an exception if adding this name to the vocabulary would create anomalies anomaly = constellation.Reading.detect do |r_key, reading| = reading. do |role_ref, *words| words.map! do |w| case when w == nil w when w[0...name.size] == name '_ok_'+w when w[-name.size..-1] == name w[-1]+'_ok_' else w end end words end =~ %r{\b#{name}\b} end raise "Adding new term '#{name}' would create anomalous re-interpretation of '#{anomaly.}'" if anomaly @constellation.ObjectType[[, name]] end |
#valid_value_type_name(name) ⇒ Object
If this entity type exists, ok, otherwise check it’s ok to add it
63 64 65 66 |
# File 'lib/activefacts/vocabulary/extensions.rb', line 63 def valid_value_type_name name @constellation.ValueType[[, name]] or check_valid_nonexistent_object_type_name name end |
#wipe_existing_mapping ⇒ Object
190 191 192 193 194 195 196 |
# File 'lib/activefacts/persistence/tables.rb', line 190 def wipe_existing_mapping 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 end |