activefacts-api 0.8.9

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