activefacts 0.7.3 → 0.8.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. data/LICENSE +19 -0
  2. data/Manifest.txt +24 -2
  3. data/Rakefile +25 -3
  4. data/bin/afgen +1 -1
  5. data/bin/cql +13 -2
  6. data/css/offline.css +3 -0
  7. data/css/orm2.css +24 -0
  8. data/css/print.css +8 -0
  9. data/css/style-print.css +357 -0
  10. data/css/style.css +387 -0
  11. data/download.html +85 -0
  12. data/examples/CQL/Address.cql +3 -3
  13. data/examples/CQL/Blog.cql +13 -14
  14. data/examples/CQL/CompanyDirectorEmployee.cql +4 -4
  15. data/examples/CQL/Death.cql +3 -2
  16. data/examples/CQL/Genealogy.cql +13 -11
  17. data/examples/CQL/Marriage.cql +2 -2
  18. data/examples/CQL/Metamodel.cql +136 -93
  19. data/examples/CQL/MultiInheritance.cql +2 -2
  20. data/examples/CQL/OilSupply.cql +14 -10
  21. data/examples/CQL/Orienteering.cql +22 -19
  22. data/examples/CQL/PersonPlaysGame.cql +3 -2
  23. data/examples/CQL/SchoolActivities.cql +4 -2
  24. data/examples/CQL/SimplestUnary.cql +1 -1
  25. data/examples/CQL/SubtypePI.cql +6 -7
  26. data/examples/CQL/Warehousing.cql +16 -19
  27. data/examples/CQL/unit.cql +584 -0
  28. data/examples/index.html +276 -0
  29. data/examples/intro.html +497 -0
  30. data/examples/local.css +20 -0
  31. data/index.html +96 -0
  32. data/lib/activefacts/api/concept.rb +48 -46
  33. data/lib/activefacts/api/constellation.rb +43 -23
  34. data/lib/activefacts/api/entity.rb +2 -2
  35. data/lib/activefacts/api/instance.rb +6 -2
  36. data/lib/activefacts/api/instance_index.rb +5 -0
  37. data/lib/activefacts/api/value.rb +8 -2
  38. data/lib/activefacts/api/vocabulary.rb +15 -10
  39. data/lib/activefacts/cql/CQLParser.treetop +109 -88
  40. data/lib/activefacts/cql/Concepts.treetop +32 -10
  41. data/lib/activefacts/cql/Context.treetop +34 -0
  42. data/lib/activefacts/cql/Expressions.treetop +9 -9
  43. data/lib/activefacts/cql/FactTypes.treetop +30 -31
  44. data/lib/activefacts/cql/Language/English.treetop +50 -0
  45. data/lib/activefacts/cql/LexicalRules.treetop +2 -1
  46. data/lib/activefacts/cql/Terms.treetop +117 -0
  47. data/lib/activefacts/cql/ValueTypes.treetop +152 -0
  48. data/lib/activefacts/cql/compiler.rb +1718 -0
  49. data/lib/activefacts/cql/parser.rb +124 -57
  50. data/lib/activefacts/generate/absorption.rb +1 -1
  51. data/lib/activefacts/generate/cql.rb +111 -100
  52. data/lib/activefacts/generate/cql/html.rb +5 -5
  53. data/lib/activefacts/generate/oo.rb +3 -3
  54. data/lib/activefacts/generate/ordered.rb +51 -19
  55. data/lib/activefacts/generate/ruby.rb +10 -8
  56. data/lib/activefacts/generate/sql/mysql.rb +14 -10
  57. data/lib/activefacts/generate/sql/server.rb +29 -24
  58. data/lib/activefacts/input/cql.rb +9 -1264
  59. data/lib/activefacts/input/orm.rb +213 -200
  60. data/lib/activefacts/persistence/columns.rb +11 -10
  61. data/lib/activefacts/persistence/index.rb +15 -18
  62. data/lib/activefacts/persistence/reference.rb +17 -17
  63. data/lib/activefacts/persistence/tables.rb +50 -51
  64. data/lib/activefacts/version.rb +1 -1
  65. data/lib/activefacts/vocabulary/extensions.rb +79 -8
  66. data/lib/activefacts/vocabulary/metamodel.rb +183 -114
  67. data/spec/absorption_ruby_spec.rb +99 -0
  68. data/spec/absorption_spec.rb +3 -4
  69. data/spec/api/constellation.rb +1 -1
  70. data/spec/api/entity_type.rb +3 -1
  71. data/spec/api/instance.rb +4 -2
  72. data/spec/api/roles.rb +8 -6
  73. data/spec/api_spec.rb +1 -2
  74. data/spec/cql/context_spec.rb +71 -0
  75. data/spec/cql/samples_spec.rb +154 -0
  76. data/spec/cql/unit_spec.rb +375 -0
  77. data/spec/cql_cql_spec.rb +31 -21
  78. data/spec/cql_mysql_spec.rb +70 -0
  79. data/spec/cql_parse_spec.rb +15 -9
  80. data/spec/cql_ruby_spec.rb +27 -13
  81. data/spec/cql_sql_spec.rb +42 -16
  82. data/spec/cql_symbol_tables_spec.rb +2 -3
  83. data/spec/cqldump_spec.rb +7 -7
  84. data/spec/helpers/file_matcher.rb +39 -0
  85. data/spec/norma_cql_spec.rb +20 -12
  86. data/spec/norma_ruby_spec.rb +6 -3
  87. data/spec/norma_sql_spec.rb +6 -3
  88. data/spec/norma_tables_spec.rb +6 -4
  89. data/spec/spec_helper.rb +27 -8
  90. data/status.html +69 -0
  91. data/why.html +60 -0
  92. metadata +34 -11
  93. data/lib/activefacts/cql/DataTypes.treetop +0 -81
  94. data/spec/cql_unit_spec.rb +0 -330
@@ -0,0 +1,99 @@
1
+ #
2
+ # ActiveFacts tests: Compare column lists created by aborption and by generated Ruby.
3
+ # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
4
+ #
5
+
6
+ require 'stringio'
7
+ require 'activefacts/vocabulary'
8
+ require 'activefacts/support'
9
+ require 'activefacts/input/orm'
10
+ require 'activefacts/persistence'
11
+ require 'activefacts/generate/ruby'
12
+
13
+ include ActiveFacts
14
+
15
+ describe "Column lists from absorption compared with Ruby's" do
16
+ ABSORPTION_RUBY_FAILURES = {
17
+ "Metamodel" => "Overlaps with ActiveFacts Metamodel",
18
+ "MetamodelTerms" => "Overlaps with ActiveFacts Metamodel",
19
+ "ServiceDirector" => "Lacks standard AutoTimestamp class"
20
+ }
21
+
22
+ # Generate and return the Ruby for the given vocabulary
23
+ def ruby(vocabulary)
24
+ output = StringIO.new
25
+ @dumper = ActiveFacts::Generate::RUBY.new(vocabulary.constellation, "sql")
26
+ @dumper.generate(output)
27
+ output.rewind
28
+ output.read
29
+ end
30
+
31
+ pattern = ENV["AFTESTS"] || "*"
32
+ Dir["examples/norma/#{pattern}.orm"].each do |orm_file|
33
+ expected_file = orm_file.sub(%r{examples/norma/(.*).orm\Z}, 'examples/ruby/\1.rb')
34
+ actual_file = orm_file.sub(%r{examples/norma/(.*).orm\Z}, 'spec/actual/\1.rb')
35
+
36
+ it "should load #{orm_file} and generate relational composition and Ruby with matching column names" do
37
+ vocabulary = ActiveFacts::Input::ORM.readfile(orm_file)
38
+
39
+ # Get the list of tables from the relational composition:
40
+ absorption_tables = vocabulary.tables.sort_by(&:name)
41
+ absorption_table_names = absorption_tables.map{|at| at.name}
42
+
43
+ # Build the Ruby and eval it:
44
+ ruby_text = ruby(vocabulary)
45
+ File.open(actual_file, "w") { |f| f.write ruby_text }
46
+
47
+ broken = ABSORPTION_RUBY_FAILURES[File.basename(orm_file, ".orm")]
48
+ eval_it = lambda { Object.send :eval, ruby_text }
49
+ if broken
50
+ pending(broken) {
51
+ lambda {
52
+ eval_it.call
53
+ }.should_not raise_error
54
+ }
55
+ else
56
+ lambda {
57
+ eval_it.call
58
+ }.should_not raise_error
59
+ end
60
+
61
+ # Get a list of table classes in the new module, sorted by name
62
+ mod = eval(vocabulary.name)
63
+ ruby_tables = mod.constants.map{|n|
64
+ c = mod.const_get(n)
65
+ c.class == Class && c.is_table ? c : nil
66
+ }.compact.sort_by{|c|
67
+ c.basename
68
+ }
69
+ ruby_table_names = ruby_tables.map{|c| c.basename}
70
+
71
+ # Assert that the list of tables is the same:
72
+ tables = lambda { ruby_table_names.should == absorption_table_names }
73
+ if broken
74
+ pending { tables.call }
75
+ else
76
+ tables.call
77
+ end
78
+
79
+ # So we get to see the full differences, figure them here and assert them to be empty:
80
+ diffs = {}
81
+ ruby_tables.each{|rt|
82
+ next unless rt.is_entity_type
83
+ absorption_table = absorption_tables.select{|at| at.name == rt.basename}[0]
84
+ absorption_columns = absorption_table.columns.map{|c| c.name("").downcase}.sort
85
+ ruby_columns = rt.columns.map{|c| c.gsub(/\./,'').downcase}.sort
86
+ missing = absorption_columns - ruby_columns
87
+ extra = ruby_columns - absorption_columns
88
+ unless missing.empty? and extra.empty?
89
+ diffs[rt.basename] = missing.map{|m| "-"+m} + extra.map{|e| '+'+e}
90
+ end
91
+ }
92
+ diffs.should == {}
93
+
94
+ # Clean up:
95
+ Object.send :remove_const, vocabulary.name.to_sym
96
+ File.delete(actual_file) # It succeeded, we don't need the file.
97
+ end
98
+ end
99
+ end
@@ -2,8 +2,7 @@
2
2
  # ActiveFacts tests: Test the relational absorption by compiling CQL fragments.
3
3
  # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
4
4
  #
5
- require 'rubygems'
6
- require 'treetop'
5
+
7
6
  require 'activefacts/support'
8
7
  require 'activefacts/api/support'
9
8
  require 'activefacts/input/cql'
@@ -75,8 +74,8 @@ describe "Absorption" do
75
74
  cql = test[:cql]
76
75
  expected_tables = test[:tables]
77
76
  it "should #{should}" do
78
- @compiler = ActiveFacts::Input::CQL.new(cql, should)
79
- @vocabulary = @compiler.read
77
+ @compiler = ActiveFacts::CQL::Compiler.new(cql, should)
78
+ @vocabulary = @compiler.vocabulary
80
79
 
81
80
  # puts cql
82
81
 
@@ -50,7 +50,7 @@ describe "A Constellation instance" do
50
50
  identified_by :name, :family_name # REVISIT: want a way to role_alias :name, :given_name
51
51
  supertypes SurrogateId
52
52
 
53
- has_one :family_name, :Name
53
+ has_one :family_name, :class => Name
54
54
  end
55
55
  end
56
56
  @constellation = ActiveFacts::API::Constellation.new(Mod)
@@ -2,6 +2,8 @@
2
2
  # ActiveFacts tests: Entity classes in the Runtime API
3
3
  # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
4
4
  #
5
+ require 'activefacts/api'
6
+
5
7
  describe "Entity Type class definitions" do
6
8
  before :each do
7
9
  Object.send :remove_const, :Mod if Object.const_defined?("Mod")
@@ -13,7 +15,7 @@ describe "Entity Type class definitions" do
13
15
  end
14
16
  class Person < LegalEntity
15
17
  identified_by :name
16
- has_one :name, Name
18
+ has_one :name, :class => Name
17
19
  end
18
20
  end
19
21
  end
data/spec/api/instance.rb CHANGED
@@ -2,6 +2,8 @@
2
2
  # ActiveFacts tests: Value instances in the Runtime API
3
3
  # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
4
4
  #
5
+ require 'activefacts/api'
6
+
5
7
  describe "An instance of every type of Concept" do
6
8
  before :each do
7
9
  Object.send :remove_const, :Mod if Object.const_defined?("Mod")
@@ -45,7 +47,7 @@ describe "An instance of every type of Concept" do
45
47
  @role_names.map do |role_name|
46
48
  %Q{
47
49
  has_one :#{role_name}
48
- one_to_one :one_#{role_name}, #{role_name.to_s.camelcase(true)}}
50
+ one_to_one :one_#{role_name}, :class => #{role_name.to_s.camelcase(true)}}
49
51
  end*""
50
52
  }
51
53
  end
@@ -55,7 +57,7 @@ describe "An instance of every type of Concept" do
55
57
  @role_names.map do |role_name|
56
58
  %Q{
57
59
  has_one :#{role_name}
58
- one_to_one :one_#{role_name}, #{role_name.to_s.camelcase(true)}}
60
+ one_to_one :one_#{role_name}, :class => #{role_name.to_s.camelcase(true)}}
59
61
  end*""
60
62
  }
61
63
  end
data/spec/api/roles.rb CHANGED
@@ -2,6 +2,8 @@
2
2
  # ActiveFacts tests: Roles of concept classes in the Runtime API
3
3
  # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
4
4
  #
5
+ require 'activefacts/api'
6
+
5
7
  describe "Roles" do
6
8
  before :each do
7
9
  Object.send :remove_const, :Mod if Object.const_defined?("Mod")
@@ -15,16 +17,16 @@ describe "Roles" do
15
17
  end
16
18
  class Contract
17
19
  identified_by :first, :second
18
- has_one :first, LegalEntity
19
- has_one :second, LegalEntity
20
+ has_one :first, :class => LegalEntity
21
+ has_one :second, :class => LegalEntity
20
22
  end
21
23
  class Person < LegalEntity
22
24
  # identified_by # No identifier needed, inherit from superclass
23
25
  # New identifier:
24
26
  identified_by :family, :given
25
- has_one :family, Name
26
- has_one :given, Name
27
- has_one :related_to, LegalEntity
27
+ has_one :family, :class => Name
28
+ has_one :given, :class => Name
29
+ has_one :related_to, :class => LegalEntity
28
30
  end
29
31
  end
30
32
  # print "concept: "; p Mod.concept
@@ -79,7 +81,7 @@ describe "Roles" do
79
81
  module Mod
80
82
  class FamilyName < Name
81
83
  value_type
82
- one_to_one :patriarch, Person
84
+ one_to_one :patriarch, :class => Person
83
85
  end
84
86
  end
85
87
  r = Mod::FamilyName.roles(:patriarch)
data/spec/api_spec.rb CHANGED
@@ -2,10 +2,9 @@
2
2
  # ActiveFacts tests: Run the API tests
3
3
  # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
4
4
  #
5
- base = File.dirname(__FILE__)+"/"
6
- require 'rubygems'
7
5
  require 'activefacts/api'
8
6
 
7
+ base = File.dirname(__FILE__)+"/"
9
8
  require base+'api/constellation'
10
9
  require base+'api/entity_type'
11
10
  require base+'api/instance'
@@ -0,0 +1,71 @@
1
+ #
2
+ # ActiveFacts CQL Business Context Note tests
3
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
4
+ #
5
+
6
+ require 'activefacts/support'
7
+ require 'activefacts/api/support'
8
+ require 'activefacts/cql/parser'
9
+
10
+ describe "Business Context Notes" do
11
+ # (according_to people ',')? (because / as_opposed_to / so_that / to_avoid) discussion (',' as_agreed_by)? s
12
+ Prefix = %q{
13
+ Person is written as Person;
14
+ }
15
+ Notes = [
16
+ # Constraints:
17
+ [ 'each Person occurs one time in Person is employed, Person is unemployed (because it can be no other way!);',
18
+ [["Person", [:value_type, "Person", [], [], [], []]], [nil, [:constraint, :presence, [["Person"]], [1, 1], [[[{:word=>"Person"}, {:word=>"is"}, {:word=>"employed"}]], [[{:word=>"Person"}, {:word=>"is"}, {:word=>"unemployed"}]]], [nil, "because", "it can be no other way!", []]]]]
19
+ ],
20
+ [ 'each Person occurs one time in Person is employed, Person is unemployed (as opposed to blah!);',
21
+ [["Person", [:value_type, "Person", [], [], [], []]], [nil, [:constraint, :presence, [["Person"]], [1, 1], [[[{:word=>"Person"}, {:word=>"is"}, {:word=>"employed"}]], [[{:word=>"Person"}, {:word=>"is"}, {:word=>"unemployed"}]]], [nil, "as_opposed_to", " blah!", []]]]]
22
+ ],
23
+ [ 'for each Person at least one of these holds: Person is employed, Person is a bad credit risk (so that blah);',
24
+ [["Person", [:value_type, "Person", [], [], [], []]], [nil, [:constraint, :set, [["Person"]], [1, nil], [[[{:word=>"Person"}, {:word=>"is"}, {:word=>"employed"}]], [[{:word=>"Person"}, {:word=>"is"}, {:word=>"a"}, {:word=>"bad"}, {:word=>"credit"}, {:word=>"risk"}]]], [nil, "so_that", " blah", []]]]]
25
+ ],
26
+ [ 'Person is good credit risk only if Person is employed (to avoid lending to people who can\'t repay);',
27
+ [["Person", [:value_type, "Person", [], [], [], []]], [nil, [:constraint, :subset, [[[{:word=>"Person"}, {:word=>"is"}, {:word=>"good"}, {:word=>"credit"}, {:word=>"risk"}]], [[{:word=>"Person"}, {:word=>"is"}, {:word=>"employed"}]]], [nil, "to_avoid", " lending to people who can't repay", []]]]]
28
+ ],
29
+ [ 'Person is good credit risk if and only if Person is employed (to avoid lending to people who can\'t repay);',
30
+ [["Person", [:value_type, "Person", [], [], [], []]], [nil, [:constraint, :equality, [[[{:word=>"Person"}, {:word=>"is"}, {:word=>"good"}, {:word=>"credit"}, {:word=>"risk"}]], [[{:word=>"Person"}, {:word=>"is"}, {:word=>"employed"}]]], [nil, "to_avoid", " lending to people who can't repay", []]]]]
31
+ ],
32
+ # Entity and Fact types
33
+ [ 'Foo is identified by Bar [independent] where Foo has one Bar (so that we have an id);',
34
+ [["Person", [:value_type, "Person", [], [], [], []]], ["Foo", [:entity_type, [], {:roles=>[["Bar"]]}, ["independent"], [[:fact_clause, [], [{:word=>"Foo"}, {:word=>"has"}, {:word=>"Bar", :quantifier=>[1, 1]}], [nil, "so_that", " we have an id", []]]]]]]
35
+ ],
36
+ [ 'Foo has one Bar (so that we have an id), Bar is of one Foo (because we need that);',
37
+ [["Person", [:value_type, "Person", [], [], [], []]], [nil, [:fact_type, [[:fact_clause, [], [{:word=>"Foo"}, {:word=>"has"}, {:word=>"Bar", :quantifier=>[1, 1]}], [nil, "so_that", " we have an id", []]], [:fact_clause, [], [{:word=>"Bar"}, {:word=>"is"}, {:word=>"of"}, {:word=>"Foo", :quantifier=>[1, 1]}], [nil, "because", "we need that", []]]], []]]]
38
+ ],
39
+ # REVISIT: No context notes on quantifiers yet
40
+ # As agreed by:
41
+ [ 'for each Person at least one of these holds: Person is employed, Person is a bad credit risk (so that blah, as agreed by Jim);',
42
+ [["Person", [:value_type, "Person", [], [], [], []]], [nil, [:constraint, :set, [["Person"]], [1, nil], [[[{:word=>"Person"}, {:word=>"is"}, {:word=>"employed"}]], [[{:word=>"Person"}, {:word=>"is"}, {:word=>"a"}, {:word=>"bad"}, {:word=>"credit"}, {:word=>"risk"}]]], [nil, "so_that", " blah", [nil, ["Jim"]]]]]]
43
+ ],
44
+ # REVISIT: Populate an "as agreed by" with a date
45
+ [ 'for each Person at least one of these holds: Person is employed, Person is a bad credit risk (so that blah, as agreed on 29 March by Jim);',
46
+ [["Person", [:value_type, "Person", [], [], [], []]], [nil, [:constraint, :set, [["Person"]], [1, nil], [[[{:word=>"Person"}, {:word=>"is"}, {:word=>"employed"}]], [[{:word=>"Person"}, {:word=>"is"}, {:word=>"a"}, {:word=>"bad"}, {:word=>"credit"}, {:word=>"risk"}]]], [nil, "so_that", " blah", ["29 March", ["Jim"]]]]]]
47
+ ],
48
+ # According to:
49
+ [ 'for each Person at least one of these holds: Person is employed, Person is a bad credit risk (according to jim, so that blah);',
50
+ [["Person", [:value_type, "Person", [], [], [], []]], [nil, [:constraint, :set, [["Person"]], [1, nil], [[[{:word=>"Person"}, {:word=>"is"}, {:word=>"employed"}]], [[{:word=>"Person"}, {:word=>"is"}, {:word=>"a"}, {:word=>"bad"}, {:word=>"credit"}, {:word=>"risk"}]]], [["jim"], "so_that", " blah", []]]]]
51
+ ],
52
+ ]
53
+
54
+ before :each do
55
+ @parser = ActiveFacts::CQL::Parser.new
56
+ end
57
+
58
+ Notes.each do |c|
59
+ source, ast = *c
60
+ it "should parse #{source.inspect}" do
61
+ #debugger
62
+ result = @parser.parse_all(Prefix+source, :definition)
63
+
64
+ puts @parser.failure_reason unless result
65
+ result.should_not be_nil
66
+ result.map{|d| d.value}.should == ast if ast
67
+ # Uncomment this to see what should replace "nil" in the cases above:
68
+ #puts result.map{|d| d.value}.inspect unless ast
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,154 @@
1
+ #
2
+ # ActiveFacts tests: Test the CQL parser by looking at its parse trees.
3
+ # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
4
+ #
5
+
6
+ require 'activefacts/support'
7
+ require 'activefacts/api/support'
8
+ require 'activefacts/input/cql'
9
+
10
+ describe "Sample data" do
11
+ SamplePrefix = %q{
12
+ vocabulary V;
13
+
14
+ CompanyName is written as String;
15
+ Company is identified by its Name;
16
+ Person is identified by its Name where Person is called PersonName;
17
+ Directorship is where
18
+ Company is directed by Person;
19
+ }
20
+
21
+ Samples = [
22
+ [ # A simple ValueType instance
23
+ "CompanyName 'Microsoft';",
24
+ [{:facts=>[], :instances=>["CompanyName 'Microsoft'"]}]
25
+ ],
26
+ [ # Re-assert the same instance
27
+ "CompanyName 'Microsoft'; CompanyName 'Microsoft';",
28
+ [{:facts=>[], :instances=>["CompanyName 'Microsoft'"]}]
29
+ ],
30
+ [ # The same instance, but in a named population
31
+ "example: CompanyName 'Microsoft';",
32
+ [{:facts=>[], :instances=>["CompanyName 'Microsoft'"]}]
33
+ ],
34
+ [ # A simply-identified EntityType instance
35
+ "Company 'Microsoft';",
36
+ [{:facts=>["Company has CompanyName 'Microsoft'"], :instances=>["Company is identified by CompanyName where Company has CompanyName 'Microsoft'", "CompanyName 'Microsoft'"]}]
37
+ ],
38
+ [ # Re-assert the same instance
39
+ "Company 'Microsoft'; Company 'Microsoft';",
40
+ [{:facts=>["Company has CompanyName 'Microsoft'"], :instances=>["Company is identified by CompanyName where Company has CompanyName 'Microsoft'", "CompanyName 'Microsoft'"]}]
41
+ ],
42
+ [ # The same instance in a named population
43
+ "example: Company 'Microsoft';",
44
+ [{:facts=>["Company has CompanyName 'Microsoft'"], :instances=>["Company is identified by CompanyName where Company has CompanyName 'Microsoft'", "CompanyName 'Microsoft'"]}]
45
+ ],
46
+ [ # The Company instance asserted with an explicit identifying fact
47
+ "Company has CompanyName 'Microsoft';",
48
+ [{:facts=>["Company has CompanyName 'Microsoft'"], :instances=>["Company is identified by CompanyName where Company has CompanyName 'Microsoft'", "CompanyName 'Microsoft'"]}]
49
+ ],
50
+ [ # The Company instance asserted with an joined identifying instance
51
+ "Company has CompanyName, CompanyName 'Microsoft';",
52
+ [{:facts=>["Company has CompanyName 'Microsoft'"], :instances=>["Company is identified by CompanyName where Company has CompanyName 'Microsoft'", "CompanyName 'Microsoft'"]}]
53
+ ],
54
+ [ # The same, with an explicit identifying instance join
55
+ "CompanyName 'Microsoft', Company has CompanyName;",
56
+ [{:facts=>["Company has CompanyName 'Microsoft'"], :instances=>["Company is identified by CompanyName where Company has CompanyName 'Microsoft'", "CompanyName 'Microsoft'"]}]
57
+ ],
58
+ [ # A simple fact instance with two simply-identified entities
59
+ "Company 'Microsoft' is directed by Person 'Gates';",
60
+ [{:facts=>["Company has CompanyName 'Microsoft'", "Company is directed by Person", "Person is called PersonName 'Gates'"], :instances=>["Company is identified by CompanyName where Company has CompanyName 'Microsoft'", "CompanyName 'Microsoft'", "Directorship where Company is directed by Person", "Person is identified by PersonName where Person is called PersonName 'Gates'", "PersonName 'Gates'"]}]
61
+ ],
62
+ [ # Same with an explicit joined fact
63
+ "Company 'Microsoft' is directed by Person, Person is called PersonName 'Gates';",
64
+ [{:facts=>["Company has CompanyName 'Microsoft'", "Company is directed by Person", "Person is called PersonName 'Gates'"], :instances=>["Company is identified by CompanyName where Company has CompanyName 'Microsoft'", "CompanyName 'Microsoft'", "Directorship where Company is directed by Person", "Person is identified by PersonName where Person is called PersonName 'Gates'", "PersonName 'Gates'"]}]
65
+ ],
66
+ [ # Same with explicitly joined facts and instances
67
+ "Company is directed by Person, Person is called PersonName, PersonName 'Gates', Company has CompanyName, CompanyName 'Microsoft';",
68
+ [{:facts=>["Company has CompanyName 'Microsoft'", "Company is directed by Person", "Person is called PersonName 'Gates'"], :instances=>["Company is identified by CompanyName where Company has CompanyName 'Microsoft'", "CompanyName 'Microsoft'", "Directorship where Company is directed by Person", "Person is identified by PersonName where Person is called PersonName 'Gates'", "PersonName 'Gates'"]}]
69
+ ],
70
+ [ # Same in a named population
71
+ "example: Company is directed by Person, Person is called PersonName, PersonName 'Gates', Company has CompanyName, CompanyName 'Microsoft';",
72
+ [{:facts=>["Company has CompanyName 'Microsoft'", "Company is directed by Person", "Person is called PersonName 'Gates'"], :instances=>["Company is identified by CompanyName where Company has CompanyName 'Microsoft'", "CompanyName 'Microsoft'", "Directorship where Company is directed by Person", "Person is identified by PersonName where Person is called PersonName 'Gates'", "PersonName 'Gates'"]}]
73
+ ],
74
+ ]
75
+
76
+ # REVISIT: This code does a better job than verbalise. Consider incorporating it?
77
+ def instance_name(i)
78
+ if i.is_a?(ActiveFacts::Metamodel::Fact)
79
+ fact = i
80
+ reading = fact.fact_type.preferred_reading
81
+ reading_roles = reading.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}.map{|rr| rr.role }
82
+ role_values_in_reading_order = fact.all_role_value.sort_by{|rv| reading_roles.index(rv.role) }
83
+ instance_verbalisations = role_values_in_reading_order.map do |rv|
84
+ next nil unless v = rv.instance.value
85
+ v.to_s
86
+ end
87
+ return reading.expand([], false, instance_verbalisations)
88
+ # REVISIT: Include the instance_names of all role players
89
+ end
90
+
91
+ if i.concept.is_a?(ActiveFacts::Metamodel::ValueType)
92
+ return "#{i.concept.name} #{i.value}"
93
+ end
94
+
95
+ if i.concept.fact_type # An instance of an objectified fact type
96
+ return "#{i.concept.name} where #{instance_name(i.fact)}"
97
+ end
98
+
99
+ # It's an entity that's not an objectified fact type
100
+ # REVISIT: If it has a simple identifier, there's no need to fully verbalise the identifying facts
101
+ pi = i.concept.preferred_identifier
102
+ identifying_role_refs = pi.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}
103
+ return "#{i.concept.name}" +
104
+ " is identified by " +
105
+ identifying_role_refs.map do |rr|
106
+ [ (l = rr.leading_adjective) ? l+"-" : nil,
107
+ rr.role_name || rr.role.concept.name,
108
+ (t = rr.trailing_adjective) ? l+"-" : nil
109
+ ].compact*""
110
+ end * " and " +
111
+ " where " +
112
+ identifying_role_refs.map do |rr| # Go through the identifying roles and emit the facts that define them
113
+ instance_role = i.concept.all_role.detect{|r| r.fact_type == rr.role.fact_type}
114
+ identifying_fact = i.all_role_value.detect{|rv| rv.fact.fact_type == rr.role.fact_type}.fact
115
+ #counterpart_role = (rr.role.fact_type.all_role.to_a-[instance_role])[0]
116
+ #identifying_instance = counterpart_role.all_role_value.detect{|rv| rv.fact == identifying_fact}.instance
117
+ instance_name(identifying_fact)
118
+ end*", "
119
+ end
120
+
121
+ def instance_data(populations)
122
+ populations = @vocabulary.constellation.Population
123
+ populations.keys.sort.map do |popname|
124
+ popvalue = populations[popname]
125
+ {
126
+ :instances => popvalue.all_instance.map { |i| instance_name(i) }.sort,
127
+ :facts => popvalue.all_fact.map { |fact| instance_name(fact) }.sort
128
+ }
129
+ end
130
+ end
131
+
132
+ Samples.each do |c|
133
+ source, expected = *Array(c)
134
+ it "should handle #{source.inspect}" do
135
+ @text = SamplePrefix+source
136
+ @vocabulary = ActiveFacts::Input::CQL.readstring(@text)
137
+ result = instance_data(@vocabulary)
138
+
139
+ if expected
140
+ result[0].should == expected[0]
141
+ else
142
+ pending "#{source}:\n\t#{result.inspect}"
143
+ end
144
+ end
145
+
146
+ it "should de-duplicate #{source.inspect}" do
147
+ # Make sure you don't get anything duplicated
148
+ @text = SamplePrefix+source+source
149
+ @vocabulary = ActiveFacts::Input::CQL.readstring(@text)
150
+ result = instance_data(@vocabulary)
151
+ result[0].should == expected[0]
152
+ end
153
+ end
154
+ end