perpetuity 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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', id: 1, class: Object) }
9
- let(:second) { double('Object', id: 2, class: 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
- before do
17
- registry.should_receive(:[]).with(Object) { mapper }
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', id: 1, class: 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
- object.define_singleton_method(:id) { 1 }
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!(:book_mapper_class) do
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!(:user_mapper_class) do
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(mapper_registry[Book]) }
32
+ let(:serializer) { Serializer.new(book_mapper) }
32
33
 
33
34
  before do
34
- dave.extend PersistedObject
35
- andy.extend PersistedObject
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.id
51
+ 'id' => user_mapper.id_for(dave)
51
52
  }
52
53
  },
53
54
  {
54
55
  '__metadata__' => {
55
56
  'class' => 'User',
56
- 'id' => andy.id
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
@@ -17,29 +17,36 @@ describe Perpetuity do
17
17
  mapper = Perpetuity[Object]
18
18
  object = Object.new
19
19
  mapper.insert object
20
- object.id.should eq object.object_id + 1
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
- it 'allows methods to act as scopes' do
27
- published = Article.new('Published', 'I love cats', nil, Time.now - 30)
28
- draft = Article.new('Draft', 'I do not like moose', nil, nil)
29
- not_yet_published = Article.new('Tomorrow', 'Dogs', nil, Time.now + 30)
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
- mapper = Perpetuity[Article]
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
- published_ids = mapper.published.to_a.map(&:id)
37
- published_ids.should include published.id
38
- published_ids.should_not include draft.id, not_yet_published.id
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(&:id)
41
- unpublished_ids.should_not include published.id
42
- unpublished_ids.should include draft.id, not_yet_published.id
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
@@ -1,3 +1,9 @@
1
1
  class Car
2
2
  attr_accessor :make, :model, :seats
3
+
4
+ def initialize attributes={}
5
+ attributes.each do |attr, value|
6
+ public_send "#{attr}=", value
7
+ end
8
+ end
3
9
  end