activefacts 0.8.10 → 0.8.12

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 (52) hide show
  1. data/Rakefile +3 -2
  2. data/bin/afgen +25 -23
  3. data/bin/cql +9 -8
  4. data/css/orm2.css +23 -3
  5. data/examples/CQL/CompanyDirectorEmployee.cql +1 -1
  6. data/examples/CQL/Diplomacy.cql +3 -3
  7. data/examples/CQL/Insurance.cql +27 -21
  8. data/examples/CQL/Metamodel.cql +12 -8
  9. data/examples/CQL/MetamodelNext.cql +172 -149
  10. data/examples/CQL/ServiceDirector.cql +17 -17
  11. data/examples/CQL/Supervision.cql +3 -5
  12. data/examples/CQL/WaiterTips.cql +1 -1
  13. data/examples/CQL/unit.cql +1 -1
  14. data/index.html +0 -0
  15. data/lib/activefacts/cql/FactTypes.treetop +41 -8
  16. data/lib/activefacts/cql/Language/English.treetop +10 -0
  17. data/lib/activefacts/cql/ObjectTypes.treetop +3 -1
  18. data/lib/activefacts/cql/Terms.treetop +34 -53
  19. data/lib/activefacts/cql/compiler.rb +1 -1
  20. data/lib/activefacts/cql/compiler/clause.rb +21 -8
  21. data/lib/activefacts/cql/compiler/constraint.rb +3 -1
  22. data/lib/activefacts/cql/compiler/entity_type.rb +1 -1
  23. data/lib/activefacts/cql/compiler/fact_type.rb +9 -3
  24. data/lib/activefacts/cql/compiler/join.rb +3 -0
  25. data/lib/activefacts/cql/compiler/value_type.rb +9 -4
  26. data/lib/activefacts/cql/parser.rb +11 -3
  27. data/lib/activefacts/generate/oo.rb +3 -3
  28. data/lib/activefacts/generate/ordered.rb +0 -4
  29. data/lib/activefacts/input/orm.rb +305 -250
  30. data/lib/activefacts/persistence/tables.rb +6 -0
  31. data/lib/activefacts/support.rb +18 -0
  32. data/lib/activefacts/version.rb +1 -1
  33. data/lib/activefacts/vocabulary/extensions.rb +59 -20
  34. data/lib/activefacts/vocabulary/metamodel.rb +23 -13
  35. data/lib/activefacts/vocabulary/verbaliser.rb +5 -3
  36. data/spec/absorption_spec.rb +3 -2
  37. data/spec/cql/comparison_spec.rb +1 -3
  38. data/spec/cql/context_spec.rb +1 -1
  39. data/spec/cql/entity_type_spec.rb +2 -2
  40. data/spec/cql/expressions_spec.rb +2 -4
  41. data/spec/cql/fact_type_matching_spec.rb +55 -3
  42. data/spec/cql/parser/fact_types_spec.rb +3 -0
  43. data/spec/cql/role_matching_spec.rb +8 -7
  44. data/spec/cql/samples_spec.rb +10 -2
  45. data/spec/cql_dm_spec.rb +2 -1
  46. data/spec/helpers/array_matcher.rb +18 -35
  47. data/spec/helpers/diff_matcher.rb +34 -13
  48. data/spec/helpers/file_matcher.rb +27 -43
  49. data/spec/helpers/string_matcher.rb +23 -33
  50. data/spec/norma_cql_spec.rb +1 -0
  51. data/spec/norma_tables_spec.rb +1 -2
  52. metadata +95 -102
@@ -237,6 +237,12 @@ module ActiveFacts
237
237
  }
238
238
  debug :absorption, "#{object_type.name} has #{non_identifying_refs_from.size} non-identifying functional roles"
239
239
 
240
+ # If all non-identifying functional roles are one-to-ones that can be flipped, do that:
241
+ if non_identifying_refs_from.all? { |ref| ref.role_type == :one_one && (ref.to.is_table || ref.to.tentative) }
242
+ non_identifying_refs_from.each { |ref| ref.flip }
243
+ non_identifying_refs_from = []
244
+ end
245
+
240
246
  if object_type.references_to.size > 1 and
241
247
  non_identifying_refs_from.size > 0
242
248
  debug :absorption, "#{object_type.name} has non-identifying functional dependencies so 3NF requires it be a table"
@@ -150,3 +150,21 @@ class Array
150
150
  self
151
151
  end
152
152
  end
153
+
154
+ # Load the ruby debugger before everything else, if requested
155
+ if debug :debug
156
+ begin
157
+ require 'ruby-debug'
158
+ Debugger.start # (:post_mortem => true) # Some Ruby versions crash on post-mortem debugging
159
+ rescue LoadError
160
+ # Ok, no debugger, tough luck.
161
+ end
162
+
163
+ if debug :trap
164
+ trap('SIGINT') do
165
+ puts "Stopped at:\n\t"+caller*"\n\t"
166
+ debugger
167
+ true
168
+ end
169
+ end
170
+ end
@@ -5,5 +5,5 @@
5
5
  # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
6
  #
7
7
  module ActiveFacts
8
- VERSION = '0.8.10'
8
+ VERSION = '0.8.12'
9
9
  end
@@ -53,6 +53,10 @@ module ActiveFacts
53
53
  reading.text =~ /\{\d\}/ and reading.role_sequence.all_role_ref_in_order[$1.to_i].role == role
54
54
  end || preferred_reading
55
55
  end
56
+
57
+ def all_role_in_order
58
+ all_role.sort_by{|r| r.ordinal}
59
+ end
56
60
  end
57
61
 
58
62
  class Role
@@ -179,7 +183,7 @@ module ActiveFacts
179
183
 
180
184
  class EntityType
181
185
  def preferred_identifier
182
- #return @preferred_identifier if @preferred_identifier
186
+ return @preferred_identifier if @preferred_identifier
183
187
  if fact_type
184
188
 
185
189
  # For a nested fact type, the PI is a unique constraint over N or N-1 roles
@@ -390,39 +394,43 @@ module ActiveFacts
390
394
  (0...role_refs.size).each{|i|
391
395
  role_ref = role_refs[i]
392
396
  role = role_ref.role
393
- la = "#{role_ref.leading_adjective}".sub(/(.\b|.\Z)/, '\1-').sub(/- /,'- ')
394
- la = nil if la == ""
397
+ l_adj = "#{role_ref.leading_adjective}".sub(/(\b-\b|.\b|.\Z)/, '\1-').sub(/\b--\b/,'-- ').sub(/- /,'- ')
398
+ l_adj = nil if l_adj == ""
395
399
  # Double the space to compensate for space removed below
396
- ta = "#{role_ref.trailing_adjective}".sub(/(\b.|\A.)/, '-\1').sub(/ -/,' -')
397
- ta = nil if ta == ""
400
+ # REVISIT: hyphenated trailing adjectives are not correctly represented here
401
+ t_adj = "#{role_ref.trailing_adjective}".sub(/(\b.|\A.)/, '-\1').sub(/ -/,' -')
402
+ t_adj = nil if t_adj == ""
398
403
 
399
- expanded.gsub!(/\{#{i}\}/) {
404
+ expanded.gsub!(/\{#{i}\}/) do
400
405
  role_ref = role_refs[i]
401
406
  player = role_ref.role.object_type
402
407
  role_name = role.role_name
403
408
  role_name = nil if role_name == ""
404
409
  if role_name && define_role_names == false
405
- la = ta = nil # When using role names, don't add adjectives
410
+ l_adj = t_adj = nil # When using role names, don't add adjectives
406
411
  end
407
- fc = frequency_constraints[i]
408
- fc = fc.frequency if fc && fc.is_a?(ActiveFacts::Metamodel::PresenceConstraint)
409
- if fc.is_a?(Array)
410
- fc, player_name = *fc
412
+ freq_con = frequency_constraints[i]
413
+ freq_con = freq_con.frequency if freq_con && freq_con.is_a?(ActiveFacts::Metamodel::PresenceConstraint)
414
+ if freq_con.is_a?(Array)
415
+ freq_con, player_name = *freq_con
411
416
  else
412
417
  player_name = player.name
413
418
  end
414
419
  literal = literals[i]
415
- [
416
- fc ? fc : nil,
417
- la,
420
+ words = [
421
+ freq_con ? freq_con : nil,
422
+ l_adj,
418
423
  define_role_names == false && role_name ? role_name : player_name,
419
- ta,
424
+ t_adj,
420
425
  define_role_names && role_name && player.name != role_name ? "(as #{role_name})" : nil,
421
426
  # Can't have both a literal and a value constraint, but we don't enforce that here:
422
427
  literal ? literal : nil
423
- ].compact*" " +
424
- (subscript_block ? subscript_block.call(role_ref) : "")
425
- }
428
+ ]
429
+ if (subscript_block)
430
+ words = subscript_block.call(role_ref, *words)
431
+ end
432
+ words.compact*" "
433
+ end
426
434
  }
427
435
  expanded.gsub!(/ ?- ?/, '-') # Remove single spaces around adjectives
428
436
  #debug "Expanded '#{expanded}' using #{frequency_constraints.inspect}"
@@ -441,6 +449,37 @@ module ActiveFacts
441
449
  end
442
450
  end
443
451
  end
452
+
453
+ # Return the array of the numbers of the RoleRefs inserted into this reading from the role_sequence
454
+ def role_numbers
455
+ text.scan(/\{(\d)\}/).flatten.map{|m| Integer(m) }
456
+ end
457
+
458
+ def expand_with_final_presence_constraint &b
459
+ # Arrange the roles in order they occur in this reading:
460
+ role_refs = role_sequence.all_role_ref_in_order
461
+ role_numbers = text.scan(/\{(\d)\}/).flatten.map{|m| Integer(m) }
462
+ roles = role_numbers.map{|m| role_refs[m].role }
463
+ fact_constraints = fact_type.internal_presence_constraints
464
+
465
+ # Find the constraints that constrain frequency over each role we can verbalise:
466
+ frequency_constraints = []
467
+ roles.each do |role|
468
+ frequency_constraints <<
469
+ if (role == roles.last) # On the last role of the reading, emit any presence constraint
470
+ constraint = fact_constraints.
471
+ detect do |c| # Find a UC that spans all other Roles
472
+ c.is_a?(ActiveFacts::Metamodel::PresenceConstraint) &&
473
+ roles-c.role_sequence.all_role_ref.map(&:role) == [role]
474
+ end
475
+ constraint && constraint.frequency
476
+ else
477
+ nil
478
+ end
479
+ end
480
+
481
+ expand(frequency_constraints) { |*a| b && b.call(*a) }
482
+ end
444
483
  end
445
484
 
446
485
  class ValueConstraint
@@ -482,7 +521,7 @@ module ActiveFacts
482
521
  min_literal = min.literal
483
522
  end
484
523
  else
485
- min_literal = infinity ? "INFINITY" : ""
524
+ min_literal = infinity ? "-Infinity" : ""
486
525
  end
487
526
  if max = value_range.maximum_bound
488
527
  max = max.value
@@ -492,7 +531,7 @@ module ActiveFacts
492
531
  max_literal = max.literal
493
532
  end
494
533
  else
495
- max_literal = infinity ? "INFINITY" : ""
534
+ max_literal = infinity ? "Infinity" : ""
496
535
  end
497
536
 
498
537
  min_literal +
@@ -41,6 +41,10 @@ module ActiveFacts
41
41
  value_type
42
42
  end
43
43
 
44
+ class DisjunctionId < AutoCounter
45
+ value_type
46
+ end
47
+
44
48
  class DisplayRoleNamesSetting < String
45
49
  value_type
46
50
  restrict 'false', 'true'
@@ -182,6 +186,11 @@ module ActiveFacts
182
186
  has_one :object_type # See ObjectType.all_context_note
183
187
  end
184
188
 
189
+ class Disjunction
190
+ identified_by :disjunction_id
191
+ one_to_one :disjunction_id, :mandatory => true # See DisjunctionId.disjunction
192
+ end
193
+
185
194
  class Enforcement
186
195
  identified_by :constraint
187
196
  has_one :agent # See Agent.all_enforcement
@@ -300,9 +309,9 @@ module ActiveFacts
300
309
  has_one :coefficient # See Coefficient.all_unit
301
310
  has_one :ephemera_url, :class => EphemeraURL # See EphemeraURL.all_unit
302
311
  maybe :is_fundamental
303
- has_one :name, :mandatory => true # See Name.all_unit
312
+ one_to_one :name, :mandatory => true # See Name.unit
304
313
  has_one :offset # See Offset.all_unit
305
- has_one :plural_name, :class => Name # See Name.all_unit_as_plural_name
314
+ one_to_one :plural_name, :class => Name # See Name.unit_as_plural_name
306
315
  one_to_one :unit_id, :mandatory => true # See UnitId.unit
307
316
  has_one :vocabulary, :mandatory => true # See Vocabulary.all_unit
308
317
  end
@@ -380,6 +389,7 @@ module ActiveFacts
380
389
 
381
390
  class JoinStep
382
391
  identified_by :input_join_role, :output_join_role
392
+ has_one :disjunction # See Disjunction.all_join_step
383
393
  has_one :fact_type, :mandatory => true # See FactType.all_join_step
384
394
  has_one :input_join_role, :class => JoinRole, :mandatory => true # See JoinRole.all_join_step_as_input_join_role
385
395
  maybe :is_anti
@@ -400,7 +410,6 @@ module ActiveFacts
400
410
  end
401
411
 
402
412
  class ObjectTypeShape < Shape
403
- maybe :has_expanded_reference_mode
404
413
  has_one :object_type, :mandatory => true # See ObjectType.all_object_type_shape
405
414
  end
406
415
 
@@ -491,9 +500,7 @@ module ActiveFacts
491
500
 
492
501
  class EntityType < ObjectType
493
502
  one_to_one :fact_type # See FactType.entity_type
494
- end
495
-
496
- class ImplicitBooleanValueType < ValueType
503
+ maybe :is_implied_by_objectification
497
504
  end
498
505
 
499
506
  class Facet
@@ -502,6 +509,16 @@ module ActiveFacts
502
509
  has_one :value_type, :mandatory => true # See ValueType.all_facet
503
510
  end
504
511
 
512
+ class FacetValue
513
+ identified_by :value_type, :facet
514
+ has_one :facet, :mandatory => true # See Facet.all_facet_value
515
+ has_one :value, :mandatory => true # See Value.all_facet_value
516
+ has_one :value_type, :mandatory => true # See ValueType.all_facet_value
517
+ end
518
+
519
+ class ImplicitBooleanValueType < ValueType
520
+ end
521
+
505
522
  class TypeInheritance < FactType
506
523
  identified_by :subtype, :supertype
507
524
  has_one :subtype, :class => EntityType, :mandatory => true # See EntityType.all_type_inheritance_as_subtype
@@ -510,12 +527,5 @@ module ActiveFacts
510
527
  maybe :provides_identification
511
528
  end
512
529
 
513
- class FacetValue
514
- identified_by :value_type, :facet
515
- has_one :facet, :mandatory => true # See Facet.all_facet_value
516
- has_one :value, :mandatory => true # See Value.all_facet_value
517
- has_one :value_type, :mandatory => true # See ValueType.all_facet_value
518
- end
519
-
520
530
  end
521
531
  end
@@ -305,8 +305,10 @@ module ActiveFacts
305
305
  # these expansions include frequency constraints, role names and value constraints as passed-in,
306
306
  # and also define adjectives by using the hyphenated form (on at least the first occurrence).
307
307
  def expand_reading(reading, frequency_constraints = [], define_role_names = nil, value_constraints = [], &subscript_block)
308
- reading.expand(frequency_constraints, define_role_names, value_constraints) do |role_ref|
309
- (!(role_ref.role.role_name and define_role_names != nil) and p = player(role_ref) and p.subscript) ? "(#{p.subscript})" : ""
308
+ reading.expand(frequency_constraints, define_role_names, value_constraints) do |role_ref, *parts|
309
+ parts + [
310
+ (!(role_ref.role.role_name and define_role_names != nil) and p = player(role_ref) and p.subscript) ? "(#{p.subscript})" : nil
311
+ ]
310
312
  end
311
313
  end
312
314
 
@@ -626,7 +628,7 @@ module ActiveFacts
626
628
  # role_refs = steps.map{|step| [step.input_join_role.join_node, step.output_join_role.join_node].map{|jn| jn.all_role_ref.detect{|rr| rr.role.fact_type == object_type.fact_type}}}.flatten.compact.uniq
627
629
 
628
630
  reading = object_type.fact_type.preferred_reading
629
- " (where #{expand_reading_text(objectification_step, reading.text, reading.role_sequence, player_by_role)})"
631
+ " (in which #{expand_reading_text(objectification_step, reading.text, reading.role_sequence, player_by_role)})"
630
632
  end
631
633
 
632
634
  def elided_objectification(next_step, fact_type, last_is_contractable, next_node)
@@ -21,6 +21,7 @@ describe "Absorption" do
21
21
  Claim is identified by ClaimID where
22
22
  Claim has exactly one ClaimID,
23
23
  ClaimID is of at most one Claim;
24
+ Claim was reported on one DateTime;
24
25
  }
25
26
  AT_Incident = %Q{
26
27
  Incident is identified by Claim where
@@ -50,7 +51,7 @@ describe "Absorption" do
50
51
  #{AT_Prologue} #{AT_Claim} #{AT_Incident}
51
52
  Incident relates to loss on exactly one DateTime;
52
53
  },
53
- :tables => { "Claim" => [%w{Claim ID}, %w{Incident Date Time}]}
54
+ :tables => { "Claim" => [%w{Claim ID}, %w{Date Time}, %w{Incident Date Time}]}
54
55
  },
55
56
 
56
57
  { :should => "absorb an objectified binary with single-role UC",
@@ -62,7 +63,7 @@ describe "Absorption" do
62
63
  Person has exactly one birth-DateTime;
63
64
  },
64
65
  :tables => {
65
- "Claim" => [%w{Claim ID}, %w{Lodgement Date Time}, %w{Lodgement Person ID}],
66
+ "Claim" => [%w{Claim ID}, %w{Date Time}, %w{Lodgement Date Time}, %w{Lodgement Person ID}],
66
67
  "Party" => [%w{Party ID}, %w{Person Birth Date Time}]
67
68
  }
68
69
  },
@@ -8,9 +8,7 @@ require 'rspec/expectations'
8
8
  require 'activefacts/support'
9
9
  require 'activefacts/api/support'
10
10
  require 'activefacts/cql/compiler'
11
- require 'spec/helpers/compile_helpers'
12
-
13
- require 'ruby-debug'; Debugger.start
11
+ require File.dirname(__FILE__) + '/../helpers/compile_helpers'
14
12
 
15
13
  describe "When matching a reading with an existing fact type" do
16
14
  before :each do
@@ -41,7 +41,7 @@ describe "Business Context Notes" do
41
41
  ],
42
42
  # Entity and Fact types
43
43
  # Entity and Fact types
44
- [ 'Foo is identified by Bar [independent] where Foo has one Bar (so that we have an id);',
44
+ [ 'Foo is identified by Bar [independent] where Foo has one(to avoid fuckups) Bar (so that we have an id);',
45
45
  1, 'so_that'
46
46
  ],
47
47
  [ 'Baz has one Bar (so that we have an id), Bar is of one Baz (because we need that);',
@@ -137,7 +137,7 @@ describe "When compiling an entity type, " do
137
137
  eval(lambda { example.call(*c) }, BlackHole.new)
138
138
  rescue => raised
139
139
  end
140
- raise RSpec::Core::PendingExampleFixedError.new(msg) unless raised
140
+ raise RSpec::Core::Pending::PendingExampleFixedError.new(msg) unless raised
141
141
  throw :pending_declared_in_example, msg
142
142
  }
143
143
  end
@@ -306,7 +306,7 @@ describe "When compiling an entity type, " do
306
306
  test.call(@compiler.vocabulary.constellation)
307
307
  rescue RSpec::Expectations::ExpectationNotMetError
308
308
  raise
309
- rescue RSpec::Core::ExamplePendingError
309
+ rescue RSpec::Core::Pending::PendingDeclaredInExample.new
310
310
  raise
311
311
  rescue => e
312
312
  puts "Failed on\n\t"+tests.select{|t| t.is_a?(String)}*" "
@@ -8,9 +8,7 @@ require 'rspec/expectations'
8
8
  require 'activefacts/support'
9
9
  require 'activefacts/api/support'
10
10
  require 'activefacts/cql/compiler'
11
- require 'spec/helpers/compile_helpers'
12
-
13
- require 'ruby-debug'; Debugger.start
11
+ require File.dirname(__FILE__) + '/../helpers/compile_helpers'
14
12
 
15
13
  describe "When compiling expressions" do
16
14
  before :each do
@@ -55,7 +53,7 @@ describe "When compiling expressions" do
55
53
  (is_old_ft.all_reading.map{ |r| r.expand }*', ').should == "Person is old"
56
54
 
57
55
  comparison_ft = (new_fact_types - [is_old_ft])[0]
58
- (comparison_ft.all_reading.map{ |r| r.expand }*', ').should == "Age >= product(Integer, sum(Integer, Integer))"
56
+ (comparison_ft.all_reading.map{ |r| r.expand }*', ').should == "Boolean = Age >= product(Integer, sum(Integer, Integer))"
59
57
 
60
58
  # one_join_with_value 60, 'year'
61
59
  end
@@ -2,15 +2,14 @@
2
2
  # ActiveFacts CQL Fact Type matching tests
3
3
  # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
4
4
  #
5
+ $: << Dir::getwd
5
6
 
6
7
  require 'rspec/expectations'
7
8
 
8
9
  require 'activefacts/support'
9
10
  require 'activefacts/api/support'
10
11
  require 'activefacts/cql/compiler'
11
- require 'spec/helpers/compile_helpers'
12
-
13
- require 'ruby-debug'; Debugger.start
12
+ require File.dirname(__FILE__) + '/../helpers/compile_helpers'
14
13
 
15
14
  describe "When matching a reading" do
16
15
  before :each do
@@ -279,5 +278,58 @@ describe "When matching a reading" do
279
278
  (pcs = fact_pcs(fact_type)).size.should == 1
280
279
  end
281
280
  end
281
+
282
+ describe "with hyphenated adjectives" do
283
+ before :each do
284
+ compile %q{Girl is going out with at most one butt-- ugly Boy;}
285
+ # baseline
286
+ end
287
+
288
+ it "should compile them correctly" do
289
+ (new_fact_types = fact_types).size.should == 1
290
+ (readings = new_fact_types[0].all_reading).size.should == 1
291
+ (role_refs = readings.single.role_sequence.all_role_ref).size.should == 2
292
+ (boy_role_ref = role_refs.sort_by{|rr| rr.ordinal}[1])
293
+ boy_role_ref.leading_adjective.should == 'butt-ugly'
294
+ end
295
+
296
+ it "should match using explicit adjectives" do
297
+ compile %q{
298
+ Girl is going out with butt-- ugly Boy,
299
+ butt-ugly Boy is going out with Girl;
300
+ }
301
+ (new_fact_types = fact_types).size.should == 1
302
+ (fact_type = new_fact_types[0]).all_reading.size.should == 2
303
+ (pcs = fact_pcs(fact_type)).size.should == 1
304
+ # REVISIT: Check new and existing reading
305
+ end
306
+
307
+ it "should match using implicit adjectives" do
308
+ compile %q{
309
+ Girl is going out with butt-- ugly Boy,
310
+ butt-ugly Boy is going out with Girl;
311
+ }
312
+ (new_fact_types = fact_types).size.should == 1
313
+ (fact_type = new_fact_types[0]).all_reading.size.should == 2
314
+ (pcs = fact_pcs(fact_type)).size.should == 1
315
+ # REVISIT: Check new and existing reading
316
+ end
317
+ end
318
+
319
+ describe "with hyphenated trailing adjectives" do
320
+ before :each do
321
+ compile %q{Girl is going out with at most one Boy tres --gross;}
322
+ # baseline
323
+ end
324
+
325
+ it "should compile them correctly" do
326
+ (new_fact_types = fact_types).size.should == 1
327
+ (readings = new_fact_types[0].all_reading).size.should == 1
328
+ (role_refs = readings.single.role_sequence.all_role_ref).size.should == 2
329
+ (boy_role_ref = role_refs.sort_by{|rr| rr.ordinal}[1])
330
+ boy_role_ref.trailing_adjective.should == 'tres-gross'
331
+ end
332
+
333
+ end
282
334
  end
283
335
  end