activefacts 0.8.10 → 0.8.12

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