perpetuity 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +6 -0
- data/lib/perpetuity/data_injectable.rb +0 -3
- data/lib/perpetuity/identity_map.rb +4 -2
- data/lib/perpetuity/mapper.rb +25 -8
- data/lib/perpetuity/mongodb/serializer.rb +9 -3
- data/lib/perpetuity/reference.rb +6 -2
- data/lib/perpetuity/version.rb +1 -1
- data/lib/perpetuity.rb +0 -1
- data/spec/integration/associations_spec.rb +8 -7
- data/spec/integration/indexing_spec.rb +5 -0
- data/spec/integration/mongodb_spec.rb +7 -1
- data/spec/integration/persistence_spec.rb +28 -24
- data/spec/integration/retrieval_spec.rb +34 -33
- data/spec/integration/serialization_spec.rb +3 -3
- data/spec/integration/update_spec.rb +9 -9
- data/spec/perpetuity/data_injectable_spec.rb +2 -2
- data/spec/perpetuity/dereferencer_spec.rb +6 -12
- data/spec/perpetuity/identity_map_spec.rb +12 -3
- data/spec/perpetuity/mapper_spec.rb +49 -2
- data/spec/perpetuity/mongodb/serializer_spec.rb +47 -12
- data/spec/perpetuity/reference_spec.rb +4 -0
- data/spec/perpetuity_spec.rb +19 -12
- data/spec/support/test_classes/car.rb +6 -0
- metadata +281 -143
- checksums.yaml +0 -7
- data/lib/perpetuity/persisted_object.rb +0 -7
- data/spec/perpetuity/persisted_object_spec.rb +0 -16
@@ -3,22 +3,20 @@ require 'perpetuity/reference'
|
|
3
3
|
|
4
4
|
module Perpetuity
|
5
5
|
describe Dereferencer do
|
6
|
-
let(:registry) { double('Mapper Registry') }
|
7
6
|
let(:mapper) { double('ObjectMapper') }
|
8
|
-
let(:first) { double('Object',
|
9
|
-
let(:second) { double('Object',
|
7
|
+
let(:first) { double('Object', class: Object) }
|
8
|
+
let(:second) { double('Object', class: Object) }
|
10
9
|
let(:first_ref) { Reference.new(Object, 1) }
|
11
10
|
let(:second_ref) { Reference.new(Object, 2) }
|
12
11
|
let(:objects) { [first, second] }
|
12
|
+
let(:registry) { { Object => mapper } }
|
13
13
|
let(:derefer) { Dereferencer.new(registry) }
|
14
14
|
|
15
15
|
context 'with one reference' do
|
16
|
-
|
17
|
-
|
16
|
+
it 'loads objects based on the specified objects and attribute' do
|
17
|
+
first.instance_variable_set :@id, 1
|
18
18
|
mapper.should_receive(:find).with(1) { first }
|
19
|
-
end
|
20
19
|
|
21
|
-
it 'loads objects based on the specified objects and attribute' do
|
22
20
|
derefer.load first_ref
|
23
21
|
derefer[first_ref].should == first
|
24
22
|
end
|
@@ -31,12 +29,8 @@ module Perpetuity
|
|
31
29
|
end
|
32
30
|
|
33
31
|
context 'with multiple references' do
|
34
|
-
before do
|
35
|
-
registry.should_receive(:[]).with(Object) { mapper }
|
36
|
-
mapper.should_receive(:select) { objects }
|
37
|
-
end
|
38
|
-
|
39
32
|
it 'returns the array of dereferenced objects' do
|
33
|
+
mapper.should_receive(:select) { objects }
|
40
34
|
derefer.load([first_ref, second_ref]).should == objects
|
41
35
|
end
|
42
36
|
end
|
@@ -2,19 +2,28 @@ require 'perpetuity/identity_map'
|
|
2
2
|
|
3
3
|
module Perpetuity
|
4
4
|
describe IdentityMap do
|
5
|
+
let(:object_mapper) { double('Mapper', id_for: 1) }
|
6
|
+
let(:object) { double('Object', class: Object) }
|
7
|
+
let(:id_map) { IdentityMap.new }
|
8
|
+
|
5
9
|
context 'when the object exists in the IdentityMap' do
|
6
|
-
let(:object) { double('Object',
|
10
|
+
let(:object) { double('Object', class: Object) }
|
11
|
+
|
12
|
+
before { object.instance_variable_set :@id, 1 }
|
7
13
|
|
8
14
|
it 'returns the object with the given class and id' do
|
9
|
-
id_map = IdentityMap.new
|
10
15
|
id_map << object
|
11
16
|
id_map[Object, 1].should == object
|
12
17
|
end
|
18
|
+
|
19
|
+
it 'stringifies keys when checking' do
|
20
|
+
id_map << object
|
21
|
+
id_map[Object, '1'].should == object
|
22
|
+
end
|
13
23
|
end
|
14
24
|
|
15
25
|
context 'when the object does not exist in the IdentityMap' do
|
16
26
|
it 'returns nil' do
|
17
|
-
id_map = IdentityMap.new
|
18
27
|
id_map[Object, 1].should be_nil
|
19
28
|
end
|
20
29
|
end
|
@@ -61,7 +61,7 @@ module Perpetuity
|
|
61
61
|
|
62
62
|
describe 'finding a single object' do
|
63
63
|
let(:options) { {:attribute=>nil, :direction=>nil, :limit=>nil, :page=>nil} }
|
64
|
-
let(:returned_object) { double('Retrieved Object') }
|
64
|
+
let(:returned_object) { double('Retrieved Object', class: Object) }
|
65
65
|
|
66
66
|
it 'finds an object by ID' do
|
67
67
|
criteria = { id: 1 }
|
@@ -87,12 +87,33 @@ module Perpetuity
|
|
87
87
|
mapper.find { |o| o.name == 'foo' }.should == returned_object
|
88
88
|
mapper.detect { |o| o.name == 'foo' }.should == returned_object
|
89
89
|
end
|
90
|
+
|
91
|
+
it 'caches results' do
|
92
|
+
mapper.give_id_to returned_object, 1
|
93
|
+
criteria = { id: 1 }
|
94
|
+
data_source.should_receive(:retrieve)
|
95
|
+
.with(Object, criteria, options) { [returned_object] }
|
96
|
+
.once
|
97
|
+
|
98
|
+
mapper.find(1)
|
99
|
+
mapper.find(1)
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'does not cache nil results' do
|
103
|
+
criteria = { id: 1 }
|
104
|
+
data_source.should_receive(:retrieve)
|
105
|
+
.with(Object, criteria, options) { [] }
|
106
|
+
.twice
|
107
|
+
|
108
|
+
mapper.find(1)
|
109
|
+
mapper.find(1)
|
110
|
+
end
|
90
111
|
end
|
91
112
|
|
92
113
|
it 'saves an object' do
|
93
114
|
mapper_class.attribute :foo
|
94
115
|
object = Object.new
|
95
|
-
|
116
|
+
mapper.give_id_to object, 1
|
96
117
|
object.instance_variable_set '@foo', 'bar'
|
97
118
|
data_source.should_receive(:can_serialize?).with('bar') { true }
|
98
119
|
data_source.should_receive(:update).with Object, 1, { 'foo' => 'bar' }
|
@@ -112,5 +133,31 @@ module Perpetuity
|
|
112
133
|
mapper.delete_all
|
113
134
|
end
|
114
135
|
end
|
136
|
+
|
137
|
+
describe 'checking persistence of an object' do
|
138
|
+
let(:object) { Object.new }
|
139
|
+
|
140
|
+
context 'when persisted' do
|
141
|
+
before { mapper.give_id_to object, 1 }
|
142
|
+
|
143
|
+
it 'knows the object is persisted' do
|
144
|
+
mapper.persisted?(object).should be_true
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'knows the id of the object' do
|
148
|
+
mapper.id_for(object).should be == 1
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context 'when not persisted' do
|
153
|
+
it 'knows the object is not persisted' do
|
154
|
+
mapper.persisted?(object).should be_false
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'returns a nil id' do
|
158
|
+
mapper.id_for(object).should be_nil
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
115
162
|
end
|
116
163
|
end
|
@@ -3,6 +3,7 @@ require 'perpetuity/mapper'
|
|
3
3
|
require 'perpetuity/mapper_registry'
|
4
4
|
require 'support/test_classes/book'
|
5
5
|
require 'support/test_classes/user'
|
6
|
+
require 'support/test_classes/car'
|
6
7
|
|
7
8
|
module Perpetuity
|
8
9
|
class MongoDB
|
@@ -12,32 +13,32 @@ module Perpetuity
|
|
12
13
|
let(:authors) { [dave, andy] }
|
13
14
|
let(:book) { Book.new('The Pragmatic Programmer', authors) }
|
14
15
|
let(:mapper_registry) { MapperRegistry.new }
|
15
|
-
let
|
16
|
+
let(:book_mapper) do
|
16
17
|
registry = mapper_registry
|
17
18
|
Class.new(Perpetuity::Mapper) do
|
18
19
|
map Book, registry
|
19
20
|
attribute :title
|
20
21
|
attribute :authors
|
21
|
-
end
|
22
|
+
end.new(registry)
|
22
23
|
end
|
23
|
-
let
|
24
|
+
let(:user_mapper) do
|
24
25
|
registry = mapper_registry
|
25
26
|
Class.new(Perpetuity::Mapper) do
|
26
27
|
map User, registry
|
27
28
|
attribute :name
|
28
|
-
end
|
29
|
+
end.new(registry)
|
29
30
|
end
|
30
31
|
let(:data_source) { double('Data Source') }
|
31
|
-
let(:serializer) { Serializer.new(
|
32
|
+
let(:serializer) { Serializer.new(book_mapper) }
|
32
33
|
|
33
34
|
before do
|
34
|
-
|
35
|
-
|
36
|
-
user_mapper_class.stub(data_source: data_source)
|
37
|
-
book_mapper_class.stub(data_source: data_source)
|
35
|
+
serializer.give_id_to dave, 1
|
36
|
+
serializer.give_id_to andy, 2
|
38
37
|
end
|
39
38
|
|
40
39
|
it 'serializes an array of non-embedded attributes as references' do
|
40
|
+
user_mapper.stub(data_source: data_source)
|
41
|
+
book_mapper.stub(data_source: data_source)
|
41
42
|
data_source.should_receive(:can_serialize?).with(book.title).and_return true
|
42
43
|
data_source.should_receive(:can_serialize?).with(dave).and_return false
|
43
44
|
data_source.should_receive(:can_serialize?).with(andy).and_return false
|
@@ -47,13 +48,13 @@ module Perpetuity
|
|
47
48
|
{
|
48
49
|
'__metadata__' => {
|
49
50
|
'class' => 'User',
|
50
|
-
'id' => dave
|
51
|
+
'id' => user_mapper.id_for(dave)
|
51
52
|
}
|
52
53
|
},
|
53
54
|
{
|
54
55
|
'__metadata__' => {
|
55
56
|
'class' => 'User',
|
56
|
-
'id' => andy
|
57
|
+
'id' => user_mapper.id_for(andy)
|
57
58
|
}
|
58
59
|
}
|
59
60
|
]
|
@@ -68,10 +69,11 @@ module Perpetuity
|
|
68
69
|
}
|
69
70
|
end
|
70
71
|
let(:user) { User.new(name_data) }
|
71
|
-
let(:user_mapper) { mapper_registry[User] }
|
72
72
|
let(:user_serializer) { Serializer.new(user_mapper) }
|
73
73
|
|
74
74
|
before do
|
75
|
+
user_mapper.stub(data_source: data_source)
|
76
|
+
book_mapper.stub(data_source: data_source)
|
75
77
|
data_source.stub(:can_serialize?).with(name_data) { true }
|
76
78
|
end
|
77
79
|
|
@@ -90,6 +92,11 @@ module Perpetuity
|
|
90
92
|
let(:objects) { serializer.unserialize(serialized_attrs) }
|
91
93
|
subject { objects.first }
|
92
94
|
|
95
|
+
before do
|
96
|
+
user_mapper.stub(data_source: data_source)
|
97
|
+
book_mapper.stub(data_source: data_source)
|
98
|
+
end
|
99
|
+
|
93
100
|
it { should be_a Complex }
|
94
101
|
it { should eq unserializable_object}
|
95
102
|
end
|
@@ -99,6 +106,11 @@ module Perpetuity
|
|
99
106
|
let(:title) { 'title' }
|
100
107
|
let(:book) { Book.new(title, [author]) }
|
101
108
|
|
109
|
+
before do
|
110
|
+
user_mapper.stub(data_source: data_source)
|
111
|
+
book_mapper.stub(data_source: data_source)
|
112
|
+
end
|
113
|
+
|
102
114
|
it 'passes the reference unserialized' do
|
103
115
|
data_source.should_receive(:can_serialize?).with('title') { true }
|
104
116
|
serializer.serialize(book).should == {
|
@@ -112,6 +124,29 @@ module Perpetuity
|
|
112
124
|
}
|
113
125
|
end
|
114
126
|
end
|
127
|
+
|
128
|
+
context 'with uninitialized attributes' do
|
129
|
+
let(:car_model) { 'Corvette' }
|
130
|
+
let(:car) { Car.new(model: car_model) }
|
131
|
+
let(:mapper) do
|
132
|
+
registry = mapper_registry
|
133
|
+
Class.new(Mapper) do
|
134
|
+
map Car, registry
|
135
|
+
|
136
|
+
attribute :make
|
137
|
+
attribute :model
|
138
|
+
end.new(registry)
|
139
|
+
end
|
140
|
+
let(:serializer) { Serializer.new(mapper) }
|
141
|
+
|
142
|
+
|
143
|
+
it 'does not persist uninitialized attributes' do
|
144
|
+
mapper.stub data_source: data_source
|
145
|
+
data_source.should_receive(:can_serialize?).with(car_model) { true }
|
146
|
+
|
147
|
+
serializer.serialize(car).should == { 'model' => car_model }
|
148
|
+
end
|
149
|
+
end
|
115
150
|
end
|
116
151
|
end
|
117
152
|
end
|
@@ -3,6 +3,7 @@ require 'perpetuity/reference'
|
|
3
3
|
module Perpetuity
|
4
4
|
describe Reference do
|
5
5
|
let(:reference) { Reference.new Object, 1 }
|
6
|
+
let(:object) { double('Object', class: Object, id: 1) }
|
6
7
|
subject { reference }
|
7
8
|
|
8
9
|
its(:klass) { should be Object }
|
@@ -11,8 +12,10 @@ module Perpetuity
|
|
11
12
|
describe 'comparability' do
|
12
13
|
describe 'equality' do
|
13
14
|
let(:duplicate) { reference.dup }
|
15
|
+
|
14
16
|
it { should be == duplicate }
|
15
17
|
it { should eql duplicate }
|
18
|
+
it { should be == object }
|
16
19
|
end
|
17
20
|
|
18
21
|
describe 'inequality' do
|
@@ -20,6 +23,7 @@ module Perpetuity
|
|
20
23
|
it { should_not eql Reference.new(String, reference.id) }
|
21
24
|
it { should_not be == Reference.new(reference.klass, 2) }
|
22
25
|
it { should_not eql Reference.new(reference.klass, 2) }
|
26
|
+
it { should_not be_eql object }
|
23
27
|
end
|
24
28
|
end
|
25
29
|
end
|
data/spec/perpetuity_spec.rb
CHANGED
@@ -17,29 +17,36 @@ describe Perpetuity do
|
|
17
17
|
mapper = Perpetuity[Object]
|
18
18
|
object = Object.new
|
19
19
|
mapper.insert object
|
20
|
-
object.
|
20
|
+
mapper.id_for(object).should be == object.object_id + 1
|
21
21
|
mapper.attributes.should eq [:object_id]
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
25
|
describe 'methods on mappers' do
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
26
|
+
let(:published) { Article.new('Published', 'I love cats', nil, Time.now - 30) }
|
27
|
+
let(:draft) { Article.new('Draft', 'I do not like moose', nil, nil) }
|
28
|
+
let(:not_yet_published) { Article.new('Tomorrow', 'Dogs', nil, Time.now + 30) }
|
29
|
+
let(:mapper) { Perpetuity[Article] }
|
30
|
+
|
31
|
+
# Counting on late-bound memoization for this.
|
32
|
+
let(:published_id) { mapper.id_for(published) }
|
33
|
+
let(:draft_id) { mapper.id_for(draft) }
|
34
|
+
let(:not_yet_published_id) { mapper.id_for(not_yet_published) }
|
30
35
|
|
31
|
-
|
36
|
+
before do
|
32
37
|
mapper.insert published
|
33
38
|
mapper.insert draft
|
34
39
|
mapper.insert not_yet_published
|
40
|
+
end
|
35
41
|
|
36
|
-
|
37
|
-
published_ids
|
38
|
-
published_ids.
|
42
|
+
it 'allows methods to act as scopes' do
|
43
|
+
published_ids = mapper.published.to_a.map { |article| mapper.id_for(article) }
|
44
|
+
published_ids.should include published_id
|
45
|
+
published_ids.should_not include draft_id, not_yet_published_id
|
39
46
|
|
40
|
-
unpublished_ids = mapper.unpublished.to_a.map(
|
41
|
-
unpublished_ids.should_not include
|
42
|
-
unpublished_ids.should include
|
47
|
+
unpublished_ids = mapper.unpublished.to_a.map { |article| mapper.id_for(article) }
|
48
|
+
unpublished_ids.should_not include published_id
|
49
|
+
unpublished_ids.should include draft_id, not_yet_published_id
|
43
50
|
end
|
44
51
|
end
|
45
52
|
end
|