activefacts 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -13,7 +13,7 @@ module ActiveFacts
13
13
  end
14
14
 
15
15
  def prepare_roles clauses = nil
16
- debug :binding, "preparing roles" do
16
+ trace :binding, "preparing roles" do
17
17
  @context ||= CompilationContext.new(@vocabulary)
18
18
  @context.bind clauses||[], @conditions, @returning
19
19
  end
@@ -28,7 +28,7 @@ module ActiveFacts
28
28
 
29
29
  # Build the query:
30
30
  unless @conditions.empty? and !@returning
31
- debug :query, "building query for derived fact type" do
31
+ trace :query, "building query for derived fact type" do
32
32
  @query = build_variables(@conditions.flatten)
33
33
  @roles_by_binding = build_all_steps(@conditions)
34
34
  @query.validate
@@ -43,7 +43,7 @@ module ActiveFacts
43
43
  @conditions.each do |condition|
44
44
  next if condition.is_naked_object_type
45
45
  # REVISIT: Many conditions will imply a number of different steps, which need to be handled (similar to nested_clauses).
46
- debug :projection, "matching condition fact_type #{condition.inspect}" do
46
+ trace :projection, "matching condition fact_type #{condition.inspect}" do
47
47
  fact_type = condition.match_existing_fact_type @context
48
48
  raise "Unrecognised fact type #{condition.inspect} in #{self.class}" unless fact_type
49
49
  end
@@ -131,7 +131,7 @@ module ActiveFacts
131
131
  @existing_clauses = [first_clause]
132
132
  elsif (n = @clauses.size - @existing_clauses.size) > 0
133
133
  raise "Cannot extend a negated fact type" if @existing_clauses.detect {|clause| clause.certainty == false }
134
- debug :binding, "Extending existing fact type with #{n} new readings"
134
+ trace :binding, "Extending existing fact type with #{n} new readings"
135
135
  end
136
136
 
137
137
  # Now make any new readings:
@@ -236,7 +236,7 @@ module ActiveFacts
236
236
  return nil if fact_types.empty? # There are no matched fact types
237
237
 
238
238
  if @clauses.size == 1 && @existing_clauses[0].side_effects.cost != 0
239
- debug :matching, "There's only a single clause, but it's not an exact match"
239
+ trace :matching, "There's only a single clause, but it's not an exact match"
240
240
  return nil
241
241
  end
242
242
 
@@ -283,9 +283,9 @@ module ActiveFacts
283
283
 
284
284
  # We need to check uniqueness constraints after processing the whole vocabulary
285
285
  # raise "Fact type must be named as it has no identifying uniqueness constraint" unless @name || @fact_type.all_role.size == 1
286
- debug :constraint, "Need to check #{@fact_type.default_reading.inspect} for a uniqueness constraint"
286
+ trace :constraint, "Need to check #{@fact_type.default_reading.inspect} for a uniqueness constraint"
287
287
  fact_type.check_and_add_spanning_uniqueness_constraint = proc do
288
- debug :constraint, "Checking #{@fact_type.default_reading.inspect} for a uniqueness constraint"
288
+ trace :constraint, "Checking #{@fact_type.default_reading.inspect} for a uniqueness constraint"
289
289
  existing_pc = nil
290
290
  found = @fact_type.all_role.
291
291
  detect do |role|
@@ -312,9 +312,9 @@ module ActiveFacts
312
312
  :max_frequency => 1,
313
313
  :is_preferred_identifier => true # (prefer || !!@fact_type.entity_type)
314
314
  )
315
- debug :constraint, "Made new fact type implicit PC GUID=#{pc.concept.guid} #{pc.name} min=nil max=1 over #{rs.describe}"
315
+ trace :constraint, "Made new fact type implicit PC GUID=#{pc.concept.guid} #{pc.name} min=nil max=1 over #{rs.describe}"
316
316
  elsif pc
317
- debug :constraint, "Will rely on existing UC GUID=#{pc.concept.guid} #{pc.name} to be used as PI over #{rs.describe}"
317
+ trace :constraint, "Will rely on existing UC GUID=#{pc.concept.guid} #{pc.name} to be used as PI over #{rs.describe}"
318
318
  end
319
319
  end
320
320
  end
@@ -349,7 +349,7 @@ module ActiveFacts
349
349
  clauses_m = clauses_by_refs[variants[m]]
350
350
  l_keys = variants[l]-common
351
351
  m_keys = variants[m]-common
352
- debug :binding, "Try to collapse variant #{m} onto #{l}; diffs are #{l_keys.inspect} -> #{m_keys.inspect}"
352
+ trace :binding, "Try to collapse variant #{m} onto #{l}; diffs are #{l_keys.inspect} -> #{m_keys.inspect}"
353
353
  rebindings = 0
354
354
  l_keys.each_with_index do |l_key, i|
355
355
  # Find possible rebinding candidates; there must be exactly one.
@@ -358,19 +358,19 @@ module ActiveFacts
358
358
  m_key = m_keys[j]
359
359
  l_ref = refs_by_clause_and_key[[clauses_l[0], l_key]]
360
360
  m_ref = refs_by_clause_and_key[[clauses_m[0], m_key]]
361
- debug :binding, "Can we match #{l_ref.inspect} (#{i}) with #{m_ref.inspect} (#{j})?"
361
+ trace :binding, "Can we match #{l_ref.inspect} (#{i}) with #{m_ref.inspect} (#{j})?"
362
362
  next if m_ref.player != l_ref.player
363
363
  if has_more_adjectives(m_ref, l_ref)
364
- debug :binding, "can rebind #{m_ref.inspect} to #{l_ref.inspect}"
364
+ trace :binding, "can rebind #{m_ref.inspect} to #{l_ref.inspect}"
365
365
  candidates << [m_ref, l_ref]
366
366
  elsif has_more_adjectives(l_ref, m_ref)
367
- debug :binding, "can rebind #{l_ref.inspect} to #{m_ref.inspect}"
367
+ trace :binding, "can rebind #{l_ref.inspect} to #{m_ref.inspect}"
368
368
  candidates << [l_ref, m_ref]
369
369
  end
370
370
  end
371
371
 
372
- # debug :binding, "found #{candidates.size} rebinding candidates for this role"
373
- debug :binding, "rebinding is ambiguous so not attempted" if candidates.size > 1
372
+ # trace :binding, "found #{candidates.size} rebinding candidates for this role"
373
+ trace :binding, "rebinding is ambiguous so not attempted" if candidates.size > 1
374
374
  if (candidates.size == 1)
375
375
  candidates[0][0].rebind_to(@context, candidates[0][1])
376
376
  rebindings += 1
@@ -379,7 +379,7 @@ module ActiveFacts
379
379
  end
380
380
  if (rebindings == l_keys.size)
381
381
  # Successfully rebound this fact type
382
- debug :binding, "Successfully rebound clauses #{clauses_l.map{|r|r.inspect}*'; '} on to #{clauses_m.map{|r|r.inspect}*'; '}"
382
+ trace :binding, "Successfully rebound clauses #{clauses_l.map{|r|r.inspect}*'; '} on to #{clauses_m.map{|r|r.inspect}*'; '}"
383
383
  break
384
384
  else
385
385
  # No point continuing, we failed on this one.
@@ -4,11 +4,11 @@ module ActiveFacts
4
4
  class Definition
5
5
  # Make a Variable for every binding present in these clauses
6
6
  def build_variables(clauses_list)
7
- debug :query, "Building variables" do
7
+ trace :query, "Building variables" do
8
8
  query = @constellation.Query(:new)
9
9
  all_bindings_in_clauses(clauses_list).
10
10
  each do |binding|
11
- debug :query, "Creating variable #{query.all_variable.size} for #{binding.inspect}"
11
+ trace :query, "Creating variable #{query.all_variable.size} for #{binding.inspect}"
12
12
  binding.variable = @constellation.Variable(query, query.all_variable.size, :object_type => binding.player)
13
13
  if literal = binding.refs.detect{|r| r.literal}
14
14
  if literal.kind_of?(ActiveFacts::CQL::Compiler::Reference)
@@ -25,7 +25,7 @@ module ActiveFacts
25
25
 
26
26
  def build_all_steps(clauses_list)
27
27
  roles_by_binding = {}
28
- debug :query, "Building steps" do
28
+ trace :query, "Building steps" do
29
29
  clauses_list.each do |clause|
30
30
  next if clause.is_naked_object_type
31
31
  build_steps(clause, roles_by_binding)
@@ -37,7 +37,7 @@ module ActiveFacts
37
37
  def build_steps clause, roles_by_binding = {}, objectification_node = nil
38
38
  plays = []
39
39
  incidental_roles = []
40
- debug :query, "Creating Role Sequence for #{clause.inspect} with #{clause.refs.size} role refs" do
40
+ trace :query, "Creating Role Sequence for #{clause.inspect} with #{clause.refs.size} role refs" do
41
41
  objectification_step = nil
42
42
  clause.refs.each do |ref|
43
43
  # These refs are the Compiler::References, which have associated Metamodel::RoleRefs,
@@ -62,7 +62,7 @@ module ActiveFacts
62
62
  end
63
63
  refs_count += objectification_ref_count
64
64
 
65
- debug :query, "Creating Variable #{ref.inspect} (counts #{refs_count}/#{objectification_ref_count}) and objectification Step for #{ref.inspect}" do
65
+ trace :query, "Creating Variable #{ref.inspect} (counts #{refs_count}/#{objectification_ref_count}) and objectification Step for #{ref.inspect}" do
66
66
 
67
67
  raise "Internal error: Trying to add role of #{role.object_type.name} to variable for #{binding.variable.object_type.name}" unless binding.variable.object_type == role.object_type
68
68
  play = @constellation.Play(binding.variable, role)
@@ -81,7 +81,7 @@ module ActiveFacts
81
81
  # This is an implicit objectification, just the FT clause, not ET(where ...clause...)
82
82
  # We need to create a Variable for this object, even though it has no References
83
83
  query = binding.variable.query
84
- debug :query, "Creating JN#{query.all_variable.size} for #{clause.fact_type.entity_type.name} in objectification"
84
+ trace :query, "Creating JN#{query.all_variable.size} for #{clause.fact_type.entity_type.name} in objectification"
85
85
  objectification_node = @constellation.Variable(query, query.all_variable.size, :object_type => clause.fact_type.entity_type)
86
86
  end
87
87
  raise "Internal error: Trying to add role of #{role.link_fact_type.all_role.single.object_type.name} to variable for #{objectification_node.object_type.name}" unless objectification_node.object_type == role.link_fact_type.all_role.single.object_type
@@ -99,14 +99,14 @@ module ActiveFacts
99
99
  if clause.certainty == nil
100
100
  objectification_step.is_optional = true
101
101
  end
102
- debug :query, "New #{objectification_step.describe}"
103
- debug :query, "Associating #{incidental_roles.map(&:describe)*', '} incidental roles with #{objectification_step.describe}" if incidental_roles.size > 0
102
+ trace :query, "New #{objectification_step.describe}"
103
+ trace :query, "Associating #{incidental_roles.map(&:describe)*', '} incidental roles with #{objectification_step.describe}" if incidental_roles.size > 0
104
104
  incidental_roles.each { |jr| jr.step = objectification_step }
105
105
  incidental_roles = []
106
106
  plays = []
107
107
  end
108
108
  else
109
- debug :query, "Creating Reference for #{ref.inspect}" do
109
+ trace :query, "Creating Reference for #{ref.inspect}" do
110
110
  # REVISIT: If there's an implicit subtyping step here, create it; then always raise the error here.
111
111
  # I don't want to do this for now because the verbaliser will always verbalise all steps.
112
112
  if binding.variable.object_type != role.object_type and
@@ -129,7 +129,7 @@ module ActiveFacts
129
129
  # Each of these ImplicitFactTypes has a single phantom role played by the objectifying entity type
130
130
  # One of these phantom roles is likely to be the subject of an objectification step.
131
131
  ref.nested_clauses.each do |r|
132
- debug :query, "Building objectification step for #{ref.nested_clauses.inspect}" do
132
+ trace :query, "Building objectification step for #{ref.nested_clauses.inspect}" do
133
133
  build_steps r, roles_by_binding, binding.variable
134
134
  end
135
135
  end
@@ -154,8 +154,8 @@ module ActiveFacts
154
154
  :is_disallowed => clause.certainty == false,
155
155
  :is_optional => clause.certainty == nil
156
156
  )
157
- debug :query, "New #{js.describe}"
158
- debug :query, "Associating #{incidental_roles.map(&:describe)*', '} incidental roles with #{js.describe}" if incidental_roles.size > 0
157
+ trace :query, "New #{js.describe}"
158
+ trace :query, "Associating #{incidental_roles.map(&:describe)*', '} incidental roles with #{js.describe}" if incidental_roles.size > 0
159
159
  incidental_roles.each { |jr| jr.step = js }
160
160
  end
161
161
  roles_by_binding
@@ -35,6 +35,15 @@ module ActiveFacts
35
35
  def <=>(other)
36
36
  key <=> other.key
37
37
  end
38
+
39
+ def add_ref ref
40
+ @refs << ref
41
+ ref
42
+ end
43
+
44
+ def delete_ref ref
45
+ @refs.delete ref
46
+ end
38
47
  end
39
48
 
40
49
  class CompilationContext
@@ -29,9 +29,9 @@ module ActiveFacts
29
29
  end
30
30
  @offset = nil if @offset.to_f == 0
31
31
 
32
- debug :units, "Defining new unit #{@singular}#{@plural ? "/"+@plural : ""}" do
33
- debug :units, "Coefficient is #{coefficient.numerator}#{coefficient.denominator != 1 ? "/#{coefficient.denominator}" : ""} #{coefficient.is_precise ? "exactly" : "approximately"}" if coefficient
34
- debug :units, "Offset is #{@offset}" if @offset
32
+ trace :units, "Defining new unit #{@singular}#{@plural ? "/"+@plural : ""}" do
33
+ trace :units, "Coefficient is #{coefficient.numerator}#{coefficient.denominator != 1 ? "/#{coefficient.denominator}" : ""} #{coefficient.is_precise ? "exactly" : "approximately"}" if coefficient
34
+ trace :units, "Offset is #{@offset}" if @offset
35
35
  raise "Redefinition of unit #{@singular}" if @constellation.Unit.values.detect{|u| u.name == @singular}
36
36
  raise "Redefinition of unit #{@plural}" if @constellation.Unit.values.detect{|u| u.name == @plural}
37
37
  unit = @constellation.Unit(:new,
@@ -45,7 +45,7 @@ module ActiveFacts
45
45
  )
46
46
  @base_units.each do |base_unit, exponent|
47
47
  base = @constellation.Unit.values.detect{|u| u.name == base_unit || u.plural_name == base_unit }
48
- debug :units, "Base unit #{base_unit}^#{exponent} #{base ? "" : "(implicitly fundamental)"}"
48
+ trace :units, "Base unit #{base_unit}^#{exponent} #{base ? "" : "(implicitly fundamental)"}"
49
49
  base ||= @constellation.Unit(:new, :name => base_unit, :is_fundamental => true, :vocabulary => @vocabulary)
50
50
  @constellation.Derivation(:derived_unit => unit, :base_unit => base, :exponent => exponent)
51
51
  end
@@ -58,12 +58,12 @@ module ActiveFacts
58
58
  end
59
59
 
60
60
  def object_type(name, kind)
61
- index_name(@terms, name) && debug(:context, "new #{kind} '#{name}'")
61
+ index_name(@terms, name) && trace(:context, "new #{kind} '#{name}'")
62
62
  true
63
63
  end
64
64
 
65
65
  def reset_role_names
66
- debug :context, "\tresetting role names #{@role_names.keys.sort*", "}" if @role_names && @role_names.size > 0
66
+ trace :context, "\tresetting role names #{@role_names.keys.sort*", "}" if @role_names && @role_names.size > 0
67
67
  @role_names = {}
68
68
  end
69
69
 
@@ -72,17 +72,17 @@ module ActiveFacts
72
72
  end
73
73
 
74
74
  def new_leading_adjective_term(adj, term)
75
- index_name(@role_names, "#{adj} #{term}", term) && debug(:context, "new compound term '#{adj}- #{term}'")
75
+ index_name(@role_names, "#{adj} #{term}", term) && trace(:context, "new compound term '#{adj}- #{term}'")
76
76
  true
77
77
  end
78
78
 
79
79
  def new_trailing_adjective_term(adj, term)
80
- index_name(@role_names, "#{term} #{adj}", term) && debug(:context, "new compound term '#{term} -#{adj}'")
80
+ index_name(@role_names, "#{term} #{adj}", term) && trace(:context, "new compound term '#{term} -#{adj}'")
81
81
  true
82
82
  end
83
83
 
84
84
  def role_name(name)
85
- index_name(@role_names, name) && debug(:context, "new role '#{name}'")
85
+ index_name(@role_names, name) && trace(:context, "new role '#{name}'")
86
86
  true
87
87
  end
88
88
 
@@ -98,11 +98,11 @@ module ActiveFacts
98
98
  @global_term = @term = @term_part
99
99
  @context_saver.context = {:term => @term, :global_term => @global_term }
100
100
  end
101
- debug :context, "Term #{t[s] ? "is" : "starts"} '#{@term_part}'"
101
+ trace :context, "Term #{t[s] ? "is" : "starts"} '#{@term_part}'"
102
102
  elsif @allowed_forward_terms.include?(@term_part)
103
103
  @term = @term_part
104
104
  @context_saver.context = {:term => @term, :global_term => @term }
105
- debug :context, "Term #{s} is an allowed forward"
105
+ trace :context, "Term #{s} is an allowed forward"
106
106
  return true
107
107
  end
108
108
  t
@@ -119,13 +119,13 @@ module ActiveFacts
119
119
  w = "role_name"
120
120
  end
121
121
  if t
122
- debug :context, "Multi-word #{w} #{t[@term_part] ? 'ends at' : 'continues to'} #{@term_part.inspect}"
122
+ trace :context, "Multi-word #{w} #{t[@term_part] ? 'ends at' : 'continues to'} #{@term_part.inspect}"
123
123
 
124
124
  # Record the name of the full term and the underlying global term:
125
125
  if t[@term_part]
126
126
  @term = @term_part if t[@term_part]
127
127
  @global_term = (t = t[@term_part]) == true ? @term_part : t
128
- debug :context, "saving context #{@term}/#{@global_term}"
128
+ trace :context, "saving context #{@term}/#{@global_term}"
129
129
  @context_saver.context = {:term => @term, :global_term => @global_term }
130
130
  end
131
131
  end
@@ -15,8 +15,9 @@ module ActiveFacts
15
15
  # afgen --cql <file>.cql
16
16
  class CQL < Helpers::OrderedDumper
17
17
  private
18
- def vocabulary_start(vocabulary)
19
- puts "vocabulary #{vocabulary.name};\n\n"
18
+ def vocabulary_start
19
+ puts "vocabulary #{@vocabulary.name};\n\n"
20
+ build_indices
20
21
  end
21
22
 
22
23
  def vocabulary_end
@@ -56,7 +57,7 @@ module ActiveFacts
56
57
  end
57
58
 
58
59
  def entity_type_dump(o)
59
- @object_types_dumped[o] = true
60
+ o.ordered_dumped!
60
61
  pi = o.preferred_identifier
61
62
 
62
63
  supers = o.supertypes
@@ -67,7 +68,7 @@ module ActiveFacts
67
68
  else
68
69
  non_subtype_dump(o, pi)
69
70
  end
70
- @constraints_used[pi] = true
71
+ pi.ordered_dumped! if pi
71
72
  end
72
73
 
73
74
  def append_ring_to_reading(reading, ring)
@@ -124,8 +125,8 @@ module ActiveFacts
124
125
  end
125
126
  end
126
127
  end
127
- debug :mode, "Didn't find standard forward reading" unless forward_reading
128
- debug :mode, "Didn't find standard reverse reading" unless reverse_reading
128
+ trace :mode, "Didn't find standard forward reading" unless forward_reading
129
+ trace :mode, "Didn't find standard reverse reading" unless reverse_reading
129
130
  [forward_reading, reverse_reading]
130
131
  end
131
132
 
@@ -149,7 +150,7 @@ module ActiveFacts
149
150
  return nil if entity_type.fact_type and
150
151
  entity_type.fact_type.all_role.detect{|role| role.object_type == value_role.object_type }
151
152
 
152
- @fact_types_dumped[fact_type] = true # We've covered this fact type
153
+ fact_type.ordered_dumped! # We've covered this fact type
153
154
 
154
155
  # Elide the constraints that would have been emitted on the standard readings.
155
156
  # If there is a UC that's not in the standard form for a reference mode,
@@ -158,13 +159,13 @@ module ActiveFacts
158
159
  fact_constraints.each do |pc|
159
160
  if (pc.role_sequence.all_role_ref.size == 1 and pc.max_frequency == 1)
160
161
  # It's a uniqueness constraint, and will be regenerated
161
- @constraints_used[pc] = true
162
+ pc.ordered_dumped!
162
163
  end
163
164
  end
164
165
 
165
166
  # Figure out which non-standard readings exist, if any:
166
167
  nonstandard_readings = fact_type.all_reading - [forward_reading, reverse_reading]
167
- debug :mode, "--- nonstandard_readings.size now = #{nonstandard_readings.size}" if nonstandard_readings.size > 0
168
+ trace :mode, "--- nonstandard_readings.size now = #{nonstandard_readings.size}" if nonstandard_readings.size > 0
168
169
 
169
170
  verbaliser = ActiveFacts::Metamodel::Verbaliser.new
170
171
 
@@ -214,7 +215,7 @@ module ActiveFacts
214
215
  identifying_facts.each do |fact_type|
215
216
  # The RoleRefs for corresponding roles across all readings are for the same player.
216
217
  verbaliser.alternate_readings fact_type.all_reading
217
- @fact_types_dumped[fact_type] = true unless fact_type.entity_type # Must dump objectification still!
218
+ fact_type.ordered_dumped! unless fact_type.entity_type # Must dump objectification still!
218
219
  end
219
220
  verbaliser.create_subscripts(:rolenames)
220
221
 
@@ -304,7 +305,7 @@ module ActiveFacts
304
305
  if reading_texts.size > 1
305
306
  ambiguity =
306
307
  fact_type.all_role.to_a[0].object_type.all_role.map{|r| r.fact_type}.
307
- select{|f| f != fact_type && @fact_types_dumped.include?(f) }.
308
+ select{|f| f != fact_type && f.ordered_dumped }.
308
309
  detect do |dft|
309
310
  ambiguous_readings =
310
311
  reading_texts & dft.all_reading.map{|r| naiive_expand(r)}
@@ -397,16 +398,16 @@ module ActiveFacts
397
398
 
398
399
  # Tell the verbaliser all we know, so it can figure out which players to subscript:
399
400
  players = []
400
- debug :subscript, "Preparing query across projected roles in set comparison constraint" do
401
+ trace :subscript, "Preparing query across projected roles in set comparison constraint" do
401
402
  transposed_role_refs.each do |role_refs|
402
403
  verbaliser.role_refs_have_subtype_steps role_refs
403
404
  join_over, = ActiveFacts::Metamodel.plays_over(role_refs.map{|rr| rr.role})
404
405
  players << join_over
405
406
  end
406
407
  end
407
- debug :subscript, "Preparing query between roles in set comparison constraint" do
408
+ trace :subscript, "Preparing query between roles in set comparison constraint" do
408
409
  role_sequences.each do |role_sequence|
409
- debug :subscript, "role sequence is #{role_sequence.describe}" do
410
+ trace :subscript, "role sequence is #{role_sequence.describe}" do
410
411
  verbaliser.prepare_role_sequence role_sequence
411
412
  end
412
413
  end
@@ -559,8 +560,8 @@ module ActiveFacts
559
560
  value_constraints = []
560
561
  roles.each do |role|
561
562
  value_constraints <<
562
- if vc = role.role_value_constraint and !@constraints_used[vc]
563
- @constraints_used[vc] = true
563
+ if vc = role.role_value_constraint and !vc.ordered_dumped
564
+ vc.ordered_dumped!
564
565
  vc.describe
565
566
  else
566
567
  nil
@@ -571,10 +572,10 @@ module ActiveFacts
571
572
  constraint = fact_constraints.
572
573
  detect do |c| # Find a UC that spans all other Roles
573
574
  c.is_a?(ActiveFacts::Metamodel::PresenceConstraint) &&
574
- !@constraints_used[c] && # Already verbalised
575
+ !c.ordered_dumped && # Already verbalised
575
576
  roles-c.role_sequence.all_role_ref.map(&:role) == [role]
576
577
  end
577
- @constraints_used[constraint] = true if constraint
578
+ constraint.ordered_dumped! if constraint
578
579
  constraint && constraint.frequency
579
580
  else
580
581
  nil
@@ -585,8 +586,8 @@ module ActiveFacts
585
586
  expanded = "it is not the case that "+expanded if (reading.is_negative)
586
587
 
587
588
  if (ft_rings = @ring_constraints_by_fact[reading.fact_type]) &&
588
- (ring = ft_rings.detect{|rc| !@constraints_used[rc]})
589
- @constraints_used[ring] = true
589
+ (ring = ft_rings.detect{|rc| !rc.ordered_dumped})
590
+ ring.ordered_dumped!
590
591
  append_ring_to_reading(expanded, ring)
591
592
  end
592
593
  expanded
@@ -609,6 +610,26 @@ module ActiveFacts
609
610
  expanded
610
611
  end
611
612
 
613
+ def build_indices
614
+ @presence_constraints_by_fact = Hash.new{ |h, k| h[k] = [] }
615
+ @ring_constraints_by_fact = Hash.new{ |h, k| h[k] = [] }
616
+
617
+ @vocabulary.all_constraint.each { |c|
618
+ case c
619
+ when ActiveFacts::Metamodel::PresenceConstraint
620
+ fact_types = c.role_sequence.all_role_ref.map{|rr| rr.role.fact_type}.uniq # All fact types spanned by this constraint
621
+ if fact_types.size == 1 # There's only one, save it:
622
+ # trace "Single-fact constraint on #{fact_types[0].concept.guid}: #{c.name}"
623
+ (@presence_constraints_by_fact[fact_types[0]] ||= []) << c
624
+ end
625
+ when ActiveFacts::Metamodel::RingConstraint
626
+ (@ring_constraints_by_fact[c.role.fact_type] ||= []) << c
627
+ else
628
+ # trace "Found unhandled constraint #{c.class} #{c.name}"
629
+ end
630
+ }
631
+ end
632
+
612
633
  end
613
634
  end
614
635