Class: ActiveFacts::Metamodel::Vocabulary
- Inherits:
-
Object
- Object
- ActiveFacts::Metamodel::Vocabulary
- Defined in:
- lib/activefacts/rmap/columns.rb,
lib/activefacts/rmap/index.rb,
lib/activefacts/rmap/tables.rb,
lib/activefacts/rmap/reference.rb
Overview
The Vocabulary class is defined in the metamodel; full documentation is not generated. This section shows the features relevant to relational mapping.
Constant Summary collapse
- @@relational_transforms =
[]
Class Method Summary collapse
Instance Method Summary collapse
-
#decide_tables ⇒ Object
:nodoc:.
-
#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.
- #wipe_existing_mapping ⇒ Object
Class Method Details
.relational_transform(&block) ⇒ Object
174 175 176 177 178 179 |
# File 'lib/activefacts/rmap/tables.rb', line 174 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
#decide_tables ⇒ Object
:nodoc:
190 191 192 193 194 195 196 197 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 |
# File 'lib/activefacts/rmap/tables.rb', line 190 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/separate 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 trace :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 trace :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 trace :absorption do trace :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 trace :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| trace :absorption, "Considering #{object_type.name}:" do trace :absorption, "refs to #{object_type.name} are from #{object_type.references_to.map{|ref| ref.from.name}*", "}" if object_type.references_to.size > 0 trace :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 trace :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) trace :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 do |ref| if ref.from_role == first_pi_role and ref.from.is_a?(EntityType) # and ref.is_mandatory # REVISIT pi_ref = ref end end trace :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) } trace :absorption, "#{object_type.name} has #{.size} non-identifying functional roles" do .each do |ref| trace :absorption, "#{ref.inspect}" end end trace :absorption, "#{object_type.name} has #{object_type.references_to.size} references to it:" do object_type.references_to.each do |ref| trace :absorption, ref.inspect end end if object_type.references_to.size > 1 if object_type.references_to.size > 1 and .size > 0 trace :absorption, "#{object_type.name} has #{.size} non-identifying functional dependencies and #{object_type.references_to.size} absorption paths 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) trace :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 trace :absorption, "#{object_type.name} is fully absorbed through #{absorption_paths.inspect}" do absorption_paths.each do |ref| flip = object_type == ref.from ref.flip if flip trace :absorption, "#{object_type.name} is FULLY ABSORBED via {ref}#{flip ? ' (flipped)' : ''}" end end object_type.definitely_not_table next object_type end if .size == 0 trace :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 trace :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} trace :absorption, "Flipping references from #{object_type.name}; they're all to tables" object_type.references_from.map(&:flip) else trace :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 trace :absorption undecided.each do |object_type| trace :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 |
#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
418 419 420 421 422 |
# File 'lib/activefacts/rmap/columns.rb', line 418 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:
424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 |
# File 'lib/activefacts/rmap/columns.rb', line 424 def populate_all_columns #:nodoc: # REVISIT: Is now a good time to apply schema transforms or should this be more explicit? finish_schema trace :columns, "Populating all columns" do tables.each do |object_type| trace :columns, "Populating columns for table #{object_type.name}" do object_type.populate_columns end end end trace :columns, "Finished columns" do tables.each do |object_type| trace :columns, "Finished columns for table #{object_type.name}" do object_type.columns.each do |column| trace :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 |
# File 'lib/activefacts/rmap/index.rb', line 213 def populate_all_indices #:nodoc: trace :index, "Populating all object_type indices" do all_object_type.each do |object_type| object_type.clear_indices end tables.each do |object_type| trace :index, "Populating indices for #{object_type.name}" do object_type.populate_indices end end end trace :index, "Finished object_type indices" do tables.each do |object_type| trace :index?, "#{object_type.name}:" do object_type.indices.each do |index| trace :index, index end end end end end |
#populate_all_references ⇒ Object
:nodoc:
404 405 406 407 408 409 410 411 412 413 |
# File 'lib/activefacts/rmap/reference.rb', line 404 def populate_all_references #:nodoc: trace :references, "Populating all object_type references" do all_object_type.each do |object_type| trace :references, "Populating references for #{object_type.name}" do object_type.populate_references end end end show_all_references end |
#show_all_references ⇒ Object
415 416 417 418 419 420 421 422 423 424 425 426 427 |
# File 'lib/activefacts/rmap/reference.rb', line 415 def show_all_references if trace :references trace :references, "Finished object_type references" do all_object_type.each do |object_type| trace :references?, "#{object_type.name}:" do object_type.references_from.each do |ref| trace :references, "#{ref}" end end end end end end |
#tables ⇒ Object
return an Array of ObjectTypes that will have their own tables
168 169 170 171 172 |
# File 'lib/activefacts/rmap/tables.rb', line 168 def tables decide_tables if !@tables @@relational_transforms.each{|tr| tr.call(self)} @tables end |
#wipe_existing_mapping ⇒ Object
181 182 183 184 185 186 187 188 |
# File 'lib/activefacts/rmap/tables.rb', line 181 def wipe_existing_mapping all_object_type.each do |object_type| object_type.clear_references object_type.wipe_columns object_type.is_table = nil # Undecided; force an attempt to decide object_type.tentative = true # Uncertain end end |