activefacts 0.8.16 → 0.8.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +15 -0
  2. data/Manifest.txt +10 -4
  3. data/bin/afgen +26 -20
  4. data/bin/cql +1 -1
  5. data/css/orm2.css +89 -9
  6. data/examples/CQL/CompanyDirectorEmployee.cql +4 -4
  7. data/examples/CQL/Genealogy.cql +5 -5
  8. data/examples/CQL/Metamodel.cql +121 -91
  9. data/examples/CQL/MonthInSeason.cql +2 -6
  10. data/examples/CQL/SeparateSubtype.cql +11 -9
  11. data/examples/CQL/ServiceDirector.cql +21 -33
  12. data/examples/CQL/Supervision.cql +0 -3
  13. data/examples/CQL/WindowInRoomInBldg.cql +10 -4
  14. data/examples/CQL/unit.cql +1 -1
  15. data/lib/activefacts.rb +1 -0
  16. data/lib/activefacts/cql/CQLParser.treetop +5 -1
  17. data/lib/activefacts/cql/Context.treetop +2 -7
  18. data/lib/activefacts/cql/Expressions.treetop +2 -2
  19. data/lib/activefacts/cql/FactTypes.treetop +37 -31
  20. data/lib/activefacts/cql/Language/English.treetop +21 -4
  21. data/lib/activefacts/cql/LexicalRules.treetop +59 -1
  22. data/lib/activefacts/cql/ObjectTypes.treetop +22 -12
  23. data/lib/activefacts/cql/Terms.treetop +13 -9
  24. data/lib/activefacts/cql/ValueTypes.treetop +30 -11
  25. data/lib/activefacts/cql/compiler.rb +34 -5
  26. data/lib/activefacts/cql/compiler/clause.rb +207 -116
  27. data/lib/activefacts/cql/compiler/constraint.rb +129 -105
  28. data/lib/activefacts/cql/compiler/entity_type.rb +49 -27
  29. data/lib/activefacts/cql/compiler/expression.rb +71 -42
  30. data/lib/activefacts/cql/compiler/fact.rb +70 -64
  31. data/lib/activefacts/cql/compiler/fact_type.rb +108 -57
  32. data/lib/activefacts/cql/compiler/query.rb +178 -0
  33. data/lib/activefacts/cql/compiler/shared.rb +13 -12
  34. data/lib/activefacts/cql/compiler/value_type.rb +10 -4
  35. data/lib/activefacts/cql/nodes.rb +1 -1
  36. data/lib/activefacts/cql/parser.rb +6 -2
  37. data/lib/activefacts/generate/absorption.rb +6 -3
  38. data/lib/activefacts/generate/cql.rb +140 -84
  39. data/lib/activefacts/generate/dm.rb +12 -6
  40. data/lib/activefacts/generate/help.rb +25 -6
  41. data/lib/activefacts/generate/helpers/oo.rb +195 -0
  42. data/lib/activefacts/generate/helpers/ordered.rb +589 -0
  43. data/lib/activefacts/generate/helpers/rails.rb +57 -0
  44. data/lib/activefacts/generate/html/glossary.rb +274 -54
  45. data/lib/activefacts/generate/json.rb +25 -22
  46. data/lib/activefacts/generate/null.rb +1 -0
  47. data/lib/activefacts/generate/rails/models.rb +244 -0
  48. data/lib/activefacts/generate/rails/schema.rb +185 -0
  49. data/lib/activefacts/generate/records.rb +1 -0
  50. data/lib/activefacts/generate/ruby.rb +51 -30
  51. data/lib/activefacts/generate/sql/mysql.rb +5 -3
  52. data/lib/activefacts/generate/sql/server.rb +8 -4
  53. data/lib/activefacts/generate/text.rb +1 -0
  54. data/lib/activefacts/generate/transform/surrogate.rb +209 -0
  55. data/lib/activefacts/generate/version.rb +1 -0
  56. data/lib/activefacts/input/orm.rb +234 -181
  57. data/lib/activefacts/mapping/rails.rb +122 -0
  58. data/lib/activefacts/persistence/columns.rb +34 -18
  59. data/lib/activefacts/persistence/foreignkey.rb +129 -71
  60. data/lib/activefacts/persistence/index.rb +42 -12
  61. data/lib/activefacts/persistence/reference.rb +37 -23
  62. data/lib/activefacts/persistence/tables.rb +53 -19
  63. data/lib/activefacts/registry.rb +11 -0
  64. data/lib/activefacts/support.rb +28 -10
  65. data/lib/activefacts/version.rb +1 -1
  66. data/lib/activefacts/vocabulary/extensions.rb +246 -117
  67. data/lib/activefacts/vocabulary/metamodel.rb +105 -65
  68. data/lib/activefacts/vocabulary/verbaliser.rb +226 -194
  69. data/spec/absorption_spec.rb +1 -0
  70. data/spec/cql/comparison_spec.rb +8 -8
  71. data/spec/cql/contractions_spec.rb +16 -43
  72. data/spec/cql/entity_type_spec.rb +2 -1
  73. data/spec/cql/expressions_spec.rb +2 -2
  74. data/spec/cql/fact_type_matching_spec.rb +4 -1
  75. data/spec/cql/parser/bad_literals_spec.rb +30 -30
  76. data/spec/cql/parser/entity_types_spec.rb +6 -6
  77. data/spec/cql/parser/expressions_spec.rb +25 -19
  78. data/spec/cql/samples_spec.rb +5 -4
  79. data/spec/cql_cql_spec.rb +2 -1
  80. data/spec/cql_dm_spec.rb +4 -0
  81. data/spec/cql_mysql_spec.rb +4 -0
  82. data/spec/cql_parse_spec.rb +2 -0
  83. data/spec/cql_ruby_spec.rb +4 -0
  84. data/spec/cql_sql_spec.rb +4 -0
  85. data/spec/cqldump_spec.rb +7 -4
  86. data/spec/helpers/parse_to_ast_matcher.rb +7 -3
  87. data/spec/helpers/test_parser.rb +2 -0
  88. data/spec/norma_cql_spec.rb +5 -2
  89. data/spec/norma_ruby_spec.rb +4 -1
  90. data/spec/norma_ruby_sql_spec.rb +4 -1
  91. data/spec/norma_sql_spec.rb +4 -1
  92. data/spec/norma_tables_spec.rb +2 -2
  93. data/spec/ruby_api_spec.rb +1 -1
  94. data/spec/spec_helper.rb +2 -0
  95. data/spec/transform_surrogate_spec.rb +59 -0
  96. metadata +70 -60
  97. data/TODO +0 -308
  98. data/lib/activefacts/cql/compiler/join.rb +0 -162
  99. data/lib/activefacts/generate/oo.rb +0 -176
  100. data/lib/activefacts/generate/ordered.rb +0 -602
@@ -54,13 +54,13 @@ module ActiveFacts
54
54
  end
55
55
 
56
56
  # An array of the names of the columns this index covers
57
- def column_names(joiner = "")
58
- columns.map{|column| column.name(joiner)}
57
+ def column_names(separator = "")
58
+ columns.map{|column| column.name(separator)}
59
59
  end
60
60
 
61
61
  # An array of the names of the columns this index covers, with some lexical truncations.
62
- def abbreviated_column_names(joiner = "")
63
- columns.map{|column| column.name(joiner).sub(/^#{over.name}/,'')}
62
+ def abbreviated_column_names(separator = "")
63
+ columns.map{|column| column.name(separator).sub(/^#{over.name}/,'')}
64
64
  end
65
65
 
66
66
  # The name of a view that can be created to enforce uniqueness over non-null key values
@@ -69,18 +69,43 @@ module ActiveFacts
69
69
  end
70
70
 
71
71
  def to_s #:nodoc:
72
- name = @uniqueness_constraint.name
72
+ if @uniqueness_constraint
73
+ name = @uniqueness_constraint.name
74
+ preferred = @uniqueness_constraint.is_preferred_identifier ? " (preferred)" : ""
75
+ else
76
+ name = "#{@on.name}IsUnique"
77
+ preferred = !@on.injected_surrogate_role ? " (preferred)" : ""
78
+ end
73
79
  colnames = @columns.map(&:name)*", "
74
- preferred = @uniqueness_constraint.is_preferred_identifier ? " (preferred)" : ""
75
80
  "Index #{name} on #{@on.name} over #{@over.name}(#{colnames})#{preferred}"
76
81
  end
77
82
  end
78
83
  end
79
84
 
80
85
  module Metamodel #:nodoc:
86
+ class EntityType
87
+ def self_index
88
+ nil
89
+ end
90
+ end
91
+
92
+ class ValueType
93
+ def self_index
94
+ ActiveFacts::Persistence::Index.new(
95
+ nil, # The implied uniqueness constraint is not created
96
+ self, # ValueType being indexed
97
+ self, # Absorbed object being indexed
98
+ columns.select{|c| c.references[0].is_self_value},
99
+ injected_surrogate_role ? false : true
100
+ )
101
+ end
102
+ end
103
+
81
104
  class ObjectType
82
105
  # An array of each Index for this table
83
- def indices; @indices; end
106
+ def indices
107
+ @indices || populate_indices
108
+ end
84
109
 
85
110
  def clear_indices #:nodoc:
86
111
  # Clear any previous indices
@@ -100,7 +125,7 @@ module ActiveFacts
100
125
  # order of the columns, not the same as the columns in the PK for which they might be an FK.
101
126
  all_column_by_ref_path =
102
127
  debug :index2, "Indexing columns by ref_path" do
103
- @columns.inject({}) do |hash, column|
128
+ columns.inject({}) do |hash, column|
104
129
  debug :index2, "References in column #{name}.#{column.name}" do
105
130
  ref_path = column.absorption_references
106
131
  raise "No absorption_references for #{column.name} from #{column.references.map(&:to_s)*" and "}" if !ref_path || ref_path.empty?
@@ -118,10 +143,10 @@ module ActiveFacts
118
143
  inject({}) do |hash, ref_path|
119
144
  ref_path.each do |ref|
120
145
  next unless ref.to_role
121
- #debug :index2, "Considering #{ref_path.map(&:to_s)*" and "} yielding columns #{all_column_by_ref_path[ref_path].map{|c| c.name(".")}*", "}"
146
+ # debug :index2, "Considering #{ref_path.map(&:to_s)*" and "} yielding columns #{all_column_by_ref_path[ref_path].map{|c| c.name('.')}*", "}"
122
147
  ref.to_role.all_role_ref.each do |role_ref|
123
148
  all_pcs = role_ref.role_sequence.all_presence_constraint
124
- #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
149
+ # 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
125
150
  pcs = all_pcs.
126
151
  reject do |pc|
127
152
  !pc.max_frequency or # No maximum freq; cannot be a uniqueness constraint
@@ -148,16 +173,18 @@ module ActiveFacts
148
173
 
149
174
  debug :index, "All Indices in #{name}:" do
150
175
  @indices = columns_by_unique_constraint.map do |uc, columns_with_ordinal|
151
- #puts "Index on #{name} over (#{columns_with_ordinal.sort.map{|ca| [ca[0], ca[1], ca[2].name].inspect}})"
176
+ debug :index, "Index due to uc #{uc.guid} on #{name} over (#{columns_with_ordinal.sort_by{|onc|onc[0]}.map{|ca| ca[2].name}.inspect})"
152
177
  columns = columns_with_ordinal.sort_by{|ca| [ca[0,2], ca[2].name]}.map{|ca| ca[2]}
153
178
  absorption_level = columns.map(&:absorption_level).min
154
179
  over = columns[0].references[absorption_level].from
155
180
 
156
181
  # Absorption through a one-to-one forms a UC that we don't need to enforce using an index:
157
- next nil if over != self and
182
+ if over != self and
158
183
  over.absorbed_via == columns[0].references[absorption_level-1] and
159
184
  (rr = uc.role_sequence.all_role_ref.single) and
160
185
  over.absorbed_via.fact_type.all_role.include?(rr.role)
186
+ next nil
187
+ end
161
188
 
162
189
  index = ActiveFacts::Persistence::Index.new(
163
190
  uc,
@@ -175,6 +202,9 @@ module ActiveFacts
175
202
  index.columns.map(&:name)+['', index.over.name]
176
203
  end
177
204
  end
205
+ si = self_index
206
+ @indices.unshift(si) if si
207
+ @indices
178
208
  end
179
209
 
180
210
  end
@@ -39,9 +39,11 @@ module ActiveFacts
39
39
  attr_reader :from, :to # A "from" instance is related to one "to" instance
40
40
  attr_reader :from_role, :to_role # For objectified facts, one role will be nil (a phantom)
41
41
  attr_reader :fact_type
42
+ attr_accessor :fk_jump # True if this reference links a table to another in an FK (between absorbed references)
42
43
 
43
44
  # A Reference is created from a object_type in regard to a role it plays
44
45
  def initialize(from, role)
46
+ @fk_jump = false
45
47
  @from = from
46
48
  return unless role # All done if it's a self-value reference for a ValueType
47
49
  @fact_type = role.fact_type
@@ -73,7 +75,6 @@ module ActiveFacts
73
75
  # Is this Reference covered by a mandatory constraint (implicitly or explicitly)
74
76
  def is_mandatory
75
77
  !@from_role || # All phantom roles of fact types are mandatory
76
- is_unary || # Unary fact types become booleans, which must be true or false
77
78
  @from_role.is_mandatory
78
79
  end
79
80
 
@@ -111,10 +112,10 @@ module ActiveFacts
111
112
  end
112
113
 
113
114
  # Return the array of names for the (perhaps implicit) *to_role* of this Reference
114
- def to_names
115
+ def to_names(is_prefix = true)
115
116
  case
116
117
  when is_unary
117
- if @to && @to.fact_type
118
+ if @to && @to.fact_type && is_prefix
118
119
  @to.name.camelwords
119
120
  else
120
121
  @to_role.fact_type.preferred_reading.text.gsub(/\{[0-9]\}/,'').strip.camelwords
@@ -134,14 +135,14 @@ module ActiveFacts
134
135
  # Return the array of names for the (perhaps implicit) *from_role* of this Reference
135
136
  def from_names
136
137
  case
138
+ when @from && !@from_role # @from is an objectified fact type so @from_role is a phantom
139
+ @from.name.camelwords
137
140
  when is_unary
138
141
  if @from && @from.fact_type
139
142
  @from.name.camelwords
140
143
  else
141
144
  @from_role.fact_type.preferred_reading.text.gsub(/\{[0-9]\}/,'').strip.camelwords
142
145
  end
143
- when @from && !@from_role # @from is an objectified fact type so @from_role is a phantom
144
- @from.name.camelwords
145
146
  when !@from_role # Self-value role of an independent ValueType
146
147
  @from.name.camelwords + ["Value"]
147
148
  when @from_role.role_name # Named role
@@ -152,12 +153,21 @@ module ActiveFacts
152
153
  end
153
154
  end
154
155
 
156
+ def is_one_to_one
157
+ [:one_one, :subtype, :supertype].include?(role_type)
158
+ end
159
+
155
160
  # For a one-to-one (or a subtyping fact type), reverse the direction.
156
161
  def flip #:nodoc:
157
- raise "Illegal flip of #{self}" unless @to and [:one_one, :subtype, :supertype].include?(role_type)
162
+ raise "Illegal flip of #{self}" unless @to and is_one_to_one
158
163
 
159
164
  detabulate
165
+ mirror
166
+ tabulate
167
+ end
160
168
 
169
+ # Create a (non-tabulated) flipped version of this Reference. Careful not to tabulate it!
170
+ def mirror
161
171
  if @to.absorbed_via == self
162
172
  @to.absorbed_via = nil
163
173
  @from.absorbed_via = self
@@ -166,8 +176,11 @@ module ActiveFacts
166
176
  # Flip the reference
167
177
  @to, @from = @from, @to
168
178
  @to_role, @from_role = @from_role, @to_role
179
+ self
180
+ end
169
181
 
170
- tabulate
182
+ def reversed
183
+ clone.mirror
171
184
  end
172
185
 
173
186
  def tabulate #:nodoc:
@@ -188,7 +201,8 @@ module ActiveFacts
188
201
  end
189
202
 
190
203
  def to_s #:nodoc:
191
- "reference from #{@from.name}#{@to ? " to #{@to.name}" : ""}" + (@fact_type ? " in '#{@fact_type.default_reading}'" : "")
204
+ ref_type = fk_jump ? "jumping to" : (is_absorbing ? "absorbing" : "to")
205
+ "reference from #{@from.name}#{@to ? " #{ref_type} #{@to.name}" : ""}" + (@fact_type ? " in '#{@fact_type.default_reading}'" : "")
192
206
  end
193
207
 
194
208
  # The reading for the fact type underlying this Reference
@@ -258,8 +272,8 @@ module ActiveFacts
258
272
  def populate_references #:nodoc:
259
273
  all_role.each do |role|
260
274
  # It's possible that this role is in an implicit or derived fact type. Skip it if so.
261
- next if role.fact_type.is_a?(ImplicitFactType) or
262
- role.fact_type.preferred_reading.role_sequence.all_role_ref.to_a[0].join_role
275
+ next if role.fact_type.is_a?(LinkFactType) or
276
+ role.fact_type.preferred_reading.role_sequence.all_role_ref.to_a[0].play
263
277
 
264
278
  populate_reference role
265
279
  end
@@ -296,7 +310,6 @@ module ActiveFacts
296
310
  end
297
311
 
298
312
  when :subtype # This object is a supertype, which can absorb the subtype unless that's independent
299
- raise hell unless role.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance)
300
313
  if role.fact_type.assimilation
301
314
  ActiveFacts::Persistence::Reference.new(self, role).tabulate
302
315
  # If partitioned, the supertype is absorbed into *each* subtype; a reference to the supertype needs to know which
@@ -351,7 +364,7 @@ module ActiveFacts
351
364
  end
352
365
  end
353
366
 
354
- class EntityType < ObjectType
367
+ class EntityType < DomainObjectType
355
368
  def populate_references #:nodoc:
356
369
  if fact_type && fact_type.all_role.size > 1
357
370
  # NOT: fact_type.all_role.each do |role| # Place roles in the preferred order instead:
@@ -366,23 +379,24 @@ module ActiveFacts
366
379
  class Vocabulary
367
380
  def populate_all_references #:nodoc:
368
381
  debug :references, "Populating all object_type references" do
369
- all_object_type.each do |object_type|
370
- object_type.clear_references
371
- object_type.is_table = nil # Undecided; force an attempt to decide
372
- object_type.tentative = true # Uncertain
373
- end
374
382
  all_object_type.each do |object_type|
375
383
  debug :references, "Populating references for #{object_type.name}" do
376
384
  object_type.populate_references
377
385
  end
378
386
  end
379
387
  end
380
- debug :references, "Finished object_type references" do
381
- all_object_type.each do |object_type|
382
- next unless object_type.references_from.size > 0
383
- debug :references, "#{object_type.name}:" do
384
- object_type.references_from.each do |ref|
385
- debug :references, "#{ref}"
388
+ show_all_references
389
+ end
390
+
391
+ def show_all_references
392
+ if debug :references
393
+ debug :references, "Finished object_type references" do
394
+ all_object_type.each do |object_type|
395
+ next unless object_type.references_from.size > 0
396
+ debug :references, "#{object_type.name}:" do
397
+ object_type.references_from.each do |ref|
398
+ debug :references, "#{ref}"
399
+ end
386
400
  end
387
401
  end
388
402
  end
@@ -17,7 +17,7 @@ require 'activefacts/persistence/reference'
17
17
  module ActiveFacts
18
18
  module Metamodel
19
19
 
20
- class ValueType < ObjectType
20
+ class ValueType < DomainObjectType
21
21
  def absorbed_via #:nodoc:
22
22
  # ValueTypes aren't absorbed in the way EntityTypes are
23
23
  nil
@@ -35,7 +35,7 @@ module ActiveFacts
35
35
  end
36
36
 
37
37
  # Only a table if it has references (to another ValueType)
38
- if !references_from.empty?
38
+ if !references_from.empty? && !is_auto_assigned
39
39
  debug :absorption, "#{name} is a table because it has #{references_from.size} references to it"
40
40
  @is_table = true
41
41
  else
@@ -55,7 +55,7 @@ module ActiveFacts
55
55
  end
56
56
  end
57
57
 
58
- class EntityType < ObjectType
58
+ class EntityType < DomainObjectType
59
59
  # A Reference from an entity type that fully absorbs this one
60
60
  def absorbed_via; @absorbed_via; end
61
61
  def absorbed_via=(r) #:nodoc:
@@ -85,12 +85,25 @@ module ActiveFacts
85
85
  return @is_table = true
86
86
  end
87
87
 
88
- # Subtypes are not a table unless partitioned or separate
89
- # REVISIT: Support partitioned subtypes here
88
+ # Subtypes may be partitioned or separate, in which case they're definitely tables.
89
+ # Otherwise, if their identification is inherited from a supertype, they're definitely absorbed.
90
+ # If theey have separate identification, it might absorb them.
90
91
  if (!supertypes.empty?)
91
92
  as_ti = all_supertype_inheritance.detect{|ti| ti.assimilation}
92
- debug :absorption, "EntityType #{name} is #{as_ti ? as_ti.assimilation+' from' : 'absorbed into'} supertype #{(as_ti ? as_ti.supertype : supertypes[0]).name}"
93
- return @is_table = all_supertype_inheritance.detect{|ti| ti.assimilation} != nil
93
+ @is_table = as_ti != nil
94
+ if @is_table
95
+ debug :absorption, "EntityType #{name} is #{as_ti.assimilation} from supertype #{as_ti.supertype}"
96
+ else
97
+ identifying_fact_type = preferred_identifier.role_sequence.all_role_ref.to_a[0].role.fact_type
98
+ if identifying_fact_type.is_a?(TypeInheritance)
99
+ debug :absorption, "EntityType #{name} is absorbed into supertype #{supertypes[0].name}"
100
+ @is_table = false
101
+ else
102
+ # Possibly absorbed, we'll have to see how that pans out
103
+ @tentative = true
104
+ end
105
+ end
106
+ return @is_table
94
107
  end
95
108
 
96
109
  # If the preferred_identifier includes an auto_assigned ValueType
@@ -168,12 +181,20 @@ module ActiveFacts
168
181
  end
169
182
 
170
183
  def self.relational_transform &block
171
- # Add this block to the additional transformations which wil be applied
184
+ # Add this block to the additional transformations which will be applied
172
185
  # to the relational schema after the initial absorption.
173
186
  # For example, to perform injection of surrogate keys to replace composite keys...
174
187
  @@relational_transforms << block
175
188
  end
176
189
 
190
+ def wipe_existing_mapping
191
+ all_object_type.each do |object_type|
192
+ object_type.clear_references
193
+ object_type.is_table = nil # Undecided; force an attempt to decide
194
+ object_type.tentative = true # Uncertain
195
+ end
196
+ end
197
+
177
198
  def decide_tables #:nodoc:
178
199
  # Strategy:
179
200
  # 1) Populate references for all ObjectTypes
@@ -181,7 +202,7 @@ module ActiveFacts
181
202
  # a. ObjectTypes labelled is_independent are tables (See the is_table methods above)
182
203
  # b. Entity types having no references to them must be tables
183
204
  # c. subtypes are not tables unless marked with assimilation = separate or partitioned
184
- # d. ValueTypes are never tables unless they can have references (to other ValueTypes)
205
+ # d. ValueTypes are never tables unless they independent or can have references (to other ValueTypes)
185
206
  # e. An EntityType having an identifying AutoInc field must be a table unless it has exactly one reference
186
207
  # f. An EntityType whose only reference is through its single preferred_identifier role gets absorbed
187
208
  # g. An EntityType that must has references other than its PI must be a table (unless it has exactly one reference to it)
@@ -190,6 +211,8 @@ module ActiveFacts
190
211
  # - subtype extension where supertype has only PI roles and no AutoInc
191
212
  # 3) any ValueType that has references from it must become a table if not already
192
213
 
214
+ wipe_existing_mapping
215
+
193
216
  populate_all_references
194
217
 
195
218
  debug :absorption, "Calculating relational composition" do
@@ -218,7 +241,7 @@ module ActiveFacts
218
241
  undecided.select do |object_type|
219
242
  debug :absorption, "Considering #{object_type.name}:" do
220
243
  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
221
- debug :absorption, "refs from #{object_type.name} are to #{object_type.references_from.map{|ref| ref.to.name rescue ref.fact_type.default_reading}*", "}" if object_type.references_from.size > 0
244
+ 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
222
245
 
223
246
  # Always absorb an objectified unary into its role player:
224
247
  if object_type.fact_type && object_type.fact_type.all_role.size == 1
@@ -247,11 +270,21 @@ module ActiveFacts
247
270
  }
248
271
  debug :absorption, "#{object_type.name} has #{non_identifying_refs_from.size} non-identifying functional roles"
249
272
 
273
+ =begin
274
+ # This is kinda arbitrary. We need a policy for evaluating optional flips, so we can decide if they "improve" things.
275
+ # The flipping that occurs below always eliminates a table by absorption, but this doesn't.
276
+
250
277
  # If all non-identifying functional roles are one-to-ones that can be flipped, do that:
251
278
  if non_identifying_refs_from.all? { |ref| ref.role_type == :one_one && (ref.to.is_table || ref.to.tentative) }
252
- non_identifying_refs_from.each { |ref| ref.flip }
279
+ debug :absorption, "Flipping references from #{object_type.name}" do
280
+ non_identifying_refs_from.each do |ref|
281
+ debug :absorption, "Flipping #{ref}"
282
+ ref.flip
283
+ end
284
+ end
253
285
  non_identifying_refs_from = []
254
286
  end
287
+ =end
255
288
 
256
289
  if object_type.references_to.size > 1 and
257
290
  non_identifying_refs_from.size > 0
@@ -266,8 +299,7 @@ module ActiveFacts
266
299
  !ref.to or ref.to.absorbed_via == ref
267
300
  end+object_type.references_to
268
301
  ).reject do |ref|
269
- next true if !ref.to.is_table or
270
- ![:one_one, :supertype, :subtype].include?(ref.role_type)
302
+ next true if !ref.to.is_table or !ref.is_one_to_one
271
303
 
272
304
  # Don't absorb an object along a non-mandatory role (otherwise if it doesn't play that role, it can't exist either)
273
305
  from_is_mandatory = !!ref.is_mandatory
@@ -282,7 +314,7 @@ module ActiveFacts
282
314
  if absorption_paths.size > 0
283
315
  debug :absorption, "#{object_type.name} is fully absorbed through #{absorption_paths.inspect}"
284
316
  absorption_paths.each do |ref|
285
- debug :absorption, "flip #{ref} so #{object_type.name} can be absorbed"
317
+ debug :absorption, "Flipping #{ref} so #{object_type.name} can be absorbed"
286
318
  ref.flip if object_type == ref.from
287
319
  end
288
320
  object_type.definitely_not_table
@@ -310,8 +342,13 @@ module ActiveFacts
310
342
  # unless it should absorb something else (another ValueType is all it could be):
311
343
  all_object_type.each do |object_type|
312
344
  if (!object_type.is_table and object_type.references_to.size == 0 and object_type.references_from.size > 0)
313
- debug :absorption, "Making #{object_type.name} a table; it has nowhere else to go and needs to absorb things"
314
- object_type.probably_table
345
+ if !object_type.references_from.detect{|r| !r.is_one_to_one || !r.to.is_table}
346
+ debug :absorption, "Flipping references from #{object_type.name}; they're all to tables"
347
+ object_type.references_from.map(&:flip)
348
+ else
349
+ debug :absorption, "Making #{object_type.name} a table; it has nowhere else to go and needs to absorb things"
350
+ object_type.probably_table
351
+ end
315
352
  end
316
353
  end
317
354
 
@@ -324,9 +361,6 @@ module ActiveFacts
324
361
  end
325
362
  end
326
363
 
327
- populate_all_columns
328
- populate_all_indices
329
-
330
364
  @tables =
331
365
  all_object_type.
332
366
  select { |f| f.is_table }.
@@ -0,0 +1,11 @@
1
+ module ActiveFacts
2
+ module Registry
3
+ def self.generator(name, klass)
4
+ generators[name] = klass
5
+ end
6
+
7
+ def self.generators
8
+ @@generators ||= {}
9
+ end
10
+ end
11
+ end