activefacts 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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