activefacts 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +5 -13
  2. data/Rakefile +2 -2
  3. data/bin/afgen +1 -1
  4. data/bin/cql +118 -27
  5. data/examples/CQL/Insurance.cql +2 -2
  6. data/examples/CQL/Metamodel.cql +3 -3
  7. data/examples/CQL/SchoolActivities.cql +1 -1
  8. data/examples/CQL/Warehousing.cql +5 -4
  9. data/lib/activefacts/cql.rb +1 -1
  10. data/lib/activefacts/cql/Language/English.treetop +2 -1
  11. data/lib/activefacts/cql/compiler.rb +6 -6
  12. data/lib/activefacts/cql/compiler/clause.rb +69 -46
  13. data/lib/activefacts/cql/compiler/constraint.rb +14 -14
  14. data/lib/activefacts/cql/compiler/entity_type.rb +24 -24
  15. data/lib/activefacts/cql/compiler/fact.rb +40 -27
  16. data/lib/activefacts/cql/compiler/fact_type.rb +16 -16
  17. data/lib/activefacts/cql/compiler/query.rb +12 -12
  18. data/lib/activefacts/cql/compiler/shared.rb +9 -0
  19. data/lib/activefacts/cql/compiler/value_type.rb +4 -4
  20. data/lib/activefacts/cql/parser.rb +9 -9
  21. data/lib/activefacts/generate/cql.rb +41 -20
  22. data/lib/activefacts/generate/helpers/oo.rb +33 -70
  23. data/lib/activefacts/generate/helpers/ordered.rb +61 -87
  24. data/lib/activefacts/generate/ruby.rb +12 -72
  25. data/lib/activefacts/generate/transform/surrogate.rb +13 -13
  26. data/lib/activefacts/input/orm.rb +72 -71
  27. data/lib/activefacts/persistence/columns.rb +66 -31
  28. data/lib/activefacts/persistence/foreignkey.rb +6 -6
  29. data/lib/activefacts/persistence/index.rb +12 -12
  30. data/lib/activefacts/persistence/object_type.rb +15 -12
  31. data/lib/activefacts/persistence/reference.rb +20 -18
  32. data/lib/activefacts/persistence/tables.rb +40 -36
  33. data/lib/activefacts/support.rb +69 -123
  34. data/lib/activefacts/version.rb +2 -2
  35. data/lib/activefacts/vocabulary/extensions.rb +42 -39
  36. data/lib/activefacts/vocabulary/metamodel.rb +11 -1
  37. data/lib/activefacts/vocabulary/verbaliser.rb +28 -28
  38. data/spec/cql/contractions_spec.rb +1 -1
  39. data/spec/cql/entity_type_spec.rb +1 -1
  40. data/spec/cql/fact_type_matching_spec.rb +3 -3
  41. data/spec/cql/role_matching_spec.rb +4 -4
  42. data/spec/cql/samples_spec.rb +2 -2
  43. data/spec/cql_cql_spec.rb +1 -1
  44. data/spec/helpers/array_matcher.rb +1 -1
  45. data/spec/norma_ruby_sql_spec.rb +2 -2
  46. data/spec/norma_tables_spec.rb +3 -2
  47. metadata +47 -68
@@ -78,7 +78,7 @@ module ActiveFacts
78
78
  ref.to.is_a?(ActiveFacts::Metamodel::EntityType) and
79
79
  (role_ref = ref.to.preferred_identifier.role_sequence.all_role_ref.single) and
80
80
  role_ref.role == ref.from_role
81
- debug :columns, "Skipping #{ref}, identifies non-initial object"
81
+ trace :columns, "Skipping #{ref}, identifies non-initial object"
82
82
  next a
83
83
  end
84
84
 
@@ -87,19 +87,19 @@ module ActiveFacts
87
87
  # When traversing type inheritances, keep the subtype name, not the supertype names as well:
88
88
  if a.size > 0 && ref.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance)
89
89
  if ref.to != ref.fact_type.subtype # Did we already have the subtype?
90
- debug :columns, "Skipping supertype #{ref}"
90
+ trace :columns, "Skipping supertype #{ref}"
91
91
  next a
92
92
  end
93
- debug :columns, "Eliding supertype in #{ref}"
93
+ trace :columns, "Eliding supertype in #{ref}"
94
94
  last_names.size.times { a.pop } # Remove the last names added
95
95
  elsif last_names.last && last_names.last == names[0][0...last_names.last.size]
96
96
  # When Xyz is followed by XyzID, truncate that to just ID
97
- debug :columns, "truncating repeated #{last_names.last} in #{names[0]}"
97
+ trace :columns, "truncating repeated #{last_names.last} in #{names[0]}"
98
98
  names[0] = names[0][last_names.last.size..-1]
99
99
  names.shift if names[0] == ''
100
100
  elsif last_names.last == names[0]
101
101
  # Same, but where an underscore split up the words
102
- debug :columns, "truncating repeated name in #{names.inspect}"
102
+ trace :columns, "truncating repeated name in #{names.inspect}"
103
103
  names.shift
104
104
  end
105
105
 
@@ -113,7 +113,7 @@ module ActiveFacts
113
113
  role_ref.role == ref.to_role and
114
114
  names[0][0...et.name.size].downcase == et.name.downcase
115
115
 
116
- debug :columns, "truncating transitive identifying role #{names.inspect}"
116
+ trace :columns, "truncating transitive identifying role #{names.inspect}"
117
117
  names[0] = names[0][et.name.size..-1]
118
118
  names.shift if names[0] == ""
119
119
  end
@@ -177,10 +177,18 @@ module ActiveFacts
177
177
  # The comment is the readings from the References expressed as a series of steps (not a full verbalisation)
178
178
  def comment
179
179
  @references.map do |ref|
180
- (ref.is_mandatory ? "" : "maybe ") +
181
- (ref.fact_type && ref.fact_type.entity_type ? ref.fact_type.entity_type.name+" is where " : "") +
182
- ref.reading
183
- end * " and "
180
+ # REVISIT: Some contraction would be nice here
181
+ objectification = ref.fact_type && ref.fact_type.entity_type
182
+ involves = ''
183
+ if objectification && ref == @references.last
184
+ objectification = nil if ref.fact_type.all_role.size == 1 # Disregard the objectification of a trailing unary
185
+ involves = ref.to_role.link_fact_type.default_reading[ref.fact_type.entity_type.name.size..-1]
186
+ end
187
+ (objectification ? objectification.name + ' (in which ' : '') +
188
+ (ref.is_mandatory || ref.is_unary ? '' : 'maybe ') +
189
+ ref.reading +
190
+ (objectification ? ')'+involves : '')
191
+ end.compact * " and "
184
192
  end
185
193
 
186
194
  def to_s #:nodoc:
@@ -194,8 +202,34 @@ module ActiveFacts
194
202
  cols =
195
203
  if is_unary
196
204
  kind = "unary "
197
- [Column.new()] +
198
- ((@to && @to.fact_type) ? @to.all_columns(excluded_supertypes) : [])
205
+ objectified_unary_columns =
206
+ ((@to && @to.fact_type) ? @to.all_columns(excluded_supertypes) : [])
207
+
208
+ =begin
209
+ # This code omits the unary if it's objectified and that plays a mandatory role
210
+ first_mandatory_column = nil
211
+ if (@to && @to.fact_type)
212
+ trace :unary_col, "Deciding whether to skip unary column for #{inspect}" do
213
+ first_mandatory_column =
214
+ objectified_unary_columns.detect do |col| # Detect a mandatory column for the unary
215
+ trace :unary_col, "checking column #{col.name}" do
216
+ !col.references.detect do |ref|
217
+ trace :unary_col, "#{ref} is mandatory=#{ref.is_mandatory.inspect}"
218
+ !ref.is_mandatory
219
+ end
220
+ end
221
+ end
222
+ if is_from_objectified_fact && first_mandatory_column
223
+ trace :unary_col, "Skipping unary column for #{inspect} because #{first_mandatory_column.name} is mandatory"
224
+ end
225
+ end
226
+ end
227
+
228
+ (is_from_objectified_fact && first_mandatory_column ? [] : [Column.new()]) + # The unary itself, unless its objectified
229
+ =end
230
+
231
+ [Column.new()] + # The unary itself
232
+ objectified_unary_columns
199
233
  elsif is_self_value
200
234
  kind = "self-role "
201
235
  [Column.new()]
@@ -210,9 +244,9 @@ module ActiveFacts
210
244
  c.prepend self
211
245
  end
212
246
 
213
- debug :columns, "Columns from #{kind}#{self}" do
247
+ trace :columns, "Columns from #{kind}#{self}" do
214
248
  cols.each {|c|
215
- debug :columns, "#{c}"
249
+ trace :columns, "#{c}"
216
250
  }
217
251
  end
218
252
  end
@@ -239,7 +273,7 @@ module ActiveFacts
239
273
  class ValueType < DomainObjectType
240
274
  # The identifier_columns for a ValueType can only ever be the self-value role that was injected
241
275
  def identifier_columns
242
- debug :columns, "Identifier Columns for #{name}" do
276
+ trace :columns, "Identifier Columns for #{name}" do
243
277
  raise "Illegal call to identifier_columns for absorbed ValueType #{name}" unless is_table
244
278
  if isr = injected_surrogate_role
245
279
  columns.select{|column| column.references[0].from_role == isr }
@@ -252,7 +286,7 @@ module ActiveFacts
252
286
  # When creating a foreign key to this ValueType, what columns must we include?
253
287
  # This must be a fresh copy, because the columns will have References prepended
254
288
  def reference_columns(excluded_supertypes) #:nodoc:
255
- debug :columns, "Reference Columns for #{name}" do
289
+ trace :columns, "Reference Columns for #{name}" do
256
290
  if is_table
257
291
  if isr = injected_surrogate_role
258
292
  ref_from = references_from.detect{|ref| ref.from_role == isr}
@@ -270,14 +304,14 @@ module ActiveFacts
270
304
  # This must be a fresh copy, because the columns will have References prepended.
271
305
  def all_columns(excluded_supertypes) #:nodoc:
272
306
  columns = []
273
- debug :columns, "All Columns for #{name}" do
307
+ trace :columns, "All Columns for #{name}" do
274
308
  if is_table
275
309
  self_value_reference
276
310
  else
277
311
  columns << ActiveFacts::Persistence::Column.new
278
312
  end
279
313
  references_from.each do |ref|
280
- debug :columns, "Columns absorbed via #{ref}" do
314
+ trace :columns, "Columns absorbed via #{ref}" do
281
315
  columns += ref.columns({})
282
316
  end
283
317
  end
@@ -297,7 +331,7 @@ module ActiveFacts
297
331
  class EntityType < DomainObjectType
298
332
  # The identifier_columns for an EntityType are the columns that result from the identifying roles
299
333
  def identifier_columns
300
- debug :columns, "Identifier Columns for #{name}" do
334
+ trace :columns, "Identifier Columns for #{name}" do
301
335
  if absorbed_via and
302
336
  # If this is a subtype that has its own identification, use that.
303
337
  (all_type_inheritance_as_subtype.size == 0 ||
@@ -316,16 +350,17 @@ module ActiveFacts
316
350
  # When creating a foreign key to this EntityType, what columns must we include (the identifier columns)?
317
351
  # This must be a fresh copy, because the columns will have References prepended
318
352
  def reference_columns(excluded_supertypes) #:nodoc:
319
- debug :columns, "Reference Columns for #{name}" do
353
+ trace :columns, "Reference Columns for #{name}" do
320
354
 
321
355
  if absorbed_via and
322
356
  # If this is not a subtype, or is a subtype that has its own identification, use the id.
323
357
  (all_type_inheritance_as_subtype.size == 0 ||
324
358
  all_type_inheritance_as_subtype.detect{|ti| ti.provides_identification })
325
359
  rc = absorbed_via.from.reference_columns(excluded_supertypes)
326
- # The absorbed_via reference gets skipped here, ans also in object_type.rb
327
- debug :columns, "Skipping #{absorbed_via}"
328
- #rc.each{|col| col.prepend(absorbed_via)}
360
+ # The absorbed_via reference gets skipped here, and also in object_type.rb
361
+ trace :columns, "Skipping #{absorbed_via}"
362
+ absorbed_mirror ||= absorbed_via.reversed
363
+ rc.each{|col| col.prepend(absorbed_mirror)}
329
364
  return rc
330
365
  end
331
366
 
@@ -344,7 +379,7 @@ module ActiveFacts
344
379
  # When absorbing this EntityType, what columns must be absorbed?
345
380
  # This must be a fresh copy, because the columns will have References prepended.
346
381
  def all_columns(excluded_supertypes) #:nodoc:
347
- debug :columns, "All Columns for #{name}" do
382
+ trace :columns, "All Columns for #{name}" do
348
383
  columns = []
349
384
  sups = supertypes
350
385
  pi_roles = preferred_identifier.role_sequence.all_role_ref.map{|rr| rr.role}
@@ -357,10 +392,10 @@ module ActiveFacts
357
392
  end
358
393
  [3, ref.to_names]
359
394
  end.each do |ref|
360
- debug :columns, "Columns absorbed via #{ref}" do
395
+ trace :columns, "Columns absorbed via #{ref}" do
361
396
  if (ref.role_type == :supertype)
362
397
  if excluded_supertypes[ref.to]
363
- debug :columns, "Exclude #{ref.to.name}, we already inherited it"
398
+ trace :columns, "Exclude #{ref.to.name}, we already inherited it"
364
399
  next
365
400
  end
366
401
 
@@ -392,20 +427,20 @@ module ActiveFacts
392
427
  # REVISIT: Is now a good time to apply schema transforms or should this be more explicit?
393
428
  finish_schema
394
429
 
395
- debug :columns, "Populating all columns" do
430
+ trace :columns, "Populating all columns" do
396
431
  all_object_type.each do |object_type|
397
432
  next if !object_type.is_table
398
- debug :columns, "Populating columns for table #{object_type.name}" do
433
+ trace :columns, "Populating columns for table #{object_type.name}" do
399
434
  object_type.populate_columns
400
435
  end
401
436
  end
402
437
  end
403
- debug :columns, "Finished columns" do
438
+ trace :columns, "Finished columns" do
404
439
  all_object_type.each do |object_type|
405
440
  next if !object_type.is_table
406
- debug :columns, "Finished columns for table #{object_type.name}" do
441
+ trace :columns, "Finished columns for table #{object_type.name}" do
407
442
  object_type.columns.each do |column|
408
- debug :columns, "#{column}"
443
+ trace :columns, "#{column}"
409
444
  end
410
445
  end
411
446
  end
@@ -107,7 +107,7 @@ module ActiveFacts
107
107
  ref.fk_jump = true
108
108
  array << [ref]
109
109
  elsif ref.is_absorbing or (ref.to && !ref.to.is_table)
110
- debug :fk, "getting fks absorbed into #{name} via #{ref}" do
110
+ trace :fk, "getting fks absorbed into #{name} via #{ref}" do
111
111
  ref.to.all_absorbed_foreign_key_reference_path.each do |aref|
112
112
  array << aref.insert(0, ref)
113
113
  end
@@ -129,24 +129,24 @@ module ActiveFacts
129
129
  begin
130
130
  fk_ref_paths = all_absorbed_foreign_key_reference_path
131
131
  fk_ref_paths.map do |fk_ref_path|
132
- debug :fk, "\nFK: " + fk_ref_path.map{|fk_ref| fk_ref.reading }*" and " do
132
+ trace :fk, "\nFK: " + fk_ref_path.map{|fk_ref| fk_ref.reading }*" and " do
133
133
 
134
134
  from_columns = (columns||all_columns({})).select{|column|
135
135
  column.references[0...fk_ref_path.size] == fk_ref_path
136
136
  }
137
- debug :fk, "from_columns = #{from_columns.map { |column| column.name }*", "}"
137
+ trace :fk, "from_columns = #{from_columns.map { |column| column.name }*", "}"
138
138
 
139
139
  # Figure out absorption on the target end:
140
140
  to = fk_ref_path.last.to
141
141
  if to.absorbed_via
142
- debug :fk, "Reference target #{fk_ref_path.last.to.name} is absorbed via:" do
142
+ trace :fk, "Reference target #{fk_ref_path.last.to.name} is absorbed via:" do
143
143
  while (r = to.absorbed_via)
144
144
  m = r.reversed
145
- debug :fk, "#{m.reading}"
145
+ trace :fk, "#{m.reading}"
146
146
  fk_ref_path << m
147
147
  to = m.from == to ? m.to : m.from
148
148
  end
149
- debug :fk, "Absorption ends at #{to.name}"
149
+ trace :fk, "Absorption ends at #{to.name}"
150
150
  end
151
151
  end
152
152
 
@@ -124,13 +124,13 @@ module ActiveFacts
124
124
  # Note also that this produces columns ordered for each refpath the same as the
125
125
  # order of the columns, not the same as the columns in the PK for which they might be an FK.
126
126
  all_column_by_ref_path =
127
- debug :index2, "Indexing columns by ref_path" do
127
+ trace :index2, "Indexing columns by ref_path" do
128
128
  columns.inject({}) do |hash, column|
129
- debug :index2, "References in column #{name}.#{column.name}" do
129
+ trace :index2, "References in column #{name}.#{column.name}" do
130
130
  ref_path = column.absorption_references
131
131
  raise "No absorption_references for #{column.name} from #{column.references.map(&:to_s)*" and "}" if !ref_path || ref_path.empty?
132
132
  (hash[ref_path] ||= []) << column
133
- debug :index2, "#{column.name} involves #{ref_path.map(&:to_s)*" and "}"
133
+ trace :index2, "#{column.name} involves #{ref_path.map(&:to_s)*" and "}"
134
134
  end
135
135
  hash
136
136
  end
@@ -143,7 +143,7 @@ module ActiveFacts
143
143
  inject({}) do |hash, ref_path|
144
144
  ref_path.each do |ref|
145
145
  next unless ref.to_role
146
- # debug :index2, "Considering #{ref_path.map(&:to_s)*" and "} yielding columns #{all_column_by_ref_path[ref_path].map{|c| c.name('.')}*", "}"
146
+ # trace :index2, "Considering #{ref_path.map(&:to_s)*" and "} yielding columns #{all_column_by_ref_path[ref_path].map{|c| c.name('.')}*", "}"
147
147
  ref.to_role.all_role_ref.each do |role_ref|
148
148
  all_pcs = role_ref.role_sequence.all_presence_constraint
149
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
@@ -171,9 +171,9 @@ module ActiveFacts
171
171
  hash
172
172
  end
173
173
 
174
- debug :index, "All Indices in #{name}:" do
174
+ trace :index, "All Indices in #{name}:" do
175
175
  @indices = columns_by_unique_constraint.map do |uc, columns_with_ordinal|
176
- 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})"
176
+ trace :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})"
177
177
  columns = columns_with_ordinal.sort_by{|ca| [ca[0,2], ca[2].name]}.map{|ca| ca[2]}
178
178
  absorption_level = columns.map(&:absorption_level).min
179
179
  over = columns[0].references[absorption_level].from
@@ -193,7 +193,7 @@ module ActiveFacts
193
193
  columns,
194
194
  uc.is_preferred_identifier
195
195
  )
196
- debug :index, index
196
+ trace :index, index
197
197
  index
198
198
  end.
199
199
  compact.
@@ -211,24 +211,24 @@ module ActiveFacts
211
211
 
212
212
  class Vocabulary
213
213
  def populate_all_indices #:nodoc:
214
- debug :index, "Populating all object_type indices" do
214
+ trace :index, "Populating all object_type indices" do
215
215
  all_object_type.each do |object_type|
216
216
  object_type.clear_indices
217
217
  end
218
218
  all_object_type.each do |object_type|
219
219
  next unless object_type.is_table
220
- debug :index, "Populating indices for #{object_type.name}" do
220
+ trace :index, "Populating indices for #{object_type.name}" do
221
221
  object_type.populate_indices
222
222
  end
223
223
  end
224
224
  end
225
- debug :index, "Finished object_type indices" do
225
+ trace :index, "Finished object_type indices" do
226
226
  all_object_type.each do |object_type|
227
227
  next unless object_type.is_table
228
228
  next unless object_type.indices.size > 0
229
- debug :index, "#{object_type.name}:" do
229
+ trace :index, "#{object_type.name}:" do
230
230
  object_type.indices.each do |index|
231
- debug :index, index
231
+ trace :index, index
232
232
  end
233
233
  end
234
234
  end
@@ -12,12 +12,14 @@ module ActiveFacts
12
12
  end
13
13
 
14
14
  def columns
15
+ raise "This method is no longer in use"
16
+ =begin
15
17
  return @columns if @columns
16
- debug :persistence, "Calculating columns for #{basename}" do
18
+ trace :persistence, "Calculating columns for #{basename}" do
17
19
  @columns = (
18
20
  if superclass.is_entity_type
19
21
  # REVISIT: Need keys to secondary supertypes as well, but no duplicates.
20
- debug :persistence, "Separate subtype has a foreign key to its supertype" do
22
+ trace :persistence, "Separate subtype has a foreign key to its supertype" do
21
23
  superclass.__absorb([[superclass.basename]], self)
22
24
  end
23
25
  else
@@ -28,7 +30,7 @@ module ActiveFacts
28
30
  role.unique && !role.counterpart_unary_has_precedence
29
31
  end.inject([]) do |columns, role|
30
32
  rn = role.name.to_s.split(/_/)
31
- debug :persistence, "Role #{rn*'.'}" do
33
+ trace :persistence, "Role #{rn*'.'}" do
32
34
  columns += role.counterpart_object_type.__absorb([rn], role.counterpart)
33
35
  end
34
36
  end +
@@ -38,7 +40,7 @@ module ActiveFacts
38
40
  inject([]) do |columns, subtype|
39
41
  # Pass self as 2nd param here, not a role, standing for the supertype role
40
42
  subtype_name = subtype.basename
41
- debug :persistence, "Absorbing subtype #{subtype_name}" do
43
+ trace :persistence, "Absorbing subtype #{subtype_name}" do
42
44
  columns += subtype.__absorb([[subtype_name]], self)
43
45
  end
44
46
  end
@@ -55,6 +57,7 @@ module ActiveFacts
55
57
  end*"."
56
58
  end
57
59
  end
60
+ =end
58
61
  end
59
62
 
60
63
  # Return an array of the absorbed columns, using prefix for name truncation
@@ -68,7 +71,7 @@ module ActiveFacts
68
71
  # counterpart_object_type = role.counterpart_object_type
69
72
  # This omission matches the one in columns.rb, see EntityType#reference_columns
70
73
  # new_prefix = prefix + [role.name.to_s.split(/_/)]
71
- debug :persistence, "Reference to #{role.name} (absorbed elsewhere)" do
74
+ trace :persistence, "Reference to #{role.name} (absorbed elsewhere)" do
72
75
  role.counterpart_object_type.__absorb(prefix, role.counterpart)
73
76
  end
74
77
  else
@@ -86,7 +89,7 @@ module ActiveFacts
86
89
  inject([]) do |columns, subtype|
87
90
  # Pass self as 2nd param here, not a role, standing for the supertype role
88
91
  new_prefix = prefix[0..-2] + [[subtype.basename]]
89
- debug :persistence, "Absorbed subtype #{subtype.basename}" do
92
+ trace :persistence, "Absorbed subtype #{subtype.basename}" do
90
93
  columns += subtype.__absorb(new_prefix, self)
91
94
  end
92
95
  end
@@ -98,7 +101,7 @@ module ActiveFacts
98
101
  # Create a foreign key to the table
99
102
  if is_entity_type
100
103
  ir = identifying_role_names.map{|role_name| roles(role_name) }
101
- debug :persistence, "Reference to #{basename} with #{prefix.inspect}" do
104
+ trace :persistence, "Reference to #{basename} with #{prefix.inspect}" do
102
105
  ic = identifying_role_names.map{|role_name| role_name.to_s.split(/_/)}
103
106
  ir.inject([]) do |columns, role|
104
107
  columns += __absorb_role(prefix, role)
@@ -107,7 +110,7 @@ module ActiveFacts
107
110
  else
108
111
  # Reference to value type which is a table
109
112
  col = prefix.clone
110
- debug :persistence, "Self-value #{col[-1]}.Value"
113
+ trace :persistence, "Self-value #{col[-1]}.Value"
111
114
  col[-1] += ["Value"]
112
115
  col
113
116
  end
@@ -122,23 +125,23 @@ module ActiveFacts
122
125
  (n = irn[0].to_s.split(/_/)).size > 1 and
123
126
  (owner = role.owner.basename.snakecase.split(/_/)) and
124
127
  n[0...owner.size] == owner
125
- debug :persistence, "truncating transitive identifying role #{n.inspect}"
128
+ trace :persistence, "truncating transitive identifying role #{n.inspect}"
126
129
  owner.size.times { n.shift }
127
130
  new_prefix = prefix + [n]
128
131
  elsif (c = role.counterpart_object_type).is_entity_type and
129
132
  (irn = c.identifying_role_names).size == 1 and
130
133
  #irn[0].to_s.split(/_/)[0] == role.owner.basename.downcase
131
134
  irn[0] == role.counterpart.name
132
- #debug :persistence, "=== #{irn[0].to_s.split(/_/)[0]} elided ==="
135
+ #trace :persistence, "=== #{irn[0].to_s.split(/_/)[0]} elided ==="
133
136
  new_prefix = prefix
134
137
  elsif (fa_role = fully_absorbed) && fa_role == role
135
138
  new_prefix = prefix
136
139
  else
137
140
  new_prefix = prefix + [role.name.to_s.split(/_/)]
138
141
  end
139
- #debug :persistence, "new_prefix is #{new_prefix*"."}"
142
+ #trace :persistence, "new_prefix is #{new_prefix*"."}"
140
143
 
141
- debug :persistence, "Absorbing role #{role.name} as #{new_prefix[prefix.size..-1]*"."}" do
144
+ trace :persistence, "Absorbing role #{role.name} as #{new_prefix[prefix.size..-1]*"."}" do
142
145
  role.counterpart_object_type.__absorb(new_prefix, role.counterpart)
143
146
  end
144
147
  end
@@ -74,8 +74,9 @@ module ActiveFacts
74
74
 
75
75
  # Is this Reference covered by a mandatory constraint (implicitly or explicitly)
76
76
  def is_mandatory
77
- !@from_role || # All phantom roles of fact types are mandatory
78
- @from_role.is_mandatory
77
+ !is_unary &&
78
+ (!@from_role || # All phantom roles of fact types are mandatory
79
+ @from_role.is_mandatory)
79
80
  end
80
81
 
81
82
  # Is this Reference from a unary Role?
@@ -188,7 +189,7 @@ module ActiveFacts
188
189
  @from.references_from << self
189
190
  @to.references_to << self if @to # Guard against self-values
190
191
 
191
- debug :references, "Adding #{to_s}"
192
+ trace :references, "Adding #{to_s}"
192
193
  self
193
194
  end
194
195
 
@@ -196,7 +197,7 @@ module ActiveFacts
196
197
  # Remove from @to and @from's reference lists if present
197
198
  return unless @from.references_from.delete(self)
198
199
  @to.references_to.delete self if @to # Guard against self-values
199
- debug :references, "Dropping #{to_s}"
200
+ trace :references, "Dropping #{to_s}"
200
201
  self
201
202
  end
202
203
 
@@ -207,7 +208,7 @@ module ActiveFacts
207
208
 
208
209
  # The reading for the fact type underlying this Reference
209
210
  def reading
210
- is_self_value ? "#{from.name} has value" : @fact_type.default_reading
211
+ is_self_value ? "#{from.name} has value" : @fact_type.reading_preferably_starting_with_role(@from_role).expand
211
212
  end
212
213
 
213
214
  def inspect #:nodoc:
@@ -284,7 +285,7 @@ module ActiveFacts
284
285
 
285
286
  def populate_reference role #:nodoc:
286
287
  role_type = role.role_type
287
- debug :references, "#{name} has #{role_type} role in '#{role.fact_type.describe}'"
288
+ trace :references, "#{name} has #{role_type} role in '#{role.fact_type.describe}'"
288
289
  case role_type
289
290
  when :many_one
290
291
  ActiveFacts::Persistence::Reference.new(self, role).tabulate # A simple reference
@@ -294,7 +295,7 @@ module ActiveFacts
294
295
  ActiveFacts::Persistence::Reference.new(self, role).tabulate # A simple reference; check that
295
296
  else
296
297
  # Can't absorb many of these into one of those
297
- #debug :references, "Ignoring #{role_type} reference from #{name} to #{Reference.new(self, role).to.name}"
298
+ #trace :references, "Ignoring #{role_type} reference from #{name} to #{Reference.new(self, role).to.name}"
298
299
  end
299
300
 
300
301
  when :unary
@@ -304,11 +305,11 @@ module ActiveFacts
304
305
  # REVISIT: Or when partitioned
305
306
  raise hell unless role.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance)
306
307
  if role.fact_type.assimilation # assimilation == 'separate' or assimilation == 'partitioned'
307
- debug :references, "supertype #{name} doesn't absorb a reference to separate subtype #{role.fact_type.subtype.name}"
308
+ trace :references, "supertype #{name} doesn't absorb a reference to separate subtype #{role.fact_type.subtype.name}"
308
309
  else
309
310
  r = ActiveFacts::Persistence::Reference.new(self, role)
310
311
  r.to.absorbed_via = r
311
- debug :references, "supertype #{name} absorbs subtype #{r.to.name}"
312
+ trace :references, "supertype #{name} absorbs subtype #{r.to.name}"
312
313
  r.tabulate
313
314
  end
314
315
 
@@ -317,7 +318,7 @@ module ActiveFacts
317
318
  ActiveFacts::Persistence::Reference.new(self, role).tabulate
318
319
  # If partitioned, the supertype is absorbed into *each* subtype; a reference to the supertype needs to know which
319
320
  else
320
- # debug :references, "subtype #{name} is absorbed into #{role.fact_type.supertype.name}"
321
+ # trace :references, "subtype #{name} is absorbed into #{role.fact_type.supertype.name}"
321
322
  end
322
323
 
323
324
  when :one_one
@@ -340,11 +341,11 @@ module ActiveFacts
340
341
 
341
342
  # Force the decision if one EntityType identifies another:
342
343
  if preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role == r.to_role}
343
- debug :references, "EntityType #{name} is identified by EntityType #{r.to.name}, so gets absorbed elsewhere"
344
+ trace :references, "EntityType #{name} is identified by EntityType #{r.to.name}, so gets absorbed elsewhere"
344
345
  return
345
346
  end
346
347
  if r.to.preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role == role}
347
- debug :references, "EntityType #{name} identifies EntityType #{r.to.name}, so absorbs it"
348
+ trace :references, "EntityType #{name} identifies EntityType #{r.to.name}, so absorbs it"
348
349
  r.to.absorbed_via = r
349
350
  # We can't be absorbed into our supertype!
350
351
  # REVISIT: We might need to flip all one-to-ones as well
@@ -362,6 +363,7 @@ module ActiveFacts
362
363
  r.tabulate
363
364
  end
364
365
  else
366
+ # REVISIT: Should we implicitly objectify this fact type here and add a spanning UC?
365
367
  raise "Role #{role.object_type.name} in '#{role.fact_type.default_reading}' lacks a uniqueness constraint"
366
368
  end
367
369
  end
@@ -381,9 +383,9 @@ module ActiveFacts
381
383
 
382
384
  class Vocabulary
383
385
  def populate_all_references #:nodoc:
384
- debug :references, "Populating all object_type references" do
386
+ trace :references, "Populating all object_type references" do
385
387
  all_object_type.each do |object_type|
386
- debug :references, "Populating references for #{object_type.name}" do
388
+ trace :references, "Populating references for #{object_type.name}" do
387
389
  object_type.populate_references
388
390
  end
389
391
  end
@@ -392,13 +394,13 @@ module ActiveFacts
392
394
  end
393
395
 
394
396
  def show_all_references
395
- if debug :references
396
- debug :references, "Finished object_type references" do
397
+ if trace :references
398
+ trace :references, "Finished object_type references" do
397
399
  all_object_type.each do |object_type|
398
400
  next unless object_type.references_from.size > 0
399
- debug :references, "#{object_type.name}:" do
401
+ trace :references, "#{object_type.name}:" do
400
402
  object_type.references_from.each do |ref|
401
- debug :references, "#{ref}"
403
+ trace :references, "#{ref}"
402
404
  end
403
405
  end
404
406
  end