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
Overview
The Vocabulary class is defined in the metamodel; full documentation is not generated. This section shows the features relevant to relational Persistence.
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:.
-
#tables ⇒ Object
return an Array of Concepts that will have their own tables.
Instance Method Details
#decide_tables ⇒ Object
:nodoc:
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 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 |
# File 'lib/activefacts/persistence/tables.rb', line 165 def decide_tables #:nodoc: # Strategy: # 1) Populate references for all Concepts # 2) Decide which Concepts must be and must not be tables # a. Concepts 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 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 populate_all_references debug :absorption, "Calculating relational composition" do # Evaluate the possible independence of each concept, building an array of concepts of indeterminate status: undecided = all_concept.select do |concept| concept.is_table # Ask it whether it thinks it should be a table concept.tentative # Selection criterion end if debug :absorption, "Generating tables, #{undecided.size} undecided" (all_concept-undecided).each {|concept| next if ValueType === concept && !concept.is_table # Skip unremarkable cases debug :absorption do debug :absorption, "#{concept.name} is #{concept.is_table ? "" : "not "}a table#{concept.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 |concept| debug :absorption, "Considering #{concept.name}:" do debug :absorption, "refs to #{concept.name} are from #{concept.references_to.map{|ref| ref.from.name}*", "}" if concept.references_to.size > 0 debug :absorption, "refs from #{concept.name} are to #{concept.references_from.map{|ref| ref.to.name rescue ref.fact_type.default_reading}*", "}" if concept.references_from.size > 0 # Always absorb an objectified unary into its role player: if concept.fact_type && concept.fact_type.all_role.size == 1 debug :absorption, "Absorb objectified unary #{concept.name} into #{concept.fact_type.entity_type.name}" concept.definitely_not_table next concept end # If the PI contains one role only, played by an entity type that can absorb us, do that. pi_roles = concept.preferred_identifier.role_sequence.all_role_ref.map(&:role) debug :absorption, "pi_roles are played by #{pi_roles.map{|role| role.concept.name}*", "}" first_pi_role = pi_roles[0] pi_ref = nil if pi_roles.size == 1 and concept.references_to.detect{|ref| pi_ref = ref if ref.from_role == first_pi_role && ref.from.is_a?(EntityType)} debug :absorption, "#{concept.name} is fully absorbed along its sole reference path into entity type #{pi_ref.from.name}" concept.definitely_not_table next concept end # If there's more than one absorption path and any functional dependencies that can't absorb us, it's a table = concept.references_from.reject{|ref| pi_roles.include?(ref.to_role) } debug :absorption, "#{concept.name} has #{.size} non-identifying functional roles" if concept.references_to.size > 1 and .size > 0 debug :absorption, "#{concept.name} has non-identifying functional dependencies so 3NF requires it be a table" concept.definitely_table next concept end absorption_paths = ( .reject do |ref| !ref.to or ref.to.absorbed_via == ref end+concept.references_to ).reject do |ref| next true if !ref.to.is_table or ![:one_one, :supertype, :subtype].include?(ref.role_type) # 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 == concept ? from_is_mandatory : to_is_mandatory) debug :absorption, "Not absorbing #{concept.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, "#{concept.name} is fully absorbed through #{absorption_paths.inspect}" absorption_paths.each do |ref| debug :absorption, "flip #{ref} so #{concept.name} can be absorbed" ref.flip if concept == ref.from end concept.definitely_not_table next concept end if .size == 0 # and (!concept.is_a?(EntityType) || # # REVISIT: The roles may be collectively but not individually mandatory. # concept.references_to.detect { |ref| !ref.from_role || ref.from_role.is_mandatory }) debug :absorption, "#{concept.name} is fully absorbed in #{concept.references_to.size} places: #{concept.references_to.map{|ref| ref.from.name}*", "}" concept.definitely_not_table next concept 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_concept.each do |concept| if (!concept.is_table and concept.references_to.size == 0 and concept.references_from.size > 0) debug :absorption, "Making #{concept.name} a table; it has nowhere else to go and needs to absorb things" concept.probably_table 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 |concept| debug :absorption, "Unable to decide independence of #{concept.name}, going with #{concept.show_tabular}" end end end populate_all_columns populate_all_indices @tables = all_concept. 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
365 366 367 368 369 |
# File 'lib/activefacts/persistence/columns.rb', line 365 def finish_schema all_concept.each do |concept| concept.self_value_reference if concept.is_a?(ActiveFacts::Metamodel::ValueType) && concept.is_table end end |
#populate_all_columns ⇒ Object
:nodoc:
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 |
# File 'lib/activefacts/persistence/columns.rb', line 371 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_concept.each do |concept| next if !concept.is_table debug :columns, "Populating columns for table #{concept.name}" do concept.populate_columns end end end debug :columns, "Finished columns" do all_concept.each do |concept| next if !concept.is_table debug :columns, "Finished columns for table #{concept.name}" do concept.columns.each do |column| debug :columns, "#{column}" end end end end end |
#populate_all_indices ⇒ Object
:nodoc:
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
# File 'lib/activefacts/persistence/index.rb', line 183 def populate_all_indices #:nodoc: debug :index, "Populating all concept indices" do all_concept.each do |concept| concept.clear_indices end all_concept.each do |concept| next unless concept.is_table debug :index, "Populating indices for #{concept.name}" do concept.populate_indices end end end debug :index, "Finished concept indices" do all_concept.each do |concept| next unless concept.is_table next unless concept.indices.size > 0 debug :index, "#{concept.name}:" do concept.indices.each do |index| debug :index, index end end end end end |
#populate_all_references ⇒ Object
:nodoc:
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 |
# File 'lib/activefacts/persistence/reference.rb', line 342 def populate_all_references #:nodoc: debug :references, "Populating all concept references" do all_concept.each do |concept| concept.clear_references concept.is_table = nil # Undecided; force an attempt to decide concept.tentative = true # Uncertain end all_concept.each do |concept| debug :references, "Populating references for #{concept.name}" do concept.populate_references end end end debug :references, "Finished concept references" do all_concept.each do |concept| next unless concept.references_from.size > 0 debug :references, "#{concept.name}:" do concept.references_from.each do |ref| debug :references, "#{ref}" end end end end end |
#tables ⇒ Object
return an Array of Concepts that will have their own tables
160 161 162 163 |
# File 'lib/activefacts/persistence/tables.rb', line 160 def tables decide_tables if !@tables @tables end |