perpetuity 0.5.0 → 0.6.0
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/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
|