perpetuity 1.0.0.beta5 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -7
- data/.travis.yml +3 -0
- data/CHANGELOG.md +7 -0
- data/README.md +1 -1
- data/lib/perpetuity.rb +4 -0
- data/lib/perpetuity/config.rb +15 -0
- data/lib/perpetuity/mapper.rb +60 -37
- data/lib/perpetuity/mapper_registry.rb +2 -2
- data/lib/perpetuity/rails_model.rb +13 -0
- data/lib/perpetuity/retrieval.rb +19 -4
- data/lib/perpetuity/version.rb +1 -1
- data/spec/integration/deletion_spec.rb +7 -0
- data/spec/integration/retrieval_spec.rb +14 -0
- data/spec/perpetuity/config_spec.rb +4 -0
- data/spec/perpetuity/mapper_spec.rb +26 -12
- data/spec/perpetuity/rails_model_spec.rb +27 -0
- data/spec/perpetuity/retrieval_spec.rb +24 -2
- data/spec/spec_helper.rb +4 -7
- metadata +41 -45
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
---
|
2
|
-
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
5
|
-
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1a8e9f40c8e01e35ae70dc25cfed9e6e32eaffa8
|
4
|
+
data.tar.gz: 6115b3220a2c680626351aa1c1bb6626d9da73ed
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a216fe83851f3a6e7b7a6839fd76733dc4695cd7f73219d2d94b6bb159f111ae370e90f7befe837e460b1f40f5ddee0a05be43d0c5d8c9f9b1e7b428d07827fb
|
7
|
+
data.tar.gz: 015afc0afc78719af382d44610f969b3936204004b57a76fff8bf29069b12997ab47a5ed67d68ac80f4daa7f17f8c66f006cd7da511bf43e454e2705487cc068
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
## Version 1.0.0
|
2
|
+
|
3
|
+
- Invoke identity map when using `mapper.select` and `mapper.find` with a block.
|
4
|
+
- When finding objects with an array of ids, if they are all contained in the identity map, we don't issue a query.
|
5
|
+
- Allow indexes on multiple attributes.
|
6
|
+
- Add support to `Perpetuity::RailsModel` for ActiveModel's `human` class method.
|
7
|
+
|
1
8
|
## Version 1.0.0.beta5
|
2
9
|
|
3
10
|
- Don't stringify ids in `IdentityMap`. This should result in a little better performance. To be honest, I'm not even sure anymore why I put that in there.
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Perpetuity is a simple Ruby object persistence layer that attempts to follow Martin Fowler's Data Mapper pattern, allowing you to use plain-old Ruby objects in your Ruby apps in order to decouple your domain logic from the database as well as speed up your tests. There is no need for your model classes to inherit from another class or even include a mix-in.
|
4
4
|
|
5
|
-
Your objects will hopefully eventually be able to be persisted into whichever database you like. Right now,
|
5
|
+
Your objects will hopefully eventually be able to be persisted into whichever database you like. Right now, there are only a [PostgreSQL adapter](https://github.com/jgaskins/perpetuity-postgres) and a [MongoDB adapter](https://github.com/jgaskins/perpetuity-mongodb). Other persistence solutions will come later.
|
6
6
|
|
7
7
|
## How it works
|
8
8
|
|
data/lib/perpetuity.rb
CHANGED
@@ -32,6 +32,10 @@ module Perpetuity
|
|
32
32
|
configure { data_source *args }
|
33
33
|
end
|
34
34
|
|
35
|
+
def self.logger
|
36
|
+
configuration.logger
|
37
|
+
end
|
38
|
+
|
35
39
|
def self.register_adapter adapters
|
36
40
|
config_adapters = Perpetuity::Configuration.adapters
|
37
41
|
adapters.each do |adapter_name, adapter_class|
|
data/lib/perpetuity/config.rb
CHANGED
@@ -1,7 +1,13 @@
|
|
1
|
+
require 'logger'
|
1
2
|
require 'uri'
|
2
3
|
|
3
4
|
module Perpetuity
|
4
5
|
class Configuration
|
6
|
+
def initialize
|
7
|
+
@logger = Logger.new(STDOUT)
|
8
|
+
@logger.progname = 'Perpetuity'
|
9
|
+
end
|
10
|
+
|
5
11
|
def data_source *args
|
6
12
|
if args.any?
|
7
13
|
db = args.first
|
@@ -23,6 +29,15 @@ module Perpetuity
|
|
23
29
|
@db
|
24
30
|
end
|
25
31
|
|
32
|
+
def logger *args
|
33
|
+
if args.any?
|
34
|
+
raise ArgumentError, 'Perpetuity::Configuration#logger takes 0..1 arguments'
|
35
|
+
@logger = args.first
|
36
|
+
end
|
37
|
+
|
38
|
+
@logger
|
39
|
+
end
|
40
|
+
|
26
41
|
def data_source_from_url *args
|
27
42
|
uri = args.shift
|
28
43
|
options = args.shift || {}
|
data/lib/perpetuity/mapper.rb
CHANGED
@@ -9,6 +9,9 @@ module Perpetuity
|
|
9
9
|
class Mapper
|
10
10
|
include DataInjectable
|
11
11
|
attr_reader :mapper_registry, :identity_map, :dirty_tracker
|
12
|
+
class << self
|
13
|
+
attr_accessor :collection_name
|
14
|
+
end
|
12
15
|
|
13
16
|
def initialize registry=Perpetuity.mapper_registry, id_map=IdentityMap.new
|
14
17
|
@mapper_registry = registry
|
@@ -19,6 +22,7 @@ module Perpetuity
|
|
19
22
|
def self.map klass, registry=Perpetuity.mapper_registry
|
20
23
|
registry[klass] = self
|
21
24
|
@mapped_class = klass
|
25
|
+
collection klass.name
|
22
26
|
end
|
23
27
|
|
24
28
|
def self.attribute_set
|
@@ -34,8 +38,13 @@ module Perpetuity
|
|
34
38
|
attribute_set.map(&:name)
|
35
39
|
end
|
36
40
|
|
37
|
-
def self.index
|
38
|
-
|
41
|
+
def self.index attribute_names, options={}
|
42
|
+
attributes = Array(attribute_names).map { |name| attribute_set[name] }
|
43
|
+
if attributes.one?
|
44
|
+
data_source.index collection_name, attributes.first, options
|
45
|
+
else
|
46
|
+
data_source.index collection_name, attributes, options
|
47
|
+
end
|
39
48
|
end
|
40
49
|
|
41
50
|
def remove_index! index
|
@@ -43,7 +52,7 @@ module Perpetuity
|
|
43
52
|
end
|
44
53
|
|
45
54
|
def indexes
|
46
|
-
data_source.indexes(
|
55
|
+
data_source.indexes(collection_name)
|
47
56
|
end
|
48
57
|
|
49
58
|
def reindex!
|
@@ -54,7 +63,7 @@ module Perpetuity
|
|
54
63
|
end
|
55
64
|
|
56
65
|
def unspecified_indexes
|
57
|
-
active_indexes = data_source.active_indexes(
|
66
|
+
active_indexes = data_source.active_indexes(collection_name)
|
58
67
|
active_but_unspecified_indexes = (active_indexes - indexes)
|
59
68
|
active_but_unspecified_indexes.reject { |index| index.attribute =~ /id/ }
|
60
69
|
end
|
@@ -68,14 +77,14 @@ module Perpetuity
|
|
68
77
|
end
|
69
78
|
|
70
79
|
def delete_all
|
71
|
-
data_source.delete_all
|
80
|
+
data_source.delete_all collection_name
|
72
81
|
end
|
73
82
|
|
74
83
|
def insert object
|
75
84
|
objects = Array(object)
|
76
85
|
serialized_objects = objects.map { |obj| serialize(obj) }
|
77
86
|
|
78
|
-
new_ids = data_source.insert(
|
87
|
+
new_ids = data_source.insert(collection_name, serialized_objects, attribute_set)
|
79
88
|
objects.each_with_index do |obj, index|
|
80
89
|
give_id_to obj, new_ids[index]
|
81
90
|
end
|
@@ -96,7 +105,7 @@ module Perpetuity
|
|
96
105
|
end
|
97
106
|
|
98
107
|
def count &block
|
99
|
-
data_source.count
|
108
|
+
data_source.count collection_name, &block
|
100
109
|
end
|
101
110
|
|
102
111
|
def any? &block
|
@@ -130,41 +139,47 @@ module Perpetuity
|
|
130
139
|
alias :find_all :select
|
131
140
|
|
132
141
|
def find id=nil, &block
|
133
|
-
if block_given?
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
retrieved + from_map
|
146
|
-
else
|
147
|
-
select { |object| object.id == id }.first
|
148
|
-
end
|
149
|
-
|
150
|
-
Array(result).each do |r|
|
151
|
-
identity_map << r
|
152
|
-
dirty_tracker << r
|
153
|
-
end
|
154
|
-
|
155
|
-
result
|
142
|
+
return select(&block).first if block_given?
|
143
|
+
|
144
|
+
result = if id.is_a? Array
|
145
|
+
find_all_by_ids id
|
146
|
+
else
|
147
|
+
identity_map[mapped_class, id] ||
|
148
|
+
select { |object| object.id == id }.first
|
149
|
+
end
|
150
|
+
|
151
|
+
Array(result).each do |r|
|
152
|
+
identity_map << r
|
153
|
+
dirty_tracker << r
|
156
154
|
end
|
155
|
+
|
156
|
+
result
|
157
157
|
end
|
158
158
|
|
159
159
|
alias :detect :find
|
160
160
|
|
161
|
+
def find_all_by_ids ids
|
162
|
+
ids_in_map = ids & identity_map.ids_for(mapped_class)
|
163
|
+
ids_to_select = ids - ids_in_map
|
164
|
+
retrieved = if ids_to_select.any?
|
165
|
+
select { |object| object.id.in ids_to_select }.to_a
|
166
|
+
else
|
167
|
+
[]
|
168
|
+
end
|
169
|
+
from_map = ids_in_map.map { |id| identity_map[mapped_class, id] }
|
170
|
+
|
171
|
+
retrieved.concat from_map
|
172
|
+
end
|
173
|
+
|
161
174
|
def reject &block
|
162
175
|
retrieve data_source.negate_query(&block)
|
163
176
|
end
|
164
177
|
|
165
|
-
def delete
|
166
|
-
|
167
|
-
|
178
|
+
def delete object_or_array
|
179
|
+
ids = Array(object_or_array).map { |object|
|
180
|
+
persisted?(object) ? id_for(object) : object
|
181
|
+
}
|
182
|
+
data_source.delete ids, collection_name
|
168
183
|
end
|
169
184
|
|
170
185
|
def load_association! object, attribute
|
@@ -198,7 +213,7 @@ module Perpetuity
|
|
198
213
|
|
199
214
|
def update object, new_data
|
200
215
|
id = object.is_a?(mapped_class) ? id_for(object) : object
|
201
|
-
data_source.update
|
216
|
+
data_source.update collection_name, id, new_data
|
202
217
|
end
|
203
218
|
|
204
219
|
def save object
|
@@ -212,12 +227,12 @@ module Perpetuity
|
|
212
227
|
|
213
228
|
def increment object, attribute, count=1
|
214
229
|
id = id_for(object) || object
|
215
|
-
data_source.increment
|
230
|
+
data_source.increment collection_name, id, attribute, count
|
216
231
|
end
|
217
232
|
|
218
233
|
def decrement object, attribute, count=1
|
219
234
|
id = id_for(object) || object
|
220
|
-
data_source.increment
|
235
|
+
data_source.increment collection_name, id, attribute, -count
|
221
236
|
end
|
222
237
|
|
223
238
|
def sample
|
@@ -256,14 +271,22 @@ module Perpetuity
|
|
256
271
|
@mapped_class
|
257
272
|
end
|
258
273
|
|
274
|
+
def self.collection name
|
275
|
+
@collection_name = name.to_s
|
276
|
+
end
|
277
|
+
|
259
278
|
def mapped_class
|
260
279
|
self.class.mapped_class
|
261
280
|
end
|
262
281
|
|
282
|
+
def collection_name
|
283
|
+
self.class.collection_name
|
284
|
+
end
|
285
|
+
|
263
286
|
private
|
264
287
|
|
265
288
|
def retrieve query=data_source.query
|
266
|
-
Perpetuity::Retrieval.new self, query
|
289
|
+
Perpetuity::Retrieval.new self, query, identity_map: identity_map
|
267
290
|
end
|
268
291
|
end
|
269
292
|
end
|
@@ -15,8 +15,8 @@ module Perpetuity
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def mapper_for klass, options={}
|
18
|
-
identity_map = options
|
19
|
-
mapper_class(klass).new(self,
|
18
|
+
identity_map = options.fetch(:identity_map) { IdentityMap.new }
|
19
|
+
mapper_class(klass).new(self, identity_map)
|
20
20
|
end
|
21
21
|
|
22
22
|
def mapper_class klass
|
@@ -39,6 +39,19 @@ module Perpetuity
|
|
39
39
|
def singular_route_key
|
40
40
|
param_key
|
41
41
|
end
|
42
|
+
|
43
|
+
def to_partial_path
|
44
|
+
"#{name.downcase}s/_#{name.downcase}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def human
|
48
|
+
if name == name.upcase
|
49
|
+
name.split(/_/).map(&:capitalize).join(' ')
|
50
|
+
else
|
51
|
+
name.gsub(/::|_/, ' ')
|
52
|
+
.gsub(/(\w)([A-Z])/, '\1 \2')
|
53
|
+
end
|
54
|
+
end
|
42
55
|
end
|
43
56
|
end
|
44
57
|
end
|
data/lib/perpetuity/retrieval.rb
CHANGED
@@ -1,14 +1,17 @@
|
|
1
1
|
require 'perpetuity/reference'
|
2
|
+
require 'perpetuity/identity_map'
|
2
3
|
|
3
4
|
module Perpetuity
|
4
5
|
class Retrieval
|
5
6
|
include Enumerable
|
6
7
|
attr_accessor :sort_attribute, :sort_direction, :result_limit, :result_page, :result_offset, :result_cache
|
8
|
+
attr_reader :identity_map
|
7
9
|
|
8
|
-
def initialize mapper, query
|
10
|
+
def initialize mapper, query, options={}
|
9
11
|
@mapper = mapper
|
10
|
-
@
|
12
|
+
@collection_name = mapper.collection_name
|
11
13
|
@query = query
|
14
|
+
@identity_map = options.fetch(:identity_map) { IdentityMap.new }
|
12
15
|
@data_source = mapper.data_source
|
13
16
|
end
|
14
17
|
|
@@ -51,11 +54,23 @@ module Perpetuity
|
|
51
54
|
end
|
52
55
|
|
53
56
|
def to_a
|
54
|
-
@result_cache ||= @data_source.unserialize(@data_source.retrieve(@
|
57
|
+
@result_cache ||= @data_source.unserialize(@data_source.retrieve(@collection_name, @query, options), @mapper)
|
58
|
+
|
59
|
+
@result_cache.map do |result|
|
60
|
+
klass = result.class
|
61
|
+
id = @mapper.id_for(result)
|
62
|
+
|
63
|
+
if cached_result = identity_map[klass, id]
|
64
|
+
cached_result
|
65
|
+
else
|
66
|
+
identity_map << result
|
67
|
+
result
|
68
|
+
end
|
69
|
+
end
|
55
70
|
end
|
56
71
|
|
57
72
|
def count
|
58
|
-
@data_source.count(@
|
73
|
+
@data_source.count(@collection_name, @query)
|
59
74
|
end
|
60
75
|
|
61
76
|
def first
|
data/lib/perpetuity/version.rb
CHANGED
@@ -14,6 +14,13 @@ describe "deletion" do
|
|
14
14
|
}.to change { Perpetuity[Article].count }.by(-1)
|
15
15
|
end
|
16
16
|
|
17
|
+
it 'deletes an array of objects' do
|
18
|
+
mapper = Perpetuity[Article]
|
19
|
+
articles = Array.new(2) { Article.new }
|
20
|
+
mapper.insert articles
|
21
|
+
expect { mapper.delete articles }.to change { mapper.count }.by -2
|
22
|
+
end
|
23
|
+
|
17
24
|
describe "#delete_all" do
|
18
25
|
it "should delete all objects of a certain class" do
|
19
26
|
Perpetuity[Article].insert Article.new
|
@@ -249,4 +249,18 @@ describe "retrieval" do
|
|
249
249
|
results.should_not include article_with_comments
|
250
250
|
end
|
251
251
|
end
|
252
|
+
|
253
|
+
describe 'identity map' do
|
254
|
+
let(:id) { mapper.insert Article.new }
|
255
|
+
|
256
|
+
it 'returns the same object when requested with the same id' do
|
257
|
+
mapper.find(id).should be mapper.find(id)
|
258
|
+
end
|
259
|
+
|
260
|
+
it 'returns the same object when requested with a block' do
|
261
|
+
first = mapper.find { |article| article.id == id }
|
262
|
+
second = mapper.find { |article| article.id == id }
|
263
|
+
first.should be second
|
264
|
+
end
|
265
|
+
end
|
252
266
|
end
|
@@ -47,18 +47,18 @@ module Perpetuity
|
|
47
47
|
obj.instance_variable_set '@my_attribute', 'foo'
|
48
48
|
data_source.should_receive(:can_serialize?).with('foo') { true }
|
49
49
|
data_source.should_receive(:insert)
|
50
|
-
.with(Object, [{ 'my_attribute' => 'foo' }], mapper.attribute_set)
|
50
|
+
.with('Object', [{ 'my_attribute' => 'foo' }], mapper.attribute_set)
|
51
51
|
.and_return(['bar'])
|
52
52
|
|
53
53
|
mapper.insert(obj).should be == 'bar'
|
54
54
|
end
|
55
55
|
|
56
56
|
it 'counts objects of its mapped class in the data source' do
|
57
|
-
data_source.should_receive(:count).with(Object) { 4 }
|
57
|
+
data_source.should_receive(:count).with('Object') { 4 }
|
58
58
|
mapper.count.should be == 4
|
59
59
|
end
|
60
60
|
|
61
|
-
describe 'finding
|
61
|
+
describe 'finding specific objects' do
|
62
62
|
let(:options) { {:attribute=>nil, :direction=>nil, :limit=>1, :skip=>nil} }
|
63
63
|
let(:returned_object) { double('Retrieved Object', class: Object, delete: nil) }
|
64
64
|
|
@@ -66,17 +66,19 @@ module Perpetuity
|
|
66
66
|
returned_object.instance_variable_set :@id, 1
|
67
67
|
criteria = data_source.query { |o| o.id == 1 }
|
68
68
|
data_source.should_receive(:retrieve)
|
69
|
-
.with(Object, criteria, options) { [returned_object] }
|
69
|
+
.with('Object', criteria, options) { [returned_object] }
|
70
70
|
|
71
71
|
mapper.find(1).should be == returned_object
|
72
72
|
end
|
73
73
|
|
74
74
|
it 'finds multiple objects by ID' do
|
75
75
|
first, second = double, double
|
76
|
+
mapper.give_id_to first, 1
|
77
|
+
mapper.give_id_to second, 2
|
76
78
|
criteria = data_source.query { |o| o.id.in [1, 2] }
|
77
79
|
options.merge! limit: nil
|
78
80
|
data_source.should_receive(:retrieve)
|
79
|
-
.with(Object, criteria, options)
|
81
|
+
.with('Object', criteria, options)
|
80
82
|
.and_return [first, second]
|
81
83
|
|
82
84
|
mapper.find([1, 2]).to_a.should be == [first, second]
|
@@ -86,7 +88,7 @@ module Perpetuity
|
|
86
88
|
criteria = data_source.query { |o| o.name == 'foo' }
|
87
89
|
options = self.options.merge(limit: nil)
|
88
90
|
data_source.should_receive(:retrieve)
|
89
|
-
.with(Object, criteria, options) { [returned_object] }.twice
|
91
|
+
.with('Object', criteria, options) { [returned_object] }.twice
|
90
92
|
|
91
93
|
mapper.select { |e| e.name == 'foo' }.to_a.should be == [returned_object]
|
92
94
|
mapper.find_all { |e| e.name == 'foo' }.to_a.should be == [returned_object]
|
@@ -95,7 +97,7 @@ module Perpetuity
|
|
95
97
|
it 'finds an object with a block' do
|
96
98
|
criteria = data_source.query { |o| o.name == 'foo' }
|
97
99
|
data_source.should_receive(:retrieve)
|
98
|
-
.with(Object, criteria, options) { [returned_object] }.twice
|
100
|
+
.with('Object', criteria, options) { [returned_object] }.twice
|
99
101
|
mapper.find { |o| o.name == 'foo' }.should be == returned_object
|
100
102
|
mapper.detect { |o| o.name == 'foo' }.should be == returned_object
|
101
103
|
end
|
@@ -107,7 +109,7 @@ module Perpetuity
|
|
107
109
|
duplicate.stub(class: returned_object.class)
|
108
110
|
returned_object.stub(dup: duplicate)
|
109
111
|
data_source.should_receive(:retrieve)
|
110
|
-
.with(Object, criteria, options) { [returned_object] }
|
112
|
+
.with('Object', criteria, options) { [returned_object] }
|
111
113
|
.once
|
112
114
|
|
113
115
|
mapper.find(1)
|
@@ -117,7 +119,7 @@ module Perpetuity
|
|
117
119
|
it 'does not cache nil results' do
|
118
120
|
criteria = data_source.query { |o| o.id == 1 }
|
119
121
|
data_source.should_receive(:retrieve)
|
120
|
-
.with(Object, criteria, options) { [] }
|
122
|
+
.with('Object', criteria, options) { [] }
|
121
123
|
.twice
|
122
124
|
|
123
125
|
mapper.find(1)
|
@@ -131,7 +133,7 @@ module Perpetuity
|
|
131
133
|
mapper.give_id_to object, 1
|
132
134
|
object.instance_variable_set '@foo', 'bar'
|
133
135
|
data_source.should_receive(:can_serialize?).with('bar') { true }
|
134
|
-
data_source.should_receive(:update).with Object, 1, { 'foo' => 'bar' }
|
136
|
+
data_source.should_receive(:update).with 'Object', 1, { 'foo' => 'bar' }
|
135
137
|
|
136
138
|
mapper.save object
|
137
139
|
end
|
@@ -155,12 +157,12 @@ module Perpetuity
|
|
155
157
|
it 'deletes an object from the data source' do
|
156
158
|
object = Object.new
|
157
159
|
|
158
|
-
data_source.should_receive(:delete).with object, Object
|
160
|
+
data_source.should_receive(:delete).with [object], 'Object'
|
159
161
|
mapper.delete object
|
160
162
|
end
|
161
163
|
|
162
164
|
it 'deletes all objects it manages' do
|
163
|
-
data_source.should_receive(:delete_all).with(Object)
|
165
|
+
data_source.should_receive(:delete_all).with('Object')
|
164
166
|
mapper.delete_all
|
165
167
|
end
|
166
168
|
end
|
@@ -216,5 +218,17 @@ module Perpetuity
|
|
216
218
|
mapper.identity_map.should be id_map
|
217
219
|
end
|
218
220
|
end
|
221
|
+
|
222
|
+
describe 'specifying the collection/table name' do
|
223
|
+
it 'changes the collection name' do
|
224
|
+
mapper_class.collection_name = 'articles'
|
225
|
+
mapper.collection_name.should == 'articles'
|
226
|
+
end
|
227
|
+
|
228
|
+
it 'defaults to the mapped class name' do
|
229
|
+
mapper_class.map Object
|
230
|
+
mapper.collection_name.should == 'Object'
|
231
|
+
end
|
232
|
+
end
|
219
233
|
end
|
220
234
|
end
|
@@ -54,5 +54,32 @@ module Perpetuity
|
|
54
54
|
stub_const 'Foo::Bar', klass
|
55
55
|
Foo::Bar.singular_route_key.should be == 'foo_bar'
|
56
56
|
end
|
57
|
+
|
58
|
+
it 'returns the partial path' do
|
59
|
+
stub_const 'Article', klass
|
60
|
+
Article.to_partial_path.should be == 'articles/_article'
|
61
|
+
end
|
62
|
+
|
63
|
+
describe 'human' do
|
64
|
+
it 'returns the class name for a single-word class name' do
|
65
|
+
stub_const 'Article', klass
|
66
|
+
Article.human.should == 'Article'
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'converts namespaced to capitalized words' do
|
70
|
+
stub_const 'Foo::Bar', klass
|
71
|
+
Foo::Bar.human.should == 'Foo Bar'
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'separates title-cased words' do
|
75
|
+
stub_const 'FooBarBaz', klass
|
76
|
+
FooBarBaz.human.should == 'Foo Bar Baz'
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'separates snake-cased, capitalized words' do
|
80
|
+
stub_const 'FOO_BAR_BAZ', klass
|
81
|
+
FOO_BAR_BAZ.human.should == 'Foo Bar Baz'
|
82
|
+
end
|
83
|
+
end
|
57
84
|
end
|
58
85
|
end
|
@@ -1,10 +1,11 @@
|
|
1
1
|
require 'perpetuity/retrieval'
|
2
|
+
require 'perpetuity/mapper'
|
2
3
|
|
3
4
|
module Perpetuity
|
4
5
|
describe Retrieval do
|
5
6
|
let(:data_source) { double('data_source') }
|
6
7
|
let(:registry) { double('mapper_registry') }
|
7
|
-
let(:mapper) { double(
|
8
|
+
let(:mapper) { double(collection_name: 'Object', data_source: data_source, mapper_registry: registry) }
|
8
9
|
let(:query) { double('Query', to_db: {}) }
|
9
10
|
let(:retrieval) { Perpetuity::Retrieval.new mapper, query }
|
10
11
|
subject { retrieval }
|
@@ -54,9 +55,10 @@ module Perpetuity
|
|
54
55
|
return_object.stub(id: return_data[:id])
|
55
56
|
options = { attribute: nil, direction: nil, limit: nil, skip: nil }
|
56
57
|
|
57
|
-
data_source.should_receive(:retrieve).with(Object, query, options).
|
58
|
+
data_source.should_receive(:retrieve).with('Object', query, options).
|
58
59
|
and_return([return_data])
|
59
60
|
data_source.should_receive(:unserialize).with([return_data], mapper) { [return_object] }
|
61
|
+
mapper.stub(:id_for)
|
60
62
|
results = retrieval.to_a
|
61
63
|
|
62
64
|
results.map(&:id).should == [0]
|
@@ -67,5 +69,25 @@ module Perpetuity
|
|
67
69
|
retrieval.clear_cache
|
68
70
|
retrieval.result_cache.should be_nil
|
69
71
|
end
|
72
|
+
|
73
|
+
describe 'identity map' do
|
74
|
+
let(:id_map) { IdentityMap.new }
|
75
|
+
let(:retrieval) { Retrieval.new(mapper, query, identity_map: id_map) }
|
76
|
+
|
77
|
+
it 'maintains an identity_map' do
|
78
|
+
retrieval.identity_map.should be id_map
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'returns objects from the identity map with queries' do
|
82
|
+
result = Object.new
|
83
|
+
result.instance_variable_set :@id, '1'
|
84
|
+
id_map << result
|
85
|
+
mapper.stub(id_for: '1')
|
86
|
+
data_source.stub(:retrieve)
|
87
|
+
data_source.stub(unserialize: [result.dup])
|
88
|
+
|
89
|
+
retrieval.to_a.should include result
|
90
|
+
end
|
91
|
+
end
|
70
92
|
end
|
71
93
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,9 +1,6 @@
|
|
1
1
|
require 'bundler/setup'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
require 'perpetuity/mongodb'
|
8
|
-
Perpetuity.data_source :mongodb, 'perpetuity_gem_test'
|
9
|
-
end
|
3
|
+
adapter = ENV.fetch('PERPETUITY_ADAPTER') { 'mongodb' }
|
4
|
+
require "perpetuity/#{adapter}"
|
5
|
+
|
6
|
+
Perpetuity.data_source adapter.to_sym, 'perpetuity_gem_test'
|
metadata
CHANGED
@@ -1,50 +1,47 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: perpetuity
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
|
-
authors:
|
6
|
+
authors:
|
7
7
|
- Jamie Gaskins
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
|
12
|
+
date: 2014-08-16 00:00:00 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
14
15
|
name: rake
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - '>='
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '0'
|
20
|
-
type: :development
|
21
16
|
prerelease: false
|
22
|
-
|
23
|
-
requirements:
|
24
|
-
-
|
25
|
-
-
|
26
|
-
|
27
|
-
|
28
|
-
name: rspec
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ~>
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '2.13'
|
17
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- &id003
|
20
|
+
- ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: "0"
|
34
23
|
type: :development
|
24
|
+
version_requirements: *id001
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
35
27
|
prerelease: false
|
36
|
-
|
37
|
-
requirements:
|
28
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
38
30
|
- - ~>
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version:
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: "2.13"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id002
|
41
35
|
description: Persistence layer for Ruby objects
|
42
|
-
email:
|
36
|
+
email:
|
43
37
|
- jgaskins@gmail.com
|
44
38
|
executables: []
|
39
|
+
|
45
40
|
extensions: []
|
41
|
+
|
46
42
|
extra_rdoc_files: []
|
47
|
-
|
43
|
+
|
44
|
+
files:
|
48
45
|
- .gitignore
|
49
46
|
- .rvmrc
|
50
47
|
- .travis.yml
|
@@ -107,30 +104,29 @@ files:
|
|
107
104
|
- spec/support/test_classes/topic.rb
|
108
105
|
- spec/support/test_classes/user.rb
|
109
106
|
homepage: https://github.com/jgaskins/perpetuity
|
110
|
-
licenses:
|
107
|
+
licenses:
|
111
108
|
- MIT
|
112
109
|
metadata: {}
|
110
|
+
|
113
111
|
post_install_message:
|
114
112
|
rdoc_options: []
|
115
|
-
|
113
|
+
|
114
|
+
require_paths:
|
116
115
|
- lib
|
117
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
-
requirements:
|
119
|
-
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
requirements:
|
124
|
-
- - '>'
|
125
|
-
- !ruby/object:Gem::Version
|
126
|
-
version: 1.3.1
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- *id003
|
119
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- *id003
|
127
122
|
requirements: []
|
123
|
+
|
128
124
|
rubyforge_project:
|
129
|
-
rubygems_version: 2.
|
125
|
+
rubygems_version: 2.2.2
|
130
126
|
signing_key:
|
131
127
|
specification_version: 4
|
132
128
|
summary: Persistence library allowing serialization of Ruby objects
|
133
|
-
test_files:
|
129
|
+
test_files:
|
134
130
|
- spec/integration/associations_spec.rb
|
135
131
|
- spec/integration/deletion_spec.rb
|
136
132
|
- spec/integration/enumerable_spec.rb
|