activefacts 1.1.0 → 1.2.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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/bin/cql +137 -91
  3. data/css/style.css +3 -3
  4. data/examples/CQL/Insurance.cql +1 -1
  5. data/examples/CQL/SeparateSubtype.cql +2 -2
  6. data/lib/activefacts/cql/Language/English.treetop +9 -0
  7. data/lib/activefacts/cql/ObjectTypes.treetop +1 -1
  8. data/lib/activefacts/cql/Terms.treetop +3 -1
  9. data/lib/activefacts/cql/ValueTypes.treetop +10 -4
  10. data/lib/activefacts/cql/compiler.rb +1 -0
  11. data/lib/activefacts/cql/compiler/clause.rb +53 -23
  12. data/lib/activefacts/cql/compiler/entity_type.rb +0 -4
  13. data/lib/activefacts/cql/compiler/expression.rb +9 -13
  14. data/lib/activefacts/cql/compiler/fact.rb +49 -48
  15. data/lib/activefacts/cql/compiler/fact_type.rb +23 -20
  16. data/lib/activefacts/cql/compiler/query.rb +49 -121
  17. data/lib/activefacts/cql/compiler/shared.rb +5 -1
  18. data/lib/activefacts/cql/compiler/value_type.rb +4 -2
  19. data/lib/activefacts/generate/rails/schema.rb +138 -108
  20. data/lib/activefacts/generate/transform/surrogate.rb +1 -2
  21. data/lib/activefacts/mapping/rails.rb +52 -45
  22. data/lib/activefacts/persistence/columns.rb +5 -5
  23. data/lib/activefacts/persistence/tables.rb +6 -4
  24. data/lib/activefacts/support.rb +0 -2
  25. data/lib/activefacts/version.rb +1 -1
  26. data/lib/activefacts/vocabulary/extensions.rb +64 -42
  27. data/lib/activefacts/vocabulary/metamodel.rb +14 -12
  28. data/lib/activefacts/vocabulary/verbaliser.rb +98 -92
  29. data/spec/cql/expressions_spec.rb +8 -3
  30. data/spec/cql/parser/entity_types_spec.rb +1 -1
  31. data/spec/cql/parser/expressions_spec.rb +66 -52
  32. data/spec/cql/parser/fact_types_spec.rb +1 -1
  33. data/spec/cql/parser/literals_spec.rb +10 -10
  34. data/spec/cql/parser/pragmas_spec.rb +3 -3
  35. data/spec/cql/parser/value_types_spec.rb +1 -1
  36. metadata +2 -2
@@ -66,8 +66,7 @@ module ActiveFacts
66
66
 
67
67
  class ValueType
68
68
  def needs_surrogate
69
- supertype_names = supertypes_transitive.map(&:name)
70
- !(supertype_names.include?('Auto Counter') or supertype_names.include?('Guid') or supertype_names.include?('ID'))
69
+ !is_auto_assigned
71
70
  end
72
71
 
73
72
  def inject_surrogate
@@ -4,52 +4,7 @@ require 'active_support'
4
4
  require 'digest/sha1'
5
5
 
6
6
  module ActiveFacts
7
-
8
7
  module Persistence
9
- # Return ActiveRecord type and (modified?) length for the passed base type
10
- def self.rails_type(type, length)
11
- rails_type = case type
12
- when /^Auto ?Counter$/
13
- 'integer' # REVISIT: Need to detect surrogate ID fields and handle them correctly
14
-
15
- when /^Unsigned ?Integer$/,
16
- /^Integer$/,
17
- /^Signed ?Integer$/,
18
- /^Unsigned ?Small ?Integer$/,
19
- /^Signed ?Small ?Integer$/,
20
- /^Unsigned ?Tiny ?Integer$/
21
- length = nil
22
- 'integer'
23
-
24
- when /^Decimal$/
25
- 'decimal'
26
-
27
- when /^Fixed ?Length ?Text$/, /^Char$/
28
- 'string'
29
- when /^Variable ?Length ?Text$/, /^String$/
30
- 'string'
31
- when /^Large ?Length ?Text$/, /^Text$/
32
- 'text'
33
-
34
- when /^Date ?And ?Time$/, /^Date ?Time$/
35
- 'datetime'
36
- when /^Date$/
37
- 'datetime'
38
- when /^Time$/
39
- 'time'
40
- when /^Auto ?Time ?Stamp$/
41
- 'timestamp'
42
-
43
- when /^Money$/
44
- 'decimal'
45
- when /^Picture ?Raw ?Data$/, /^Image$/, /^Variable ?Length ?Raw ?Data$/, /^Blob$/
46
- 'binary'
47
- when /^BIT$/
48
- 'boolean'
49
- else type # raise "ActiveRecord type unknown for standard type #{type}"
50
- end
51
- [rails_type, length]
52
- end
53
8
 
54
9
  def self.rails_plural_name name
55
10
  # Crunch spaces and pluralise the first part, all in snake_case
@@ -74,6 +29,58 @@ module ActiveFacts
74
29
  def rails_name
75
30
  Persistence::rails_singular_name(name('_'))
76
31
  end
32
+
33
+ def rails_type
34
+ type_name, params, constraints = *type()
35
+ rails_type = case type_name
36
+ when /^Auto ?Counter$/i
37
+ 'serial' # REVISIT: Need to detect surrogate ID fields and handle them correctly
38
+
39
+ when /^[Ug]uid$/i
40
+ 'uuid'
41
+
42
+ when /^Unsigned ?Integer$/i,
43
+ /^Integer$/i,
44
+ /^Signed ?Integer$/i,
45
+ /^Unsigned ?Small ?Integer$/i,
46
+ /^Signed ?Small ?Integer$/i,
47
+ /^Unsigned ?Tiny ?Integer$/i
48
+ length = nil
49
+ 'integer'
50
+
51
+ when /^Decimal$/i
52
+ 'decimal'
53
+
54
+ when /^Float$/i, /^Double$/i, /^Real$/i
55
+ 'float'
56
+
57
+ when /^Fixed ?Length ?Text$/i, /^Char$/i
58
+ 'string'
59
+ when /^Variable ?Length ?Text$/i, /^String$/i
60
+ 'string'
61
+ when /^Large ?Length ?Text$/i, /^Text$/i
62
+ 'text'
63
+
64
+ when /^Date ?And ?Time$/i, /^Date ?Time$/i
65
+ 'datetime'
66
+ when /^Date$/i
67
+ 'datetime'
68
+ when /^Time$/i
69
+ 'time'
70
+ when /^Auto ?Time ?Stamp$/i
71
+ 'timestamp'
72
+
73
+ when /^Money$/i
74
+ 'decimal'
75
+ when /^Picture ?Raw ?Data$/i, /^Image$/i, /^Variable ?Length ?Raw ?Data$/i, /^Blob$/i
76
+ 'binary'
77
+ when /^BIT$/i, /^Boolean$/i
78
+ 'boolean'
79
+ else
80
+ type_name # raise "ActiveRecord type unknown for standard type #{type}"
81
+ end
82
+ [rails_type, params[:length]]
83
+ end
77
84
  end
78
85
 
79
86
  class Index
@@ -163,15 +163,15 @@ module ActiveFacts
163
163
  end
164
164
 
165
165
  vt = references[-1].is_self_value ? references[-1].from : references[-1].to
166
- params[:length] ||= vt.length if vt.length.to_i != 0
167
- params[:scale] ||= vt.scale if vt.scale.to_i != 0
168
- while vt.supertype
166
+ begin
169
167
  params[:length] ||= vt.length if vt.length.to_i != 0
170
168
  params[:scale] ||= vt.scale if vt.scale.to_i != 0
171
169
  constraints << vt.value_constraint if vt.value_constraint
170
+ last_vt = vt
172
171
  vt = vt.supertype
173
- end
174
- return [vt.name, params, constraints]
172
+ end while vt
173
+ params[:underlying_type] = last_vt
174
+ return [last_vt.name, params, constraints]
175
175
  end
176
176
 
177
177
  # The comment is the readings from the References expressed as a series of steps (not a full verbalisation)
@@ -46,13 +46,15 @@ module ActiveFacts
46
46
  @is_table
47
47
  end
48
48
 
49
- # Is this ValueType auto-assigned on first save to the database?
49
+ # Is this ValueType auto-assigned either at assert or on first save to the database?
50
50
  def is_auto_assigned
51
- # REVISIT: Find a better way to determine AutoCounters (ValueType unary role?)
52
51
  type = self
53
- type = type.supertype while type.supertype
54
- type.name =~ /^Auto/
52
+ while type
53
+ return true if type.name =~ /^Auto/ || type.transaction_phase
54
+ type = type.supertype
55
+ end
55
56
  end
57
+ false
56
58
  end
57
59
 
58
60
  class EntityType < DomainObjectType
@@ -1,7 +1,5 @@
1
1
  #
2
2
  # ActiveFacts Support code.
3
- # The trace method supports indented tracing.
4
- # Set the TRACE environment variable to enable it. Search the code to find the TRACE keywords, or use "all".
5
3
  #
6
4
  # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
7
5
  #
@@ -7,7 +7,7 @@
7
7
  module ActiveFacts
8
8
  module Version
9
9
  MAJOR = 1
10
- MINOR = 1
10
+ MINOR = 2
11
11
  PATCH = 0
12
12
 
13
13
  STRING = [MAJOR, MINOR, PATCH].compact.join('.')
@@ -265,6 +265,13 @@ module ActiveFacts
265
265
  def subtypes_transitive
266
266
  [self] + subtypes.map{|st| st.subtypes_transitive}.flatten
267
267
  end
268
+
269
+ def common_supertype(other)
270
+ return nil unless other.is_?(ActiveFacts::Metamodel::ValueType)
271
+ return self if other.supertypes_transitive.include?(self)
272
+ return other if supertypes_transitive.include(other)
273
+ nil
274
+ end
268
275
  end
269
276
 
270
277
  class EntityType
@@ -478,6 +485,13 @@ module ActiveFacts
478
485
  return nil
479
486
  end
480
487
 
488
+ def common_supertype(other)
489
+ return nil unless other.is_?(ActiveFacts::Metamodel::EntityType)
490
+ candidates = supertypes_transitive & other.supertypes_transitive
491
+ return candidates[0] if candidates.size <= 1
492
+ candidates[0] # REVISIT: This might not be the closest supertype
493
+ end
494
+
481
495
  # This entity type has just objectified a fact type. Create the necessary ImplicitFactTypes with phantom roles
482
496
  def create_implicit_fact_types
483
497
  fact_type.all_role.map do |role|
@@ -722,23 +736,26 @@ module ActiveFacts
722
736
  "#{is_optional ? 'maybe ' : ''}" +
723
737
  (is_unary_step ? '(unary) ' : "from #{input_play.describe} ") +
724
738
  "#{is_disallowed ? 'not ' : ''}" +
725
- "to #{output_play.describe} " +
726
- "over " +
727
- (is_objectification_step ? 'objectification ' : '') +
728
- "'#{fact_type.default_reading}'"
739
+ "to #{output_plays.map(&:describe)*', '}" +
740
+ (objectification_variable ? ", objectified as #{objectification_variable.describe}" : '') +
741
+ " '#{fact_type.default_reading}'"
742
+ end
743
+
744
+ def input_play
745
+ all_play.detect{|p| p.is_input}
746
+ end
747
+
748
+ def output_plays
749
+ all_play.reject{|p| p.is_input}
729
750
  end
730
751
 
731
752
  def is_unary_step
732
753
  # Preserve this in case we have to use a real variable for the phantom
733
- input_play == output_play
754
+ all_play.size == 1
734
755
  end
735
756
 
736
757
  def is_objectification_step
737
- fact_type.is_a?(LinkFactType)
738
- end
739
-
740
- def all_play
741
- [input_play, output_play].uniq + all_incidental_play.to_a
758
+ !!objectification_variable
742
759
  end
743
760
 
744
761
  def external_fact_type
@@ -750,38 +767,27 @@ module ActiveFacts
750
767
  def describe
751
768
  object_type.name +
752
769
  (subscript ? "(#{subscript})" : '') +
753
- " JN#{ordinal}" +
770
+ " Var#{ordinal}" +
754
771
  (value ? ' = '+value.to_s : '')
755
772
  end
756
773
 
757
774
  def all_step
758
- all_play.map do |jr|
759
- jr.all_step_as_input_play.to_a +
760
- jr.all_step_as_output_play.to_a
761
- end.
762
- flatten.
763
- uniq
775
+ all_play.map(&:step).uniq
764
776
  end
765
777
  end
766
778
 
767
779
  class Play
768
780
  def describe
769
- "#{role.object_type.name} JN#{variable.ordinal}" +
781
+ "#{role.object_type.name} Var#{variable.ordinal}" +
770
782
  (role_ref ? " (projected)" : "")
771
783
  end
772
-
773
- def all_step
774
- (all_step_as_input_play.to_a +
775
- all_step_as_output_play.to_a +
776
- [step]).flatten.compact.uniq
777
- end
778
784
  end
779
785
 
780
786
  class Query
781
787
  def show
782
788
  steps_shown = {}
783
789
  trace :query, "Displaying full contents of Query #{concept.guid}" do
784
- all_variable.sort_by{|jn| jn.ordinal}.each do |variable|
790
+ all_variable.sort_by{|var| var.ordinal}.each do |variable|
785
791
  trace :query, "#{variable.describe}" do
786
792
  variable.all_step.
787
793
  each do |step|
@@ -798,7 +804,7 @@ module ActiveFacts
798
804
  end
799
805
 
800
806
  def all_step
801
- all_variable.map{|jn| jn.all_step.to_a}.flatten.uniq
807
+ all_variable.map{|var| var.all_step.to_a}.flatten.uniq
802
808
  end
803
809
 
804
810
  # Check all parts of this query for validity
@@ -808,7 +814,7 @@ module ActiveFacts
808
814
 
809
815
  # Check the variables:
810
816
  steps = []
811
- variables = all_variable.sort_by{|jn| jn.ordinal}
817
+ variables = all_variable.sort_by{|var| var.ordinal}
812
818
  variables.each_with_index do |variable, i|
813
819
  raise "Variable #{i} should have ordinal #{variable.ordinal}" unless variable.ordinal == i
814
820
  raise "Variable #{i} has missing object_type" unless variable.object_type
@@ -1025,22 +1031,38 @@ module ActiveFacts
1025
1031
  def verbalise(context = nil)
1026
1032
  return "#{object_type.name} #{value}" if object_type.is_a?(ValueType)
1027
1033
 
1028
- return "#{object_type.name} where #{fact.verbalise(context)}" if object_type.fact_type
1034
+ return "#{object_type.name} (in which #{fact.verbalise(context)})" if object_type.fact_type
1029
1035
 
1030
1036
  # It's an entity that's not an objectified fact type
1031
- # REVISIT: If it has a simple identifier, there's no need to fully verbalise the identifying facts
1032
- pi = object_type.preferred_identifier
1033
- identifying_role_refs = pi.role_sequence.all_role_ref_in_order
1034
- "#{object_type.name}" +
1035
- " is identified by " + # REVISIT: Where the single fact type is TypeInheritance, we can shrink this
1036
- if identifying_role_refs.size == 1 &&
1037
- (role = identifying_role_refs[0].role) &&
1038
- (my_role = (role.fact_type.all_role.to_a-[role])[0]) &&
1039
- (identifying_fact = my_role.all_role_value.detect{|rv| rv.instance == self}.fact) &&
1040
- (identifying_instance = identifying_fact.all_role_value.detect{|rv| rv.role == role}.instance)
1041
-
1042
- "its #{identifying_instance.verbalise(context)}"
1043
- else
1037
+
1038
+ # If it has a simple identifier, there's no need to fully verbalise the identifying facts.
1039
+ # This recursive block returns either the identifying value or nil
1040
+ simple_identifier = proc do |instance|
1041
+ if instance.object_type.is_a?(ActiveFacts::Metamodel::ValueType)
1042
+ instance
1043
+ else
1044
+ pi = instance.object_type.preferred_identifier
1045
+ identifying_role_refs = pi.role_sequence.all_role_ref_in_order
1046
+ if identifying_role_refs.size != 1
1047
+ nil
1048
+ else
1049
+ role = identifying_role_refs[0].role
1050
+ my_role = (role.fact_type.all_role.to_a-[role])[0]
1051
+ identifying_fact = my_role.all_role_value.detect{|rv| rv.instance == self}.fact
1052
+ irv = identifying_fact.all_role_value.detect{|rv| rv.role == role}
1053
+ identifying_instance = irv.instance
1054
+ simple_identifier.call(identifying_instance)
1055
+ end
1056
+ end
1057
+ end
1058
+
1059
+ if (id = simple_identifier.call(self))
1060
+ "#{object_type.name} #{id.value}"
1061
+ else
1062
+ pi = object_type.preferred_identifier
1063
+ identifying_role_refs = pi.role_sequence.all_role_ref_in_order
1064
+ "#{object_type.name}" +
1065
+ " is identified by " + # REVISIT: Where the single fact type is TypeInheritance, we can shrink this
1044
1066
  identifying_role_refs.map do |rr|
1045
1067
  rr = rr.preferred_reference
1046
1068
  [ (l = rr.leading_adjective) ? l+"-" : nil,
@@ -1056,7 +1078,7 @@ module ActiveFacts
1056
1078
  #identifying_instance = counterpart_role.all_role_value.detect{|rv| rv.fact == identifying_fact}.instance
1057
1079
  identifying_fact.verbalise(context)
1058
1080
  end*", "
1059
- end
1081
+ end
1060
1082
 
1061
1083
  end
1062
1084
  end
@@ -66,6 +66,7 @@ module ActiveFacts
66
66
  one_to_one :concept # See Concept.guid
67
67
  one_to_one :role_sequence # See RoleSequence.guid
68
68
  one_to_one :shape # See Shape.guid
69
+ one_to_one :step # See Step.guid
69
70
  end
70
71
 
71
72
  class ImplicationRuleName < String
@@ -292,6 +293,15 @@ module ActiveFacts
292
293
  has_one :orm_diagram, :class => "ORMDiagram", :mandatory => true # See ORMDiagram.all_shape
293
294
  end
294
295
 
296
+ class Step
297
+ identified_by :guid
298
+ has_one :alternative_set # See AlternativeSet.all_step
299
+ has_one :fact_type, :mandatory => true # See FactType.all_step
300
+ one_to_one :guid, :mandatory => true # See Guid.step
301
+ maybe :is_disallowed
302
+ maybe :is_optional
303
+ end
304
+
295
305
  class SubsetConstraint < SetConstraint
296
306
  has_one :subset_role_sequence, :class => RoleSequence, :mandatory => true # See RoleSequence.all_subset_constraint_as_subset_role_sequence
297
307
  has_one :superset_role_sequence, :class => RoleSequence, :mandatory => true # See RoleSequence.all_subset_constraint_as_superset_role_sequence
@@ -329,6 +339,7 @@ module ActiveFacts
329
339
  one_to_one :projection, :class => Role # See Role.variable_as_projection
330
340
  has_one :query, :mandatory => true # See Query.all_variable
331
341
  has_one :role_name, :class => Name # See Name.all_variable_as_role_name
342
+ one_to_one :step, :counterpart => :objectification_variable # See Step.objectification_variable
332
343
  has_one :subscript # See Subscript.all_variable
333
344
  has_one :value # See Value.all_variable
334
345
  end
@@ -419,10 +430,11 @@ module ActiveFacts
419
430
  end
420
431
 
421
432
  class Play
422
- identified_by :variable, :role
433
+ identified_by :step, :role
423
434
  has_one :role, :mandatory => true # See Role.all_play
435
+ has_one :step, :mandatory => true # See Step.all_play
424
436
  has_one :variable, :mandatory => true # See Variable.all_play
425
- has_one :step, :counterpart => :incidental_play # See Step.all_incidental_play
437
+ maybe :is_input
426
438
  end
427
439
 
428
440
  class Population
@@ -479,16 +491,6 @@ module ActiveFacts
479
491
  maybe :is_mandatory
480
492
  end
481
493
 
482
- class Step
483
- identified_by :input_play, :output_play
484
- has_one :alternative_set # See AlternativeSet.all_step
485
- has_one :fact_type, :mandatory => true # See FactType.all_step
486
- has_one :input_play, :class => Play, :mandatory => true # See Play.all_step_as_input_play
487
- maybe :is_disallowed
488
- maybe :is_optional
489
- has_one :output_play, :class => Play # See Play.all_step_as_output_play
490
- end
491
-
492
494
  class ValueConstraintShape < ConstraintShape
493
495
  has_one :object_type_shape # See ObjectTypeShape.all_value_constraint_shape
494
496
  one_to_one :role_display # See RoleDisplay.value_constraint_shape
@@ -163,8 +163,8 @@ module ActiveFacts
163
163
  # Add a RoleRef to an existing Player
164
164
  def add_role_player player, role_ref
165
165
  #trace :subscript, "Adding role_ref #{role_ref.object_id} to player #{player.object_id}"
166
- if jr = role_ref.play
167
- add_play(player, jr)
166
+ if play = role_ref.play
167
+ add_play(player, play)
168
168
  elsif !player.role_refs.include?(role_ref)
169
169
  trace :subscript, "Adding reference to player #{player.object_id} for #{role_ref.role.object_type.name} in #{role_ref.role_sequence.describe} with #{role_ref.role_sequence.all_reading.size} readings"
170
170
  player.role_refs.push(role_ref)
@@ -216,12 +216,11 @@ module ActiveFacts
216
216
  return if plays.empty?
217
217
 
218
218
  # If any of these plays are for a known player, use that, else make a new player.
219
- existing_players = plays.map{|jr| @player_by_play[jr] }.compact.uniq
219
+ existing_players = plays.map{|play| @player_by_play[play] }.compact.uniq
220
220
  if existing_players.size > 1
221
221
  raise "At most one existing player can play these roles: #{existing_players.map{|p|p.object_type.name}*', '}!"
222
222
  end
223
223
  p = existing_players[0] || player(plays[0])
224
- debugger if plays.detect{|jr| jr.role.object_type != p.object_type }
225
224
  trace :subscript, "roles are playes of #{p.describe}" do
226
225
  plays.each do |play|
227
226
  trace :subscript, "#{play.describe}" do
@@ -281,22 +280,22 @@ module ActiveFacts
281
280
  trace :subscript, "Applying subscripts to #{dups.size} occurrences of #{object_type.name}" do
282
281
  s = 0
283
282
  dups.
284
- sort_by{|p| # Guarantee stable numbering
283
+ sort_by do |p| # Guarantee stable numbering
285
284
  p.role_adjuncts(:role_name) + ' ' +
286
285
  # Tie-breaker:
287
286
  p.role_refs.map{|rr| rr.role.fact_type.preferred_reading.text}.sort.to_s
288
- }.
287
+ end.
289
288
  each do |player|
290
- jrname = player.plays.map{|jr| jr.role_ref && jr.role_ref.role.role_name}.compact[0]
291
- rname = (rr = player.role_refs[0]) && rr.role.role_name
292
- if jrname and !rname
293
- # puts "Oops: rolename #{rname.inspect} != #{jrname.inspect}" if jrname != rname
294
- player.variables_by_query.values.each{|jn| jn.role_name = jrname }
295
- else
296
- player.subscript = s+1
297
- s += 1
298
- end
299
- end
289
+ jrname = player.plays.map{|play| play.role_ref && play.role_ref.role.role_name}.compact[0]
290
+ rname = (rr = player.role_refs[0]) && rr.role.role_name
291
+ if jrname and !rname
292
+ # puts "Oops: rolename #{rname.inspect} != #{jrname.inspect}" if jrname != rname
293
+ player.variables_by_query.values.each{|jn| jn.role_name = jrname }
294
+ else
295
+ player.subscript = s+1
296
+ s += 1
297
+ end
298
+ end
300
299
  end
301
300
  end
302
301
  end
@@ -364,7 +363,7 @@ module ActiveFacts
364
363
  trace :subscript, "Indexing roles of fact types in #{query.all_step.size} steps" do
365
364
  steps = []
366
365
  # Register all references to each variable as being for the same player:
367
- query.all_variable.sort_by{|jn| jn.ordinal}.each do |variable|
366
+ query.all_variable.to_a.sort_by(&:ordinal).each do |variable|
368
367
  trace :subscript, "Adding Roles of #{variable.describe}" do
369
368
  plays_have_same_player(variable.all_play.to_a)
370
369
  steps = steps | variable.all_step
@@ -445,13 +444,12 @@ module ActiveFacts
445
444
  # Expand this reading (or partial reading, during contraction)
446
445
  def expand_reading_text(step, text, role_sequence, player_by_role = {})
447
446
  if !player_by_role.empty? and !player_by_role.is_a?(Hash) || player_by_role.keys.detect{|k| !k.is_a?(ActiveFacts::Metamodel::Role)}
448
- debugger
449
447
  raise "Need to change this call to expand_reading_text to pass a role->variable hash"
450
448
  end
451
449
  rrs = role_sequence.all_role_ref_in_order
452
450
  variable_by_role = {}
453
451
  if step
454
- plays = ([step.input_play, step.output_play]+step.all_incidental_play.to_a).compact
452
+ plays = step.all_play
455
453
  variable_by_role = plays.inject({}) { |h, play| h[play.role] = play.variable; h }
456
454
  end
457
455
  trace :subscript, "expanding '#{text}' with #{role_sequence.describe}" do
@@ -462,8 +460,9 @@ module ActiveFacts
462
460
  variable = variable_by_role[role_ref.role]
463
461
 
464
462
  play_name = variable && variable.role_name
463
+ raise hell if player && player.is_a?(ActiveFacts::Metamodel::EntityType) && player.fact_type && !variable
465
464
  subscripted_player(role_ref, player && player.subscript, play_name, variable && variable.value) +
466
- objectification_verbalisation(role_ref.role.object_type)
465
+ objectification_verbalisation(variable)
467
466
  end
468
467
  end
469
468
  end
@@ -495,40 +494,35 @@ module ActiveFacts
495
494
  @query = query
496
495
  return unless query
497
496
 
498
- @variables = query.all_variable.sort_by{|jn| jn.ordinal}
497
+ @variables = query.all_variable.to_a.sort_by(&:ordinal)
499
498
 
500
- @steps = @variables.map{|jn| jn.all_step }.flatten.uniq
499
+ @steps = @variables.map(&:all_step).flatten.uniq
501
500
  @steps_by_variable = @variables.
502
- inject({}) do |h, jn|
503
- jn.all_step.each{|js| (h[jn] ||= []) << js}
501
+ inject({}) do |h, var|
502
+ var.all_step.each{|step| (h[var] ||= []) << step}
504
503
  h
505
504
  end
506
505
  end
507
506
 
508
- # Remove this step now that we've processed it:
507
+ # De-index this step now that we've processed it:
509
508
  def step_completed(step)
510
509
  @steps.delete(step)
511
510
 
512
- input_node = step.input_play.variable
513
- steps = @steps_by_variable[input_node]
514
- steps.delete(step)
515
- @steps_by_variable.delete(input_node) if steps.empty?
516
-
517
- output_node = step.output_play.variable
518
- if (input_node != output_node)
519
- steps = @steps_by_variable[output_node]
511
+ step.all_play.each do |play|
512
+ var = play.variable
513
+ steps = @steps_by_variable[var]
520
514
  steps.delete(step)
521
- @steps_by_variable.delete(output_node) if steps.empty?
515
+ @steps_by_variable.delete(var) if steps.empty?
522
516
  end
523
517
  end
524
518
 
525
- def choose_step(next_node)
526
- next_steps = @steps_by_variable[next_node]
519
+ def choose_step(next_var)
520
+ next_steps = @steps_by_variable[next_var]
527
521
 
528
522
  # We need to emit each objectification before mentioning an object that plays a role in one, if possible
529
523
  # so that we don't wind up with an objectification as the only way to mention its name.
530
524
 
531
- # If we don't have a next_node against which we can contract,
525
+ # If we don't have a next_var against which we can contract,
532
526
  # so just use any step involving this node, or just any step.
533
527
  if next_steps
534
528
  if next_step = next_steps.detect { |ns| !ns.is_objectification_step }
@@ -558,26 +552,26 @@ module ActiveFacts
558
552
  end
559
553
 
560
554
  # The step we just emitted (using the reading given) is contractable iff
561
- # the reading has the next_node's role player as the final text
562
- def node_contractable_against_reading(next_node, reading)
555
+ # the reading has the next_var's role player as the final text
556
+ def node_contractable_against_reading(next_var, reading)
563
557
  reading &&
564
558
  # Find whether last role has no following text, and its ordinal
565
559
  (reading.text =~ /\{([0-9])\}$/) &&
566
560
  # This reading's RoleRef for that role:
567
561
  (role_ref = reading.role_sequence.all_role_ref_in_order[$1.to_i]) &&
568
562
  # was that RoleRef for the upcoming node?
569
- role_ref.role.object_type == next_node.object_type
563
+ role_ref.role.object_type == next_var.object_type
570
564
  end
571
565
 
572
- def reading_starts_with_node(reading, next_node)
566
+ def reading_starts_with_node(reading, next_var)
573
567
  reading.text =~ /^\{([0-9])\}/ and
574
568
  role_ref = reading.role_sequence.all_role_ref.detect{|rr| rr.ordinal == $1.to_i} and
575
- role_ref.role.object_type == next_node.object_type
569
+ role_ref.role.object_type == next_var.object_type
576
570
  end
577
571
 
578
- # The last reading we emitted ended with the object type name for next_node.
572
+ # The last reading we emitted ended with the object type name for next_var.
579
573
  # Choose a step and a reading that can be contracted against that name
580
- def contractable_step(next_steps, next_node)
574
+ def contractable_step(next_steps, next_var)
581
575
  next_reading = nil
582
576
  next_step =
583
577
  next_steps.detect do |js|
@@ -585,51 +579,47 @@ module ActiveFacts
585
579
  # If we find a reading here, it can be contracted against the previous one
586
580
  next_reading =
587
581
  js.fact_type.all_reading_by_ordinal.detect do |reading|
588
- # This step is contractable iff the FactType has a reading that starts with the role of next_node (no preceding text)
589
- reading_starts_with_node(reading, next_node)
582
+ # This step is contractable iff the FactType has a reading that starts with the role of next_var (no preceding text)
583
+ reading_starts_with_node(reading, next_var)
590
584
  end
591
585
  next_reading
592
586
  end
593
- trace :query, "#{next_reading ? "'"+next_reading.expand+"'" : "No reading"} contracts against last node '#{next_node.object_type.name}'"
587
+ trace :query, "#{next_reading ? "'"+next_reading.expand+"'" : "No reading"} contracts against last node '#{next_var.object_type.name}'"
594
588
  return [next_step, next_reading]
595
589
  end
596
590
 
597
- # REVISIT: There might be more than one objectification_verbalisation for a given object_type. Need to get the Variable here and emit an objectification step involving that node.
598
- def objectification_verbalisation(object_type)
591
+ def objectification_verbalisation(variable)
592
+ return '' unless variable
593
+ raise "Not fully re-implemented, should pass the variable instead of #{variable.inspect}" unless variable.is_a?(ActiveFacts::Metamodel::Variable)
599
594
  objectified_node = nil
600
- unless object_type.is_a?(Metamodel::EntityType) and
601
- object_type.fact_type and # Not objectified
602
- objectification_step = @steps.
603
- detect do |js|
604
- # The objectifying entity type should always be the input_variable here, but be safe:
605
- js.is_objectification_step and
606
- (objectified_node = js.input_play.variable).object_type == object_type ||
607
- (objectified_node = js.output_play.variable).object_type == object_type
608
- end
609
- return ''
610
- end
595
+ object_type = variable.object_type
596
+ return '' unless object_type.is_a?(Metamodel::EntityType) # Not a entity type
597
+ return '' unless object_type.fact_type # Not objectified
611
598
 
612
- # REVISIT: We need to be working from the role_ref here - pass it in
613
- # if objectification_step.variable != role_ref.variable
599
+ objectification_step = variable.step
600
+ return '' unless objectification_step
614
601
 
615
602
  steps = [objectification_step]
616
603
  step_completed(objectification_step)
604
+
605
+ =begin
617
606
  while other_step =
618
607
  @steps.
619
- detect{|js|
620
- js.is_objectification_step and
621
- js.input_play.variable.object_type == object_type || js.output_play.variable.object_type == object_type
608
+ detect{|step|
609
+ step.is_objectification_step and
610
+ step.input_play.variable.object_type == object_type || step.output_play.variable.object_type == object_type
622
611
  }
623
612
  steps << other_step
624
613
  trace :query, "Emitting objectification step allows deleting #{other_step.describe}"
625
614
  step_completed(other_step)
626
615
  end
616
+ =end
627
617
 
628
618
  # Find all references to roles in this objectified fact type which are relevant to the variables of these steps:
629
619
  player_by_role = {}
630
620
  steps.each do |step|
631
- step.all_play.to_a.map do |jr|
632
- player_by_role[jr.role] = @player_by_play[jr]
621
+ step.all_play.to_a.map do |play|
622
+ player_by_role[play.role] = @player_by_play[play]
633
623
  end
634
624
  end
635
625
 
@@ -639,14 +629,14 @@ module ActiveFacts
639
629
  " (in which #{expand_reading_text(objectification_step, reading.text, reading.role_sequence, player_by_role)})"
640
630
  end
641
631
 
642
- def elided_objectification(next_step, fact_type, last_is_contractable, next_node)
632
+ def elided_objectification(next_step, fact_type, last_is_contractable, next_var)
643
633
  if last_is_contractable
644
634
  # Choose a reading that's contractable against the previous step, if possible
645
635
  reading = fact_type.all_reading_by_ordinal.
646
636
  detect do |reading|
647
637
  # Only contract a negative reading if we want one
648
638
  (!next_step.is_disallowed || !reading.is_negative == !next_step.is_disallowed) and
649
- reading_starts_with_node(reading, next_node)
639
+ reading_starts_with_node(reading, next_var)
650
640
  end
651
641
  end
652
642
  last_is_contractable = false unless reading
@@ -655,7 +645,7 @@ module ActiveFacts
655
645
  # Find which role occurs last in the reading, and which Variable is attached
656
646
  reading.text =~ /\{(\d)\}[^{]*\Z/
657
647
  last_role_ref = reading.role_sequence.all_role_ref_in_order[$1.to_i]
658
- exit_node = @variables.detect{|jn| jn.all_play.detect{|jr| jr.role == last_role_ref.role}}
648
+ exit_node = @variables.detect{|jn| jn.all_play.detect{|play| play.role == last_role_ref.role}}
659
649
  exit_step = nil
660
650
 
661
651
  trace :query, "Stepping over an objectification to #{exit_node.object_type.name} requires eliding the other implied steps" do
@@ -683,35 +673,49 @@ module ActiveFacts
683
673
  def verbalise_query query
684
674
  prepare_query query
685
675
  readings = ''
686
- next_node = @role_refs[0].play.variable # Choose a place to start
676
+ next_var = @role_refs[0].play.variable # Choose a place to start
687
677
  last_is_contractable = false
688
- trace :query, "Variables are #{@variables.map{|jn| jn.describe }.inspect}, Steps are #{@steps.map{|js| js.describe }.inspect}" do
678
+
679
+ trace :query, "Verbalising query" do
680
+ if trace(:query)
681
+ trace :query, "variables:" do
682
+ @variables.each do |var|
683
+ trace :query, var.describe
684
+ end
685
+ end
686
+ trace :query, "steps:" do
687
+ @steps.each do |step|
688
+ trace :query, step.describe
689
+ end
690
+ end
691
+ end
692
+
689
693
  until @steps.empty?
690
694
  next_reading = nil
691
- # Choose amonst all remaining steps we can take from the next node, if any
692
- next_steps = @steps_by_variable[next_node]
693
- trace :query, "Next Steps from #{next_node.describe} are #{(next_steps||[]).map{|js| js.describe }.inspect}"
695
+ # Choose amongst all remaining steps we can take from the next node, if any
696
+ next_steps = @steps_by_variable[next_var]
697
+ trace :query, "Next Steps from #{next_var.describe} are #{(next_steps||[]).map{|js| js.describe }.inspect}"
694
698
 
695
699
  # See if we can find a next step that contracts against the last (if any):
696
700
  next_step = nil
697
701
  if last_is_contractable && next_steps
698
- next_step, next_reading = *contractable_step(next_steps, next_node)
702
+ next_step, next_reading = *contractable_step(next_steps, next_var)
699
703
  end
700
704
 
701
705
  if next_step
702
- trace :query, "Chose #{next_step.describe} because it's contractable against last node #{next_node.object_type.name} using #{next_reading.expand}"
706
+ trace :query, "Chose #{next_step.describe} because it's contractable against last node #{next_var.object_type.name} using #{next_reading.expand}"
703
707
 
704
708
  player_by_role =
705
- next_step.all_play.inject({}) {|h, jr| h[jr.role] = @player_by_play[jr]; h }
709
+ next_step.all_play.inject({}) {|h, play| h[play.role] = @player_by_play[play]; h }
706
710
  raise "REVISIT: Needed a negated reading here" if !next_reading.is_negative != !next_step.is_disallowed
707
711
  raise "REVISIT: Need to emit 'maybe' here" if next_step.is_optional
708
712
  readings += expand_contracted_text(next_step, next_reading, player_by_role)
709
713
  step_completed(next_step)
710
714
  else
711
- next_step = choose_step(next_node) if !next_step
715
+ next_step = choose_step(next_var) if !next_step
712
716
 
713
717
  player_by_role =
714
- next_step.all_play.inject({}) {|h, jr| h[jr.role] = @player_by_play[jr]; h }
718
+ next_step.all_play.inject({}) {|h, play| h[play.role] = @player_by_play[play]; h }
715
719
 
716
720
  if next_step.is_unary_step
717
721
  # Objectified unaries get emitted as unaries, not as objectifications:
@@ -731,20 +735,20 @@ module ActiveFacts
731
735
  objectified_node = next_step.input_play.variable
732
736
  raise "Assumption violated that the objectification is the input play" unless objectified_node.object_type.fact_type
733
737
  objectified_node.all_step.map do |other_step|
734
- (other_step.all_incidental_play.to_a + [other_step.output_play]).map do |jr|
735
- player_by_role[jr.role] = @player_by_play[jr]
738
+ other_step.all_play.map do |play|
739
+ player_by_role[play.role] = @player_by_play[play]
736
740
  end
737
741
  end
738
742
 
739
- if last_is_contractable and next_node.object_type.is_a?(EntityType) and next_node.object_type.fact_type == fact_type
743
+ if last_is_contractable and next_var.object_type.is_a?(EntityType) and next_var.object_type.fact_type == fact_type
740
744
  # The last reading we emitted ended with the name of the objectification of this fact type, so we can contract the objectification
741
745
  # REVISIT: Do we need to use player_by_role here (if this objectification is traversed twice and so is subscripted)
742
746
  readings += objectification_verbalisation(fact_type.entity_type)
743
747
  else
744
748
  # This objectified fact type does not need to be made explicit.
745
749
  negation = next_step.is_disallowed
746
- next_reading, next_node, next_step, last_is_contractable =
747
- *elided_objectification(next_step, fact_type, last_is_contractable, next_node)
750
+ next_reading, next_var, next_step, last_is_contractable =
751
+ *elided_objectification(next_step, fact_type, last_is_contractable, next_var)
748
752
  if last_is_contractable
749
753
  raise "REVISIT: Need to emit 'maybe' here" if next_step and next_step.is_optional
750
754
  readings += expand_contracted_text(next_step, next_reading, player_by_role)
@@ -760,11 +764,11 @@ module ActiveFacts
760
764
  end
761
765
  else
762
766
  fact_type = next_step.fact_type
763
- # Prefer a reading that starts with the player of next_node
767
+ # Prefer a reading that starts with the player of next_var
764
768
  next_reading = fact_type.all_reading_by_ordinal.
765
769
  detect do |reading|
766
770
  (!next_step.is_disallowed || !reading.is_negative == !next_step.is_disallowed) and
767
- reading_starts_with_node(reading, next_node)
771
+ reading_starts_with_node(reading, next_var)
768
772
  end || fact_type.preferred_reading(next_step.is_disallowed)
769
773
  # REVISIT: If this step and reading has role references with adjectives, we need to expand using those
770
774
  readings += " and " unless readings.empty?
@@ -777,14 +781,16 @@ module ActiveFacts
777
781
 
778
782
  if next_step
779
783
  # Continue from this step with the node having the most steps remaining
780
- input_steps = @steps_by_variable[input_node = next_step.input_play.variable] || []
781
- output_steps = @steps_by_variable[output_node = next_step.output_play.variable] || []
782
- next_node = input_steps.size > output_steps.size ? input_node : output_node
784
+ input_steps = @steps_by_variable[input_var = next_step.input_play.variable] || []
785
+ output_play = next_step.output_plays.last
786
+ output_steps = (output_play && (output_var = output_play.variable) && @steps_by_variable[output_var]) || []
787
+
788
+ next_var = input_steps.size > output_steps.size ? input_var : output_var
783
789
  # Prepare for possible contraction following:
784
- last_is_contractable = next_reading && node_contractable_against_reading(next_node, next_reading)
790
+ last_is_contractable = next_reading && node_contractable_against_reading(next_var, next_reading)
785
791
  else
786
792
  # This shouldn't happen, but an elided objectification that had missing steps can cause it. Survive:
787
- next_node = (steps[0].input_play || steps[0].output_play).variable
793
+ next_var = (steps[0].input_play || steps[0].output_plays.last).variable
788
794
  last_is_contractable = false
789
795
  end
790
796