activefacts-api 0.8.9

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.
@@ -0,0 +1,82 @@
1
+ #
2
+ # ActiveFacts Runtime API
3
+ # Vocabulary module (mixin for any Module that contains classes having ObjectType mixed in)
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
7
+ # The methods of this module are extended into any module that contains
8
+ # a ObjectType class (Entity type or Value type).
9
+ #
10
+ module ActiveFacts
11
+ module API
12
+ # Vocabulary is a mixin that adds methods to any Module which has any ObjectType classes (ValueType or EntityType).
13
+ # A Vocabulary knows all the ObjectType classes including forward-referenced ones,
14
+ # and can resolve the forward references when the class is finally defined.
15
+ # Construction of a Constellation requires a Vocabuary as argument.
16
+ module Vocabulary
17
+ # With a parameter, look up a object_type class by name.
18
+ # Without, return the hash (keyed by the class' basename) of all object_types in this vocabulary
19
+ def object_type(name = nil)
20
+ @object_type ||= {}
21
+ return @object_type unless name
22
+
23
+ return name if name.is_a? Class
24
+
25
+ # puts "Looking up object_type #{name} in #{self.name}"
26
+ camel = name.to_s.camelcase
27
+ if (c = @object_type[camel])
28
+ __bind(camel)
29
+ return c
30
+ end
31
+ return (const_get("#{name}::#{camel}") rescue nil)
32
+ end
33
+
34
+ # Create a new constellation over this vocabulary
35
+ def constellation
36
+ Constellation.new(self)
37
+ end
38
+
39
+ def populate &b
40
+ constellation.populate &b
41
+ end
42
+
43
+ def verbalise
44
+ "Vocabulary #{name}:\n\t" +
45
+ @object_type.keys.sort.map{|object_type|
46
+ c = @object_type[object_type]
47
+ __bind(c.basename)
48
+ c.verbalise + "\n\t\t// Roles played: " + c.roles.verbalise
49
+ }*"\n\t"
50
+ end
51
+
52
+ def __add_object_type(klass) #:nodoc:
53
+ name = klass.basename
54
+ __bind(name)
55
+ # puts "Adding object_type #{name} to #{self.name}"
56
+ @object_type ||= {}
57
+ @object_type[klass.basename] = klass
58
+ end
59
+
60
+ def __delay(object_type_name, args, &block) #:nodoc:
61
+ # puts "Arranging for delayed binding on #{object_type_name.inspect}"
62
+ @delayed ||= Hash.new { |h,k| h[k] = [] }
63
+ @delayed[object_type_name] << [args, block]
64
+ end
65
+
66
+ # __bind raises an error if the named class doesn't exist yet.
67
+ def __bind(object_type_name) #:nodoc:
68
+ object_type = const_get(object_type_name)
69
+ # puts "#{name}.__bind #{object_type_name} -> #{object_type.name}" if object_type
70
+ if (@delayed && @delayed.include?(object_type_name))
71
+ # $stderr.puts "#{object_type_name} was delayed, binding now"
72
+ d = @delayed[object_type_name]
73
+ d.each{|(a,b)|
74
+ b.call(object_type, *a)
75
+ }
76
+ @delayed.delete(object_type_name)
77
+ end
78
+ end
79
+
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,84 @@
1
+ #
2
+ # ActiveFacts tests: Value instances in the Runtime API
3
+ # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
4
+ #
5
+ require 'activefacts/api'
6
+
7
+ describe "AutoCounter Value Type instances" do
8
+ before :each do
9
+ Object.send :remove_const, :Mod if Object.const_defined?("Mod")
10
+ module Mod
11
+ class ThingId < AutoCounter
12
+ value_type
13
+ end
14
+ class Thing
15
+ identified_by :thing_id
16
+ has_one :thing_id
17
+ end
18
+ class Ordinal < Int
19
+ value_type
20
+ end
21
+ class ThingFacet
22
+ identified_by :thing, :ordinal
23
+ has_one :thing
24
+ has_one :ordinal
25
+ end
26
+ end
27
+ @constellation = ActiveFacts::API::Constellation.new(Mod)
28
+ @thing = Mod::Thing.new(:new)
29
+ @thing_id = Mod::ThingId.new
30
+ end
31
+
32
+ it "should respond to verbalise" do
33
+ @thing_id.respond_to?(:verbalise).should be_true
34
+ end
35
+
36
+ it "should verbalise correctly" do
37
+ @thing_id.verbalise.should =~ /ThingId 'new_[0-9]+'/
38
+ end
39
+
40
+ it "should respond to constellation" do
41
+ @thing_id.respond_to?(:constellation).should be_true
42
+ end
43
+
44
+ it "should respond to its roles" do
45
+ @thing_id.respond_to?(:all_thing).should be_true
46
+ end
47
+
48
+ it "should allow prevent invalid role assignment" do
49
+ lambda {
50
+ @thing.thing_id = "foo"
51
+ }.should raise_error
52
+ end
53
+
54
+ it "should not allow its identifying roles to be assigned" do
55
+ lambda {
56
+ @thing.thing_id = @thing_id
57
+ }.should raise_error
58
+ end
59
+
60
+ it "should allow an existing counter to be re-used" do
61
+ @new_thing = Mod::Thing.new(@thing_id)
62
+ @new_thing.thing_id.should == @thing_id
63
+ end
64
+
65
+ it "should return the ValueType in response to .class()" do
66
+ @thing_id.class.vocabulary.should == Mod
67
+ end
68
+
69
+ it "should not allow a counter to be cloned" do
70
+ lambda {
71
+ @thing_id.clone
72
+ }.should raise_error
73
+ end
74
+
75
+ it "should allow an existing counter-identified object to be re-used" do
76
+ thing = @constellation.Thing(:new)
77
+ facets = []
78
+ facets << @constellation.ThingFacet(thing, 0)
79
+ facets << @constellation.ThingFacet(thing, 1)
80
+ facets[0].thing.object_id.should == facets[1].thing.object_id
81
+ facets[0].thing.thing_id.object_id.should == facets[1].thing.thing_id.object_id
82
+ end
83
+
84
+ end
@@ -0,0 +1,129 @@
1
+ #
2
+ # ActiveFacts tests: Constellation instances in the Runtime API
3
+ # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
4
+ #
5
+
6
+ require 'activefacts/api'
7
+
8
+ describe "A Constellation instance" do
9
+ before :each do
10
+ Object.send :remove_const, :Mod if Object.const_defined?("Mod")
11
+ module Mod
12
+ @base_types = [
13
+ Int, Real, AutoCounter, String, Date, DateTime
14
+ ]
15
+
16
+ # Create a value type and a subtype of that value type for each base type:
17
+ @base_types.each do |base_type|
18
+ eval %Q{
19
+ class #{base_type.name}Value < #{base_type.name}
20
+ value_type
21
+ end
22
+
23
+ class #{base_type.name}SubValue < #{base_type.name}Value
24
+ # Note no new "value_type" is required here, it comes through inheritance
25
+ end
26
+ }
27
+ end
28
+
29
+ class Name < StringValue
30
+ value_type
31
+ #has_one :attr, Name
32
+ end
33
+
34
+ class LegalEntity
35
+ identified_by :name
36
+ has_one :name
37
+ end
38
+
39
+ class SurrogateId
40
+ identified_by :auto_counter_value
41
+ has_one :auto_counter_value
42
+ end
43
+
44
+ class Company < LegalEntity
45
+ supertypes SurrogateId
46
+ end
47
+
48
+ class Person < LegalEntity
49
+ identified_by :name, :family_name # REVISIT: want a way to role_alias :name, :given_name
50
+ supertypes SurrogateId
51
+
52
+ has_one :family_name, :class => Name
53
+ end
54
+ end
55
+ @constellation = ActiveFacts::API::Constellation.new(Mod)
56
+ end
57
+
58
+ it "should support fetching its vocabulary" do
59
+ @constellation.vocabulary.should == Mod
60
+ end
61
+
62
+ # it "should support fetching its query" do
63
+ # pending
64
+ # @constellation.query.should == Mod
65
+ # end
66
+
67
+ it "should support methods to construct instances of any object_type" do
68
+ name = foo = acme = fred_fly = nil
69
+ lambda {
70
+ name = @constellation.Name("foo")
71
+ foo = @constellation.LegalEntity("foo")
72
+ acme = @constellation.Company("Acme, Inc")
73
+ fred_fly = @constellation.Person("fred", "fly")
74
+ }.should_not raise_error
75
+ name.class.should == Mod::Name
76
+ name.constellation.should == @constellation
77
+ foo.class.should == Mod::LegalEntity
78
+ foo.constellation.should == @constellation
79
+ acme.class.should == Mod::Company
80
+ acme.constellation.should == @constellation
81
+ fred_fly.class.should == Mod::Person
82
+ fred_fly.constellation.should == @constellation
83
+ end
84
+
85
+ it "should re-use instances constructed the same way" do
86
+ name1 = @constellation.Name("foo")
87
+ foo1 = @constellation.LegalEntity("foo")
88
+ acme1 = @constellation.Company("Acme, Inc")
89
+ fred_fly1 = @constellation.Person("fred", "fly")
90
+
91
+ name2 = @constellation.Name("foo")
92
+ foo2 = @constellation.LegalEntity("foo")
93
+ acme2 = @constellation.Company("Acme, Inc")
94
+ fred_fly2 = @constellation.Person("fred", "fly")
95
+
96
+ name1.object_id.should == name2.object_id
97
+ foo1.object_id.should == foo2.object_id
98
+ acme1.object_id.should == acme2.object_id
99
+ fred_fly1.object_id.should == fred_fly2.object_id
100
+ end
101
+
102
+ it "should index value instances, including by its superclasses" do
103
+ baz = @constellation.Name("baz")
104
+ @constellation.Name.keys.sort.should == ["baz"]
105
+
106
+ @constellation.StringValue.keys.sort.should == ["baz"]
107
+ end
108
+
109
+ it "should index entity instances, including by its superclass and secondary supertypes" do
110
+ name = "Acme, Inc"
111
+ fred = "Fred"
112
+ fly = "Fly"
113
+ acme = @constellation.Company name, :auto_counter_value => :new
114
+ fred_fly = @constellation.Person fred, fly, :auto_counter_value => :new
115
+
116
+ # REVISIT: This should be illegal:
117
+ #fred_fly.auto_counter_value = :new
118
+
119
+ @constellation.Person.keys.sort.should == [[fred, fly]]
120
+ @constellation.Company.keys.sort.should == [[name]]
121
+
122
+ @constellation.LegalEntity.keys.sort.should be_include([name])
123
+ @constellation.LegalEntity.keys.sort.should be_include([fred])
124
+
125
+ @constellation.SurrogateId.values.should be_include(acme)
126
+ @constellation.SurrogateId.values.should be_include(fred_fly)
127
+ end
128
+
129
+ end
@@ -0,0 +1,103 @@
1
+ #
2
+ # ActiveFacts tests: Entity classes in the Runtime API
3
+ # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
4
+ #
5
+ require 'activefacts/api'
6
+
7
+ describe "Entity Type class definitions" do
8
+ before :each do
9
+ Object.send :remove_const, :Mod if Object.const_defined?("Mod")
10
+ module Mod
11
+ class Name < String
12
+ value_type
13
+ end
14
+ class LegalEntity
15
+ end
16
+ class Person < LegalEntity
17
+ identified_by :name
18
+ has_one :name, :class => Name
19
+ end
20
+ end
21
+ end
22
+
23
+ it "should respond_to verbalise" do
24
+ Mod::Person.respond_to?(:verbalise).should be_true
25
+ end
26
+
27
+ it "should not pollute the superclass" do
28
+ Mod::LegalEntity.respond_to?(:verbalise).should_not be_true
29
+ Class.respond_to?(:verbalise).should_not be_true
30
+ end
31
+
32
+ it "should return a string from verbalise" do
33
+ v = Mod::Person.verbalise
34
+ v.should_not be_nil
35
+ v.should_not =~ /REVISIT/
36
+ end
37
+
38
+ it "should respond_to vocabulary" do
39
+ Mod::Person.respond_to?(:vocabulary).should be_true
40
+ end
41
+
42
+ it "should return the parent module as the vocabulary" do
43
+ vocabulary = Mod::Person.vocabulary
44
+ vocabulary.should == Mod
45
+ end
46
+
47
+ it "should return a vocabulary that knows about this object_type" do
48
+ vocabulary = Mod::Person.vocabulary
49
+ vocabulary.respond_to?(:object_type).should be_true
50
+ vocabulary.object_type.has_key?("Person").should be_true
51
+ end
52
+
53
+ it "should respond to roles()" do
54
+ Mod::Person.respond_to?(:roles).should be_true
55
+ end
56
+
57
+ it "should contain only the added role definition" do
58
+ Mod::Person.roles.size.should == 1
59
+ end
60
+
61
+ it "should return the role definition" do
62
+ # Check the role definition may be accessed by passing an index:
63
+ Mod::Person.roles(0).should be_nil
64
+
65
+ role = Mod::Person.roles(:name)
66
+ role.should_not be_nil
67
+
68
+ role = Mod::Person.roles("name")
69
+ role.should_not be_nil
70
+
71
+ # Check the role definition may be accessed by indexing the returned hash:
72
+ role = Mod::Person.roles[:name]
73
+ role.should_not be_nil
74
+
75
+ # Check the role definition array by .include?
76
+ Mod::Person.roles.include?(:name).should be_true
77
+ end
78
+
79
+ it "should fail on a ValueClass" do
80
+ lambda{
81
+ class SomeClass < String
82
+ identified_by
83
+ end
84
+ }.should raise_error
85
+ end
86
+
87
+ it "should return the identifying roles" do
88
+ Mod::Person.identifying_role_names.should == [:name]
89
+ end
90
+
91
+ it "should prevent a role name from matching a object_type that exists unless that object_type is the counterpart" do
92
+ lambda {
93
+ module Mod
94
+ class LegalEntity
95
+ end
96
+ class Bad
97
+ identified_by :name
98
+ has_one :name, LegalEntity
99
+ end
100
+ end
101
+ }.should raise_error
102
+ end
103
+ end
@@ -0,0 +1,462 @@
1
+ #
2
+ # ActiveFacts tests: Value instances in the Runtime API
3
+ # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
4
+ #
5
+ require 'activefacts/api'
6
+
7
+ describe "An instance of every type of ObjectType" do
8
+ before :each do
9
+ Object.send :remove_const, :Mod if Object.const_defined?("Mod")
10
+ module Mod
11
+ # These are the base value types we're going to test:
12
+ @base_types = [
13
+ Int, Real, AutoCounter, String, Date, DateTime, Decimal
14
+ ]
15
+
16
+ # Construct the names of the roles they play:
17
+ @base_type_roles = @base_types.map do |t|
18
+ t.name.snakecase
19
+ end
20
+ @role_names = @base_type_roles.inject([]) {|a, t|
21
+ a << :"#{t}_value"
22
+ } +
23
+ @base_type_roles.inject([]) {|a, t|
24
+ a << :"#{t}_sub_value"
25
+ }
26
+
27
+ # Create a value type and a subtype of that value type for each base type:
28
+ @base_types.each do |base_type|
29
+ eval %Q{
30
+ class #{base_type.name}Value < #{base_type.name}
31
+ value_type
32
+ end
33
+
34
+ class #{base_type.name}SubValue < #{base_type.name}Value
35
+ # Note no new "value_type" is required here, it comes through inheritance
36
+ end
37
+ }
38
+ end
39
+
40
+ # Create a TestByX, TestByXSub, and TestSubByX class for all base types X
41
+ # Each class has a has_one and a one_to_one for all roles.
42
+ # and is identified by the has_one :x role
43
+ @base_types.each do |base_type|
44
+ code = %Q{
45
+ class TestBy#{base_type.name}
46
+ identified_by :#{base_type.name.snakecase}_value#{
47
+ @role_names.map do |role_name|
48
+ %Q{
49
+ has_one :#{role_name}
50
+ one_to_one :one_#{role_name}, :class => #{role_name.to_s.camelcase}}
51
+ end*""
52
+ }
53
+ end
54
+
55
+ class TestBy#{base_type.name}Sub
56
+ identified_by :#{base_type.name.snakecase}_sub_value#{
57
+ @role_names.map do |role_name|
58
+ %Q{
59
+ has_one :#{role_name}
60
+ one_to_one :one_#{role_name}, :class => #{role_name.to_s.camelcase}}
61
+ end*""
62
+ }
63
+ end
64
+
65
+ class TestSubBy#{base_type.name} < TestBy#{base_type.name}
66
+ # Entity subtypes, inherit identification and all roles
67
+ end
68
+
69
+ class TestBy#{base_type.name}Entity
70
+ identified_by :test_by_#{base_type.name.snakecase}
71
+ one_to_one :test_by_#{base_type.name.snakecase}
72
+ end}
73
+ eval code
74
+ end
75
+ end
76
+
77
+ # Simple Values
78
+ @int = 0
79
+ @real = 0.0
80
+ @auto_counter = 0
81
+ @new_auto_counter = :new
82
+ @string = "zero"
83
+ @date = [2008, 04, 19]
84
+ @date_time = [2008, 04, 19, 10, 28, 14]
85
+
86
+ # Value Type instances
87
+ @int_value = Mod::IntValue.new(1)
88
+ @real_value = Mod::RealValue.new(1.0)
89
+ @auto_counter_value = Mod::AutoCounterValue.new(1)
90
+ @new_auto_counter_value = Mod::AutoCounterValue.new(:new)
91
+ @string_value = Mod::StringValue.new("one")
92
+ @date_value = Mod::DateValue.new(2008, 04, 20)
93
+ @date_time_value = Mod::DateTimeValue.new(2008, 04, 20, 10, 28, 14)
94
+
95
+ # Value SubType instances
96
+ @int_sub_value = Mod::IntSubValue.new(4)
97
+ @real_sub_value = Mod::RealSubValue.new(4.0)
98
+ @auto_counter_sub_value = Mod::AutoCounterSubValue.new(4)
99
+ @auto_counter_sub_value_new = Mod::AutoCounterSubValue.new(:new)
100
+ @string_sub_value = Mod::StringSubValue.new("five")
101
+ @date_sub_value = Mod::DateSubValue.new(2008, 04, 25)
102
+ @date_time_sub_value = Mod::DateTimeSubValue.new(2008, 04, 26, 10, 28, 14)
103
+
104
+ # Entities identified by Value Type, SubType and Entity-by-value-type instances
105
+ @test_by_int = Mod::TestByInt.new(2)
106
+ @test_by_real = Mod::TestByReal.new(2.0)
107
+ @test_by_auto_counter = Mod::TestByAutoCounter.new(2)
108
+ @test_by_auto_counter_new = Mod::TestByAutoCounter.new(:new)
109
+ @test_by_string = Mod::TestByString.new("two")
110
+ @test_by_date = Mod::TestByDate.new(Date.new(2008,04,28))
111
+ #@test_by_date = Mod::TestByDate.new(2008,04,28)
112
+ @test_by_date_time = Mod::TestByDateTime.new(2008,04,28,10,28,15)
113
+ #@test_by_date_time = Mod::TestByDateTime.new(DateTime.new(2008,04,28,10,28,15))
114
+
115
+ @test_by_int_sub = Mod::TestByIntSub.new(2)
116
+ @test_by_real_sub = Mod::TestByRealSub.new(5.0)
117
+ @test_by_auto_counter_sub = Mod::TestByAutoCounterSub.new(6)
118
+ @test_by_auto_counter_new_sub = Mod::TestByAutoCounterSub.new(:new)
119
+ @test_by_string_sub = Mod::TestByStringSub.new("six")
120
+ @test_by_date_sub = Mod::TestByDateSub.new(Date.new(2008,04,27))
121
+ @test_by_date_time_sub = Mod::TestByDateTimeSub.new(2008,04,29,10,28,15)
122
+
123
+ @test_by_int_entity = Mod::TestByIntEntity.new(@test_by_int)
124
+ @test_by_real_entity = Mod::TestByRealEntity.new(@test_by_real)
125
+ @test_by_auto_counter_entity = Mod::TestByAutoCounterEntity.new(@test_by_auto_counter)
126
+ @test_by_auto_counter_new_entity = Mod::TestByAutoCounterEntity.new(@test_by_auto_counter_new)
127
+ @test_by_string_entity = Mod::TestByStringEntity.new(@test_by_string)
128
+ @test_by_date_entity = Mod::TestByDateEntity.new(@test_by_date)
129
+ @test_by_date_time_entity = Mod::TestByDateTimeEntity.new(@test_by_date_time)
130
+
131
+ # Entity subtypes
132
+ @test_sub_by_int = Mod::TestSubByInt.new(2)
133
+ @test_sub_by_real = Mod::TestSubByReal.new(2.0)
134
+ @test_sub_by_auto_counter = Mod::TestSubByAutoCounter.new(2)
135
+ @test_sub_by_auto_counter_new = Mod::TestSubByAutoCounter.new(:new)
136
+ @test_sub_by_string = Mod::TestSubByString.new("two")
137
+ @test_sub_by_date = Mod::TestSubByDate.new(Date.new(2008,04,28))
138
+ @test_sub_by_date_time = Mod::TestSubByDateTime.new(2008,04,28,10,28,15)
139
+
140
+ # These arrays get zipped together in various ways. Keep them aligned.
141
+ @values = [
142
+ @int, @real, @auto_counter, @new_auto_counter,
143
+ @string, @date, @date_time,
144
+ ]
145
+ @classes = [
146
+ Int, Real, AutoCounter, AutoCounter,
147
+ String, Date, DateTime,
148
+ ]
149
+ @value_types = [
150
+ Mod::IntValue, Mod::RealValue, Mod::AutoCounterValue, Mod::AutoCounterValue,
151
+ Mod::StringValue, Mod::DateValue, Mod::DateTimeValue,
152
+ Mod::IntSubValue, Mod::RealSubValue, Mod::AutoCounterSubValue, Mod::AutoCounterSubValue,
153
+ Mod::StringSubValue, Mod::DateSubValue, Mod::DateTimeSubValue,
154
+ ]
155
+ @value_instances = [
156
+ @int_value, @real_value, @auto_counter_value, @new_auto_counter_value,
157
+ @string_value, @date_value, @date_time_value,
158
+ @int_sub_value, @real_sub_value, @auto_counter_sub_value, @auto_counter_sub_value_new,
159
+ @string_sub_value, @date_sub_value, @date_time_sub_value,
160
+ @int_value, @real_value, @auto_counter_value, @new_auto_counter_value,
161
+ @string_value, @date_value, @date_time_value,
162
+ ]
163
+ @entity_types = [
164
+ Mod::TestByInt, Mod::TestByReal, Mod::TestByAutoCounter, Mod::TestByAutoCounter,
165
+ Mod::TestByString, Mod::TestByDate, Mod::TestByDateTime,
166
+ Mod::TestByIntSub, Mod::TestByRealSub, Mod::TestByAutoCounterSub, Mod::TestByAutoCounterSub,
167
+ Mod::TestByStringSub, Mod::TestByDateSub, Mod::TestByDateTimeSub,
168
+ Mod::TestSubByInt, Mod::TestSubByReal, Mod::TestSubByAutoCounter, Mod::TestSubByAutoCounter,
169
+ Mod::TestSubByString, Mod::TestSubByDate, Mod::TestSubByDateTime,
170
+ ]
171
+ @entities = [
172
+ @test_by_int, @test_by_real, @test_by_auto_counter, @test_by_auto_counter_new,
173
+ @test_by_string, @test_by_date, @test_by_date_time,
174
+ @test_by_int_sub, @test_by_real_sub, @test_by_auto_counter_sub, @test_by_auto_counter_new_sub,
175
+ @test_by_string_sub, @test_by_date_sub, @test_by_date_time_sub,
176
+ @test_sub_by_int, @test_sub_by_real, @test_sub_by_auto_counter, @test_sub_by_auto_counter_new,
177
+ @test_sub_by_string, @test_sub_by_date, @test_sub_by_date_time,
178
+ ]
179
+ @entities_by_entity = [
180
+ @test_by_int_entity,
181
+ @test_by_real_entity,
182
+ @test_by_auto_counter_entity,
183
+ @test_by_auto_counter_new_entity,
184
+ @test_by_string_entity,
185
+ @test_by_date_entity,
186
+ @test_by_date_time_entity,
187
+ ]
188
+ @entities_by_entity_types = [
189
+ Mod::TestByIntEntity, Mod::TestByRealEntity, Mod::TestByAutoCounterEntity, Mod::TestByAutoCounterEntity,
190
+ Mod::TestByStringEntity, Mod::TestByDateEntity, Mod::TestByDateTimeEntity,
191
+ ]
192
+ @test_role_names = [
193
+ :int_value, :real_value, :auto_counter_value, :auto_counter_value,
194
+ :string_value, :date_value, :date_time_value,
195
+ :int_sub_value, :real_sub_value, :auto_counter_sub_value, :auto_counter_sub_value,
196
+ :string_sub_value, :date_sub_value, :date_time_sub_value,
197
+ :int_value, :real_value, :auto_counter_value, :auto_counter_value,
198
+ :string_value, :date_value, :date_time_value,
199
+ ]
200
+ @role_values = [
201
+ 3, 3.0, 6, 7,
202
+ "three", Date.new(2008,4,21), DateTime.new(2008,4,22,10,28,16),
203
+ ]
204
+ @subtype_role_instances = [
205
+ Mod::IntSubValue.new(6), Mod::RealSubValue.new(6.0),
206
+ Mod::AutoCounterSubValue.new(:new), Mod::AutoCounterSubValue.new(8),
207
+ Mod::StringSubValue.new("seven"),
208
+ Mod::DateSubValue.new(2008,4,29), Mod::DateTimeSubValue.new(2008,4,30,10,28,16)
209
+ ]
210
+ end
211
+
212
+ it "if a value type, should verbalise" do
213
+ @value_types.each do |value_type|
214
+ #puts "#{value_type} verbalises as #{value_type.verbalise}"
215
+ value_type.respond_to?(:verbalise).should be_true
216
+ verbalisation = value_type.verbalise
217
+ verbalisation.should =~ %r{\b#{value_type.basename}\b}
218
+ verbalisation.should =~ %r{\b#{value_type.superclass.basename}\b}
219
+ end
220
+ end
221
+
222
+ it "if an entity type, should verbalise" do
223
+ @entity_types.each do |entity_type|
224
+ #puts entity_type.verbalise
225
+ entity_type.respond_to?(:verbalise).should be_true
226
+ verbalisation = entity_type.verbalise
227
+ verbalisation.should =~ %r{\b#{entity_type.basename}\b}
228
+
229
+ # All identifying roles should be in the verbalisation.
230
+ # Strictly this should be the role name, but we don't set names here.
231
+ entity_type.identifying_role_names.each do |ir|
232
+ role = entity_type.roles(ir)
233
+ role.should_not be_nil
234
+ counterpart_object_type = role.counterpart_object_type
235
+ verbalisation.should =~ %r{\b#{counterpart_object_type.basename}\b}
236
+ end
237
+ end
238
+ end
239
+
240
+ it "if a value, should verbalise" do
241
+ @value_instances.each do |value|
242
+ #puts value.verbalise
243
+ value.respond_to?(:verbalise).should be_true
244
+ verbalisation = value.verbalise
245
+ verbalisation.should =~ %r{\b#{value.class.basename}\b}
246
+ end
247
+ end
248
+
249
+ it "if an entity, should respond to verbalise" do
250
+ (@entities+@entities_by_entity).each do |entity|
251
+ #puts entity.verbalise
252
+ entity.respond_to?(:verbalise).should be_true
253
+ verbalisation = entity.verbalise
254
+ verbalisation.should =~ %r{\b#{entity.class.basename}\b}
255
+ entity.class.identifying_role_names.each do |ir|
256
+ role = entity.class.roles(ir)
257
+ role.should_not be_nil
258
+ counterpart_object_type = role.counterpart_object_type
259
+ verbalisation.should =~ %r{\b#{counterpart_object_type.basename}\b}
260
+ end
261
+ end
262
+ end
263
+
264
+ it "should respond to constellation" do
265
+ (@value_instances+@entities+@entities_by_entity).each do |instance|
266
+ instance.respond_to?(:constellation).should be_true
267
+ end
268
+ end
269
+
270
+ it "should respond to all its roles" do
271
+ @entities.each do |entity|
272
+ @test_role_names.each do |role_name|
273
+ entity.respond_to?(role_name).should be_true
274
+ entity.respond_to?(:"#{role_name}=").should be_true
275
+ entity.respond_to?(:"one_#{role_name}").should be_true
276
+ entity.respond_to?(:"one_#{role_name}=").should be_true
277
+ end
278
+ end
279
+ @entities_by_entity.each do |entity|
280
+ role = entity.class.roles(entity.class.identifying_role_names[0])
281
+ role_name = role.name
282
+ entity.respond_to?(role_name).should be_true
283
+ entity.respond_to?(:"#{role_name}=").should be_true
284
+ end
285
+ end
286
+
287
+ it "should return the ObjectType in response to .class()" do
288
+ @value_types.zip(@value_instances).each do |object_type, instance|
289
+ instance.class.should == object_type
290
+ end
291
+ @entity_types.zip(@entities).each do |object_type, instance|
292
+ instance.class.should == object_type
293
+ end
294
+ @entities_by_entity_types.zip(@entities_by_entity).each do |object_type, instance|
295
+ instance.class.should == object_type
296
+ end
297
+ end
298
+
299
+ it "should return the module in response to .vocabulary()" do
300
+ (@value_types+@entity_types).zip((@value_instances+@entities+@entities_by_entity)).each do |object_type, instance|
301
+ instance.class.vocabulary.should == Mod
302
+ end
303
+ end
304
+
305
+ it "each entity type should be able to be constructed using simple values" do
306
+ @entity_types.zip(@values+@values+@values, @classes+@classes+@classes).each do |entity_type, value, klass|
307
+ # An identifier parameter can be an array containing a simple value too
308
+ [ value,
309
+ Array === value ? nil : [value],
310
+ # entity_type.new(value) # REVISIT: It's not yet the case that an instance of the correct type can be used as a constructor parameter
311
+ ].compact.each do |value|
312
+ e = nil
313
+ lambda {
314
+ #puts "Constructing #{entity_type} using #{value.class} #{value.inspect}:"
315
+ e = entity_type.new(value)
316
+ }.should_not raise_error
317
+ # Verify that the identifying role has a equivalent value (except AutoCounter):
318
+ role_name = entity_type.identifying_role_names[0]
319
+ role = entity_type.roles(role_name)
320
+ counterpart_object_type = role.counterpart_object_type
321
+ player_superclasses = [ counterpart_object_type.superclass, counterpart_object_type.superclass.superclass ]
322
+ e.send(role_name).should == klass.new(*value) unless player_superclasses.include?(AutoCounter)
323
+ end
324
+ end
325
+ end
326
+
327
+ it "should allow its non-identifying roles to be assigned values" do
328
+ @entities.zip(@test_role_names).each do |entity, identifying_role|
329
+ @test_role_names.zip(@role_values).each do |role_name, value|
330
+ # No roles of ValueType instances are tested in this file:
331
+ raise hell unless entity.class.included_modules.include?(ActiveFacts::API::Entity)
332
+ next if entity.class.identifying_role_names.include?(role_name)
333
+ lambda {
334
+ begin
335
+ entity.send(:"#{role_name}=", value)
336
+ rescue => e
337
+ raise
338
+ end
339
+ }.should_not raise_error
340
+ lambda {
341
+ entity.send(:"one_#{role_name}=", value)
342
+ }.should_not raise_error
343
+ end
344
+ end
345
+ end
346
+
347
+ it "that is an entity type should not allow its identifying roles to be re-assigned" do
348
+ @entities.zip(@test_role_names).each do |entity, identifying_role|
349
+ @test_role_names.zip(@role_values).each do |role_name, value|
350
+ if entity.class.identifying_role_names.include?(role_name) && entity.send(role_name) != nil && value != nil
351
+ lambda {
352
+ entity.send(:"#{role_name}=", value)
353
+ }.should raise_error
354
+ end
355
+ one_role = :"one_#{role_name}"
356
+ if entity.class.identifying_role_names.include?(one_role) && entity.send(one_role) != nil
357
+ lambda {
358
+ entity.send(:"one_#{role_name}=", value)
359
+ }.should raise_error
360
+ end
361
+ end
362
+ end
363
+ end
364
+
365
+ it "that is an entity type should allow its identifying roles to be assigned to and from nil" do
366
+ @entities.zip(@test_role_names).each do |entity, identifying_role|
367
+ @test_role_names.zip(@role_values).each do |role_name, value|
368
+ if entity.class.identifying_role_names.include?(role_name)
369
+ # Nullify the value first:
370
+ entity.send(:"#{role_name}=", nil)
371
+ lambda {
372
+ entity.send(:"#{role_name}=", value)
373
+ }.should_not raise_error
374
+ end
375
+ one_role = :"one_#{role_name}"
376
+ if entity.class.identifying_role_names.include?(one_role) && entity.send(one_role) == nil
377
+ entity.send(one_role, nil)
378
+ lambda {
379
+ entity.send(one_role, value)
380
+ }.should_not raise_error
381
+ end
382
+ end
383
+ end
384
+ end
385
+
386
+ it "should allow its non-identifying roles to be assigned instances" do
387
+ @entities.zip(@test_role_names).each do |entity, identifying_role|
388
+ @test_role_names.zip(@value_types, @role_values).each do |role_name, klass, value|
389
+ next unless value
390
+ next if role_name == identifying_role
391
+ instance = klass.new(value)
392
+ lambda {
393
+ entity.send(:"#{role_name}=", instance)
394
+ }.should_not raise_error
395
+ entity.send(role_name).class.should == klass
396
+ lambda {
397
+ entity.send(:"one_#{role_name}=", instance)
398
+ }.should_not raise_error
399
+ entity.send(:"one_#{role_name}").class.should == klass
400
+ end
401
+ end
402
+ end
403
+
404
+ it "should allow its non-identifying roles to be assigned instances of value subtypes, retaining the subtype" do
405
+ @entities.zip(@test_role_names).each do |entity, identifying_role|
406
+ @test_role_names.zip(@subtype_role_instances).each do |role_name, instance|
407
+ next unless instance
408
+ next if role_name == identifying_role
409
+ lambda {
410
+ entity.send(:"#{role_name}=", instance)
411
+ }.should_not raise_error
412
+ entity.send(role_name).class.should == instance.class
413
+ lambda {
414
+ entity.send(:"one_#{role_name}=", instance)
415
+ }.should_not raise_error
416
+ entity.send(:"one_#{role_name}").class.should == instance.class
417
+ end
418
+ end
419
+ end
420
+
421
+ it "should add to has_one's counterpart role when non-identifying roles are assigned values" do
422
+ @entities.zip(@test_role_names).each do |entity, identifying_role|
423
+ @test_role_names.zip(@role_values).each do |role_name, value|
424
+ next if role_name == identifying_role or !value
425
+
426
+ # Test the has_one role:
427
+ role = entity.class.roles(role_name)
428
+ old_counterpart = entity.send(:"#{role_name}")
429
+ entity.send(:"#{role_name}=", value)
430
+ counterpart = entity.send(:"#{role_name}")
431
+ old_counterpart.should_not == counterpart
432
+ counterpart.send(role.counterpart.name).should be_include(entity)
433
+ old_counterpart.send(role.counterpart.name).should_not be_include(entity) if old_counterpart
434
+
435
+ # Test the one_to_one role:
436
+ role = entity.class.roles(:"one_#{role_name}")
437
+ old_counterpart = entity.send(:"one_#{role_name}")
438
+ entity.send(:"one_#{role_name}=", value)
439
+ counterpart = entity.send(:"one_#{role_name}")
440
+ old_counterpart.should_not == counterpart # Make sure we changed it!
441
+ counterpart.send(role.counterpart.name).should == entity
442
+ old_counterpart.send(role.counterpart.name).should be_nil if old_counterpart
443
+ end
444
+ end
445
+ end
446
+
447
+ it "should allow its non-identifying roles to be assigned nil" do
448
+ @entities.zip(@test_role_names).each do |entity, identifying_role|
449
+ @test_role_names.zip(@role_values).each do |role_name, value|
450
+ next if role_name == identifying_role
451
+ entity.send(:"#{role_name}=", value)
452
+ lambda {
453
+ entity.send(:"#{role_name}=", nil)
454
+ }.should_not raise_error
455
+ lambda {
456
+ entity.send(:"one_#{role_name}=", nil)
457
+ }.should_not raise_error
458
+ end
459
+ end
460
+ end
461
+
462
+ end