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.
- data/.document +5 -0
- data/.rspec +1 -0
- data/LICENSE.txt +19 -0
- data/README.rdoc +41 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/lib/activefacts/api.rb +44 -0
- data/lib/activefacts/api/constellation.rb +128 -0
- data/lib/activefacts/api/entity.rb +260 -0
- data/lib/activefacts/api/instance.rb +60 -0
- data/lib/activefacts/api/instance_index.rb +84 -0
- data/lib/activefacts/api/numeric.rb +175 -0
- data/lib/activefacts/api/object_type.rb +411 -0
- data/lib/activefacts/api/role.rb +80 -0
- data/lib/activefacts/api/role_proxy.rb +71 -0
- data/lib/activefacts/api/role_values.rb +117 -0
- data/lib/activefacts/api/standard_types.rb +87 -0
- data/lib/activefacts/api/support.rb +66 -0
- data/lib/activefacts/api/value.rb +135 -0
- data/lib/activefacts/api/vocabulary.rb +82 -0
- data/spec/api/autocounter_spec.rb +84 -0
- data/spec/api/constellation_spec.rb +129 -0
- data/spec/api/entity_type_spec.rb +103 -0
- data/spec/api/instance_spec.rb +462 -0
- data/spec/api/roles_spec.rb +124 -0
- data/spec/api/value_type_spec.rb +114 -0
- data/spec/spec_helper.rb +12 -0
- metadata +154 -0
@@ -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
|