perpetuity 0.4.7 → 0.4.8

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.
@@ -1,21 +1,24 @@
1
1
  require 'spec_helper'
2
2
  require 'support/test_classes'
3
+ require 'securerandom'
3
4
 
4
5
  describe "retrieval" do
6
+ let(:mapper) { Perpetuity[Article] }
7
+
5
8
  it "gets all the objects of a class" do
6
- expect { Perpetuity[Article].insert Article.new }.
7
- to change { Perpetuity[Article].all.count }.by 1
9
+ expect { mapper.insert Article.new }.
10
+ to change { mapper.all.count }.by 1
8
11
  end
9
12
 
10
13
  it "has an ID when retrieved" do
11
- Perpetuity[Article].insert Article.new
12
- Perpetuity[Article].first.should respond_to :id
14
+ mapper.insert Article.new
15
+ mapper.first.should respond_to :id
13
16
  end
14
17
 
15
18
  it "gets an item with a specific ID" do
16
19
  article = Article.new
17
- Perpetuity[Article].insert article
18
- retrieved = Perpetuity[Article].find(article.id)
20
+ mapper.insert article
21
+ retrieved = mapper.find(article.id)
19
22
 
20
23
  retrieved.id.should eq article.id
21
24
  retrieved.title.should eq article.title
@@ -28,94 +31,109 @@ describe "retrieval" do
28
31
  let(:third) { Article.new('Third', '', nil, Time.now) }
29
32
 
30
33
  before do
31
- Perpetuity[Article].delete_all
32
- [second, third, first].each { |article| Perpetuity[Article].insert article }
34
+ mapper.delete_all
35
+ [second, third, first].each { |article| mapper.insert article }
33
36
  end
34
37
 
35
38
  it 'sorts results' do
36
- titles = Perpetuity[Article].all.sort(:published_at).map(&:title)
39
+ titles = mapper.all.sort(:published_at).map(&:title)
37
40
  titles.should be == %w(First Second Third)
38
41
  end
39
42
 
40
43
  it 'reverse-sorts results' do
41
- titles = Perpetuity[Article].all.sort(:published_at).reverse.map(&:title)
44
+ titles = mapper.all.sort(:published_at).reverse.map(&:title)
42
45
  titles.should be == %w(Third Second First)
43
46
  end
44
47
  end
45
48
 
46
49
  it 'limits result set' do
47
- 5.times { Perpetuity[Article].insert Article.new }
48
- Perpetuity[Article].all.limit(4).should have(4).items
50
+ 5.times { mapper.insert Article.new }
51
+ mapper.all.limit(4).to_a.should have(4).items
49
52
  end
50
53
 
51
54
  describe "Array-like syntax" do
52
55
  let(:draft) { Article.new 'Draft', 'draft content', nil, Time.now + 30 }
53
56
  let(:published) { Article.new 'Published', 'content', nil, Time.now - 30, 3 }
54
57
  before do
55
- Perpetuity[Article].insert draft
56
- Perpetuity[Article].insert published
58
+ mapper.insert draft
59
+ mapper.insert published
57
60
  end
58
61
 
59
62
  it 'selects objects using equality' do
60
- selected = Perpetuity[Article].select { |article| article.title == 'Published' }
63
+ selected = mapper.select { |article| article.title == 'Published' }
61
64
  selected.map(&:id).should include published.id
62
65
  selected.map(&:id).should_not include draft.id
63
66
  end
64
67
 
65
68
  it 'selects objects using greater-than' do
66
- selected = Perpetuity[Article].select { |article| article.published_at < Time.now }
69
+ selected = mapper.select { |article| article.published_at < Time.now }
67
70
  ids = selected.map(&:id)
68
71
  ids.should include published.id
69
72
  ids.should_not include draft.id
70
73
  end
71
74
 
72
75
  it 'selects objects using greater-than-or-equal' do
73
- selected = Perpetuity[Article].select { |article| article.views >= 3 }
76
+ selected = mapper.select { |article| article.views >= 3 }
74
77
  ids = selected.map(&:id)
75
78
  ids.should include published.id
76
79
  ids.should_not include draft.id
77
80
  end
78
81
 
79
82
  it 'selects objects using less-than' do
80
- selected = Perpetuity[Article].select { |article| article.views < 3 }
83
+ selected = mapper.select { |article| article.views < 3 }
81
84
  ids = selected.map(&:id)
82
85
  ids.should include draft.id
83
86
  ids.should_not include published.id
84
87
  end
85
88
 
86
89
  it 'selects objects using less-than-or-equal' do
87
- selected = Perpetuity[Article].select { |article| article.views <= 0 }
90
+ selected = mapper.select { |article| article.views <= 0 }
88
91
  ids = selected.map(&:id)
89
92
  ids.should include draft.id
90
93
  ids.should_not include published.id
91
94
  end
92
95
 
93
96
  it 'selects objects using inequality' do
94
- selected = Perpetuity[Article].select { |article| article.title != 'Draft' }
97
+ selected = mapper.select { |article| article.title != 'Draft' }
95
98
  ids = selected.map(&:id)
96
99
  ids.should_not include draft.id
97
100
  ids.should include published.id
98
101
  end
99
102
 
100
103
  it 'selects objects using regular expressions' do
101
- selected = Perpetuity[Article].select { |article| article.title =~ /Pub/ }
104
+ selected = mapper.select { |article| article.title =~ /Pub/ }
102
105
  ids = selected.map(&:id)
103
106
  ids.should include published.id
104
107
  ids.should_not include draft.id
105
108
  end
106
109
 
107
110
  it 'selects objects using inclusion' do
108
- selected = Perpetuity[Article].select { |article| article.title.in %w( Published ) }
111
+ selected = mapper.select { |article| article.title.in %w( Published ) }
109
112
  ids = selected.map(&:id)
110
113
  ids.should include published.id
111
114
  ids.should_not include draft.id
112
115
  end
113
116
  end
114
117
 
118
+ describe 'counting results' do
119
+ let(:title) { SecureRandom.hex }
120
+ let(:articles) do
121
+ 5.times.map { Article.new(title) } + 5.times.map { Article.new }
122
+ end
123
+
124
+ before do
125
+ articles.each { |article| mapper.insert article }
126
+ end
127
+
128
+ it 'counts the results' do
129
+ query = mapper.select { |article| article.title == title }
130
+ query.count.should == 5
131
+ end
132
+ end
133
+
115
134
  context 'with namespaced classes' do
116
135
  let(:article) { Article.new }
117
136
  let(:person) { CRM::Person.new }
118
- let(:mapper) { Perpetuity[Article] }
119
137
 
120
138
  before { article.author = person }
121
139
 
@@ -38,6 +38,24 @@ describe 'updating' do
38
38
  retrieved_article = mapper.find(retrieved_article.id)
39
39
  retrieved_article.author.should be_a Perpetuity::Reference
40
40
  end
41
+
42
+ it 'updates an object with an array of referenced attributes' do
43
+ dave = User.new('Dave')
44
+ andy = User.new('Andy')
45
+ authors = [dave]
46
+ book = Book.new("Title #{Time.now.to_f}", authors)
47
+ mapper = Perpetuity[Book]
48
+
49
+ mapper.insert book
50
+
51
+ retrieved_book = mapper.find(book.id)
52
+ retrieved_book.authors << andy
53
+ mapper.save retrieved_book
54
+
55
+ retrieved_authors = mapper.find(retrieved_book.id).authors
56
+ retrieved_authors.map(&:klass).should == [User, User]
57
+ retrieved_authors.map(&:id).should == [dave.id, andy.id]
58
+ end
41
59
  end
42
60
 
43
61
 
@@ -0,0 +1,23 @@
1
+ require 'perpetuity/dereferencer'
2
+ require 'perpetuity/reference'
3
+
4
+ module Perpetuity
5
+ describe Dereferencer do
6
+ let(:registry) { double('Mapper Registry') }
7
+ let(:mapper) { double('ObjectMapper') }
8
+ let(:object) { double('Object', id: 1, class: Object) }
9
+ let(:reference) { Reference.new(Object, 1) }
10
+ let(:objects) { [object] }
11
+
12
+ before do
13
+ registry.should_receive(:[]).with(Object) { mapper }
14
+ mapper.should_receive(:select) { objects }
15
+ end
16
+
17
+ it 'loads objects based on the specified objects and attribute' do
18
+ derefer = Dereferencer.new(registry)
19
+ derefer.load reference
20
+ derefer[reference].should == object
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ require 'perpetuity/identity_map'
2
+
3
+ module Perpetuity
4
+ describe IdentityMap do
5
+ context 'when the object exists in the IdentityMap' do
6
+ let(:object) { double('Object', id: 1, class: Object) }
7
+
8
+ it 'returns the object with the given class and id' do
9
+ id_map = IdentityMap.new
10
+ id_map << object
11
+ id_map[Object, 1].should == object
12
+ end
13
+ end
14
+
15
+ context 'when the object does not exist in the IdentityMap' do
16
+ it 'returns nil' do
17
+ id_map = IdentityMap.new
18
+ id_map[Object, 1].should be_nil
19
+ end
20
+ end
21
+ end
22
+ end
@@ -30,25 +30,6 @@ module Perpetuity
30
30
  registry[Object].should be_instance_of mapper_class
31
31
  end
32
32
 
33
- context 'with unserializable embedded attributes' do
34
- let(:unserializable_object) { 1.to_c }
35
- let(:serialized_attrs) do
36
- [ Marshal.dump(unserializable_object) ]
37
- end
38
-
39
- it 'serializes attributes' do
40
- object = Object.new
41
- object.instance_variable_set '@sub_objects', [unserializable_object]
42
- mapper_class.attribute :sub_objects, embedded: true
43
- mapper_class.map Object, registry
44
- data_source = double(:data_source)
45
- mapper.stub(data_source: data_source)
46
- data_source.should_receive(:can_serialize?).with(unserializable_object).and_return false
47
-
48
- mapper.serialize(object)['sub_objects'].should eq serialized_attrs
49
- end
50
- end
51
-
52
33
  describe 'talking to the data source' do
53
34
  let(:data_source) { MongoDB.new(db: nil) }
54
35
  before do
@@ -0,0 +1,117 @@
1
+ require 'perpetuity/mongodb/serializer'
2
+ require 'perpetuity/mapper'
3
+ require 'perpetuity/mapper_registry'
4
+ require 'support/test_classes/book'
5
+ require 'support/test_classes/user'
6
+
7
+ module Perpetuity
8
+ class MongoDB
9
+ describe Serializer do
10
+ let(:dave) { User.new('Dave') }
11
+ let(:andy) { User.new('Andy') }
12
+ let(:authors) { [dave, andy] }
13
+ let(:book) { Book.new('The Pragmatic Programmer', authors) }
14
+ let(:mapper_registry) { MapperRegistry.new }
15
+ let!(:book_mapper_class) do
16
+ registry = mapper_registry
17
+ Class.new(Perpetuity::Mapper) do
18
+ map Book, registry
19
+ attribute :title
20
+ attribute :authors
21
+ end
22
+ end
23
+ let!(:user_mapper_class) do
24
+ registry = mapper_registry
25
+ Class.new(Perpetuity::Mapper) do
26
+ map User, registry
27
+ attribute :name
28
+ end
29
+ end
30
+ let(:data_source) { double('Data Source') }
31
+ let(:serializer) { Serializer.new(mapper_registry[Book]) }
32
+
33
+ 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)
38
+ end
39
+
40
+ it 'serializes an array of non-embedded attributes as references' do
41
+ data_source.should_receive(:can_serialize?).with(book.title).and_return true
42
+ data_source.should_receive(:can_serialize?).with(dave).and_return false
43
+ data_source.should_receive(:can_serialize?).with(andy).and_return false
44
+ serializer.serialize(book).should be == {
45
+ 'title' => book.title,
46
+ 'authors' => [
47
+ {
48
+ '__metadata__' => {
49
+ 'class' => 'User',
50
+ 'id' => dave.id
51
+ }
52
+ },
53
+ {
54
+ '__metadata__' => {
55
+ 'class' => 'User',
56
+ 'id' => andy.id
57
+ }
58
+ }
59
+ ]
60
+ }
61
+ end
62
+
63
+ context 'with objects that have hashes as attributes' do
64
+ let(:name_data) { {first_name: 'Jamie', last_name: 'Gaskins'} }
65
+ let(:serialized_data) do
66
+ {
67
+ 'name' => name_data
68
+ }
69
+ end
70
+ let(:user) { User.new(name_data) }
71
+ let(:user_mapper) { mapper_registry[User] }
72
+ let(:user_serializer) { Serializer.new(user_mapper) }
73
+
74
+ before do
75
+ data_source.stub(:can_serialize?).with(name_data) { true }
76
+ end
77
+
78
+ it 'serializes' do
79
+ user_serializer.serialize(user).should be == serialized_data
80
+ end
81
+
82
+ it 'unserializes' do
83
+ user_serializer.unserialize(serialized_data).name.should be == user.name
84
+ end
85
+ end
86
+
87
+ describe 'unserializes attributes' do
88
+ let(:unserializable_object) { 1.to_c }
89
+ let(:serialized_attrs) { [ Marshal.dump(unserializable_object) ] }
90
+ let(:objects) { serializer.unserialize(serialized_attrs) }
91
+ subject { objects.first }
92
+
93
+ it { should be_a Complex }
94
+ it { should eq unserializable_object}
95
+ end
96
+
97
+ describe 'with an array of references' do
98
+ let(:author) { Reference.new(User, 1) }
99
+ let(:title) { 'title' }
100
+ let(:book) { Book.new(title, [author]) }
101
+
102
+ it 'passes the reference unserialized' do
103
+ data_source.should_receive(:can_serialize?).with('title') { true }
104
+ serializer.serialize(book).should == {
105
+ 'title' => title,
106
+ 'authors' => [{
107
+ '__metadata__' => {
108
+ 'class' => author.klass.to_s,
109
+ 'id' => author.id
110
+ }
111
+ }]
112
+ }
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -49,22 +49,16 @@ module Perpetuity
49
49
 
50
50
  it 'retrieves data from the data source' do
51
51
  return_data = { id: 0, a: 1, b: 2 }
52
+ return_object = Object.new
53
+ return_object.stub(id: return_data[:id])
52
54
  options = { attribute: nil, direction: nil, limit: nil, page: nil }
55
+
53
56
  data_source.should_receive(:retrieve).with(Object, {}, options).
54
57
  and_return([return_data])
58
+ data_source.should_receive(:unserialize).with([return_data], mapper) { [return_object] }
55
59
  results = retrieval.to_a
56
60
 
57
61
  results.map(&:id).should == [0]
58
62
  end
59
-
60
- describe 'unserializes attributes' do
61
- let(:unserializable_object) { 1.to_c }
62
- let(:serialized_attrs) { [ Marshal.dump(unserializable_object) ] }
63
- let(:comments) { retrieval.unserialize(serialized_attrs) }
64
- subject { comments.first }
65
-
66
- it { should be_a Complex }
67
- it { should eq unserializable_object}
68
- end
69
63
  end
70
64
  end
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,3 @@
1
1
  require 'perpetuity'
2
2
 
3
- Perpetuity.configure do
4
- data_source Perpetuity::MongoDB.new db: 'perpetuity_gem_test'
5
- end
6
-
3
+ Perpetuity.data_source :mongodb, 'perpetuity_gem_test'
@@ -0,0 +1,3 @@
1
+ class GenericObject
2
+ attr_accessor :referenced_attribute, :embedded_attribute
3
+ end
@@ -3,4 +3,8 @@ class User
3
3
  def initialize name="Foo"
4
4
  @name = name
5
5
  end
6
+
7
+ def == other
8
+ name == other.name
9
+ end
6
10
  end
@@ -1,4 +1,4 @@
1
- %w( user article comment book message topic car crm_person).each do |file|
1
+ %w( user article comment book message topic car crm_person generic_object).each do |file|
2
2
  require "support/test_classes/#{file}"
3
3
  end
4
4
 
@@ -59,3 +59,8 @@ end
59
59
  Perpetuity.generate_mapper_for CRM::Person do
60
60
  attribute :name
61
61
  end
62
+
63
+ Perpetuity.generate_mapper_for GenericObject do
64
+ attribute :referenced_attribute
65
+ attribute :embedded_attribute, embedded: true
66
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: perpetuity
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.7
4
+ version: 0.4.8
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,14 +9,14 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-03-25 00:00:00.000000000 Z
12
+ date: 2013-05-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
- - - ! '>='
19
+ - - '>='
20
20
  - !ruby/object:Gem::Version
21
21
  version: '0'
22
22
  type: :development
@@ -24,7 +24,7 @@ dependencies:
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  none: false
26
26
  requirements:
27
- - - ! '>='
27
+ - - '>='
28
28
  - !ruby/object:Gem::Version
29
29
  version: '0'
30
30
  - !ruby/object:Gem::Dependency
@@ -48,7 +48,7 @@ dependencies:
48
48
  requirement: !ruby/object:Gem::Requirement
49
49
  none: false
50
50
  requirements:
51
- - - ! '>='
51
+ - - '>='
52
52
  - !ruby/object:Gem::Version
53
53
  version: '0'
54
54
  type: :runtime
@@ -56,7 +56,7 @@ dependencies:
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  none: false
58
58
  requirements:
59
- - - ! '>='
59
+ - - '>='
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  description: Persistence layer for Ruby objects
@@ -79,6 +79,7 @@ files:
79
79
  - lib/perpetuity/attribute_set.rb
80
80
  - lib/perpetuity/config.rb
81
81
  - lib/perpetuity/data_injectable.rb
82
+ - lib/perpetuity/dereferencer.rb
82
83
  - lib/perpetuity/exceptions/duplicate_key_error.rb
83
84
  - lib/perpetuity/identity_map.rb
84
85
  - lib/perpetuity/mapper.rb
@@ -90,10 +91,10 @@ files:
90
91
  - lib/perpetuity/mongodb/query_expression.rb
91
92
  - lib/perpetuity/mongodb/query_intersection.rb
92
93
  - lib/perpetuity/mongodb/query_union.rb
94
+ - lib/perpetuity/mongodb/serializer.rb
93
95
  - lib/perpetuity/persisted_object.rb
94
96
  - lib/perpetuity/reference.rb
95
97
  - lib/perpetuity/retrieval.rb
96
- - lib/perpetuity/serializer.rb
97
98
  - lib/perpetuity/validations.rb
98
99
  - lib/perpetuity/validations/length.rb
99
100
  - lib/perpetuity/validations/presence.rb
@@ -114,6 +115,8 @@ files:
114
115
  - spec/perpetuity/attribute_spec.rb
115
116
  - spec/perpetuity/config_spec.rb
116
117
  - spec/perpetuity/data_injectable_spec.rb
118
+ - spec/perpetuity/dereferencer_spec.rb
119
+ - spec/perpetuity/identity_map_spec.rb
117
120
  - spec/perpetuity/mapper_registry_spec.rb
118
121
  - spec/perpetuity/mapper_spec.rb
119
122
  - spec/perpetuity/mongodb/index_spec.rb
@@ -122,10 +125,10 @@ files:
122
125
  - spec/perpetuity/mongodb/query_intersection_spec.rb
123
126
  - spec/perpetuity/mongodb/query_spec.rb
124
127
  - spec/perpetuity/mongodb/query_union_spec.rb
128
+ - spec/perpetuity/mongodb/serializer_spec.rb
125
129
  - spec/perpetuity/persisted_object_spec.rb
126
130
  - spec/perpetuity/reference_spec.rb
127
131
  - spec/perpetuity/retrieval_spec.rb
128
- - spec/perpetuity/serializer_spec.rb
129
132
  - spec/perpetuity/validations/length_spec.rb
130
133
  - spec/perpetuity/validations/presence_spec.rb
131
134
  - spec/perpetuity/validations_spec.rb
@@ -137,6 +140,7 @@ files:
137
140
  - spec/support/test_classes/car.rb
138
141
  - spec/support/test_classes/comment.rb
139
142
  - spec/support/test_classes/crm_person.rb
143
+ - spec/support/test_classes/generic_object.rb
140
144
  - spec/support/test_classes/message.rb
141
145
  - spec/support/test_classes/topic.rb
142
146
  - spec/support/test_classes/user.rb
@@ -149,21 +153,21 @@ require_paths:
149
153
  required_ruby_version: !ruby/object:Gem::Requirement
150
154
  none: false
151
155
  requirements:
152
- - - ! '>='
156
+ - - '>='
153
157
  - !ruby/object:Gem::Version
154
158
  version: '0'
155
159
  segments:
156
160
  - 0
157
- hash: 3471626582421512512
161
+ hash: -66928365718597043
158
162
  required_rubygems_version: !ruby/object:Gem::Requirement
159
163
  none: false
160
164
  requirements:
161
- - - ! '>='
165
+ - - '>='
162
166
  - !ruby/object:Gem::Version
163
167
  version: '0'
164
168
  segments:
165
169
  - 0
166
- hash: 3471626582421512512
170
+ hash: -66928365718597043
167
171
  requirements: []
168
172
  rubyforge_project:
169
173
  rubygems_version: 1.8.25
@@ -185,6 +189,8 @@ test_files:
185
189
  - spec/perpetuity/attribute_spec.rb
186
190
  - spec/perpetuity/config_spec.rb
187
191
  - spec/perpetuity/data_injectable_spec.rb
192
+ - spec/perpetuity/dereferencer_spec.rb
193
+ - spec/perpetuity/identity_map_spec.rb
188
194
  - spec/perpetuity/mapper_registry_spec.rb
189
195
  - spec/perpetuity/mapper_spec.rb
190
196
  - spec/perpetuity/mongodb/index_spec.rb
@@ -193,10 +199,10 @@ test_files:
193
199
  - spec/perpetuity/mongodb/query_intersection_spec.rb
194
200
  - spec/perpetuity/mongodb/query_spec.rb
195
201
  - spec/perpetuity/mongodb/query_union_spec.rb
202
+ - spec/perpetuity/mongodb/serializer_spec.rb
196
203
  - spec/perpetuity/persisted_object_spec.rb
197
204
  - spec/perpetuity/reference_spec.rb
198
205
  - spec/perpetuity/retrieval_spec.rb
199
- - spec/perpetuity/serializer_spec.rb
200
206
  - spec/perpetuity/validations/length_spec.rb
201
207
  - spec/perpetuity/validations/presence_spec.rb
202
208
  - spec/perpetuity/validations_spec.rb
@@ -208,6 +214,7 @@ test_files:
208
214
  - spec/support/test_classes/car.rb
209
215
  - spec/support/test_classes/comment.rb
210
216
  - spec/support/test_classes/crm_person.rb
217
+ - spec/support/test_classes/generic_object.rb
211
218
  - spec/support/test_classes/message.rb
212
219
  - spec/support/test_classes/topic.rb
213
220
  - spec/support/test_classes/user.rb