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.
@@ -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