viximo-cache-money 0.3.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/LICENSE +201 -0
- data/README +204 -0
- data/README.markdown +204 -0
- data/TODO +17 -0
- data/UNSUPPORTED_FEATURES +13 -0
- data/config/environment.rb +8 -0
- data/config/memcached.yml +4 -0
- data/db/schema.rb +18 -0
- data/init.rb +1 -0
- data/lib/cache_money.rb +105 -0
- data/lib/cash/accessor.rb +83 -0
- data/lib/cash/adapter/memcache_client.rb +36 -0
- data/lib/cash/adapter/memcached.rb +127 -0
- data/lib/cash/adapter/redis.rb +144 -0
- data/lib/cash/buffered.rb +137 -0
- data/lib/cash/config.rb +78 -0
- data/lib/cash/fake.rb +83 -0
- data/lib/cash/finders.rb +50 -0
- data/lib/cash/index.rb +211 -0
- data/lib/cash/local.rb +105 -0
- data/lib/cash/lock.rb +63 -0
- data/lib/cash/mock.rb +158 -0
- data/lib/cash/query/abstract.rb +219 -0
- data/lib/cash/query/calculation.rb +45 -0
- data/lib/cash/query/primary_key.rb +50 -0
- data/lib/cash/query/select.rb +16 -0
- data/lib/cash/request.rb +3 -0
- data/lib/cash/transactional.rb +43 -0
- data/lib/cash/util/array.rb +9 -0
- data/lib/cash/util/marshal.rb +19 -0
- data/lib/cash/version.rb +3 -0
- data/lib/cash/write_through.rb +71 -0
- data/lib/mem_cached_session_store.rb +49 -0
- data/lib/mem_cached_support_store.rb +143 -0
- data/rails/init.rb +1 -0
- data/spec/cash/accessor_spec.rb +186 -0
- data/spec/cash/active_record_spec.rb +224 -0
- data/spec/cash/buffered_spec.rb +9 -0
- data/spec/cash/calculations_spec.rb +78 -0
- data/spec/cash/finders_spec.rb +455 -0
- data/spec/cash/local_buffer_spec.rb +9 -0
- data/spec/cash/local_spec.rb +9 -0
- data/spec/cash/lock_spec.rb +110 -0
- data/spec/cash/marshal_spec.rb +60 -0
- data/spec/cash/order_spec.rb +172 -0
- data/spec/cash/transactional_spec.rb +602 -0
- data/spec/cash/window_spec.rb +195 -0
- data/spec/cash/without_caching_spec.rb +32 -0
- data/spec/cash/write_through_spec.rb +252 -0
- data/spec/spec_helper.rb +87 -0
- metadata +300 -0
@@ -0,0 +1,455 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Cash
|
4
|
+
describe Finders do
|
5
|
+
describe 'Cache Usage' do
|
6
|
+
describe 'when the cache is populated' do
|
7
|
+
describe '#find' do
|
8
|
+
describe '#find(1)' do
|
9
|
+
it 'does not use the database' do
|
10
|
+
story = Story.create!
|
11
|
+
mock(Story.connection).execute.never
|
12
|
+
Story.find(story.id).should == story
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#find(object)' do
|
17
|
+
it 'uses the objects quoted id' do
|
18
|
+
story = Story.create!
|
19
|
+
mock(Story.connection).execute.never
|
20
|
+
Story.find(story).should == story
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#find(:first, ...)' do
|
25
|
+
describe '#find(:first, :conditions => { :id => ? })' do
|
26
|
+
it "does not use the database" do
|
27
|
+
story = Story.create!
|
28
|
+
mock(Story.connection).execute.never
|
29
|
+
Story.find(:first, :conditions => { :id => story.id }).should == story
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#find(:first, :conditions => [ "id = :id", { :id => story.id } ])' do
|
34
|
+
it "does not use the database" do
|
35
|
+
story = Story.create!
|
36
|
+
mock(Story.connection).execute.never
|
37
|
+
Story.find(:first, :conditions => [ "id = :id", { :id => story.id } ]).should == story
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "#find(:first, :conditions => 'id = ?')" do
|
42
|
+
it "does not use the database" do
|
43
|
+
story = Story.create!
|
44
|
+
mock(Story.connection).execute.never
|
45
|
+
Story.find(:first, :conditions => "id = #{story.id}").should == story
|
46
|
+
Story.find(:first, :conditions => "`stories`.id = #{story.id}").should == story
|
47
|
+
Story.find(:first, :conditions => "`stories`.`id` = #{story.id}").should == story
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe '#find(:first, :readonly => false) and any other options other than conditions are nil' do
|
52
|
+
it "does not use the database" do
|
53
|
+
story = Story.create!
|
54
|
+
mock(Story.connection).execute.never
|
55
|
+
Story.find(:first, :conditions => { :id => story.id }, :readonly => false, :limit => nil, :offset => nil, :joins => nil, :include => nil).should == story
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#find(:first, :readonly => true)' do
|
60
|
+
it "uses the database, not the cache" do
|
61
|
+
story = Story.create!
|
62
|
+
mock(Story).get.never
|
63
|
+
Story.find(:first, :conditions => { :id => story.id }, :readonly => true).should == story
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe '#find(:first, :join => ...) or find(..., :include => ...)' do
|
68
|
+
it "uses the database, not the cache" do
|
69
|
+
story = Story.create!
|
70
|
+
mock(Story).get.never
|
71
|
+
Story.find(:first, :conditions => { :id => story.id }, :joins => 'AS stories').should == story
|
72
|
+
Story.find(:first, :conditions => { :id => story.id }, :include => :characters).should == story
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe '#find(:first)' do
|
77
|
+
it 'uses the database, not the cache' do
|
78
|
+
mock(Story).get.never
|
79
|
+
Story.find(:first)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe '#find(:first, :conditions => "...")' do
|
84
|
+
describe 'on unindexed attributes' do
|
85
|
+
it 'uses the database, not the cache' do
|
86
|
+
story = Story.create!
|
87
|
+
mock(Story).get.never
|
88
|
+
Story.find(:first, :conditions => "type IS NULL")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe 'on indexed attributes' do
|
93
|
+
describe 'when the attributes are integers' do
|
94
|
+
it 'does not use the database' do
|
95
|
+
story = Story.create!
|
96
|
+
mock(Story.connection).execute.never
|
97
|
+
Story.find(:first, :conditions => "`stories`.id = #{story.id}") \
|
98
|
+
.should == story
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe 'when the attributes are non-integers' do
|
103
|
+
it 'uses the database, not the cache' do
|
104
|
+
story = Story.create!(:title => "title")
|
105
|
+
mock(Story.connection).execute.never
|
106
|
+
Story.find(:first, :conditions => "`stories`.title = '#{story.title }'") \
|
107
|
+
.should == story
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe 'when the attributes must be coerced to sql values' do
|
112
|
+
it 'does not use the database' do
|
113
|
+
story1 = Story.create!(:published => true)
|
114
|
+
story2 = Story.create!(:published => false)
|
115
|
+
mock(Story.connection).execute.never
|
116
|
+
Story.find(:first, :conditions => 'published = 0').should == story2
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe '#find(:first, :conditions => [...])' do
|
122
|
+
describe 'with one indexed attribute' do
|
123
|
+
it 'does not use the database' do
|
124
|
+
story = Story.create!
|
125
|
+
mock(Story.connection).execute.never
|
126
|
+
Story.find(:first, :conditions => ['id = ?', story.id]).should == story
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe 'with one indexed attribute and object as param' do
|
131
|
+
it 'does not use the database' do
|
132
|
+
story = Story.create!
|
133
|
+
mock(Story.connection).execute.never
|
134
|
+
Story.find(:first, :conditions => ['id = ?', story]).should == story
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe 'with two attributes that match a combo-index' do
|
139
|
+
it 'does not use the database' do
|
140
|
+
story = Story.create!(:title => 'title')
|
141
|
+
mock(Story.connection).execute.never
|
142
|
+
Story.find(:first, :conditions => ['id = ? AND title = ?', story.id, story.title]).should == story
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe '#find(:first, :conditions => {...})' do
|
149
|
+
it "does not use the database" do
|
150
|
+
story = Story.create!(:title => "Sam")
|
151
|
+
mock(Story.connection).execute.never
|
152
|
+
Story.find(:first, :conditions => { :id => story.id, :title => story.title }).should == story
|
153
|
+
end
|
154
|
+
|
155
|
+
describe 'regardless of hash order' do
|
156
|
+
it 'does not use the database' do
|
157
|
+
story = Story.create!(:title => "Sam")
|
158
|
+
mock(Story.connection).execute.never
|
159
|
+
Story.find(:first, :conditions => { :id => story.id, :title => story.title }).should == story
|
160
|
+
Story.find(:first, :conditions => { :title => story.title, :id => story.id }).should == story
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
describe 'on unindexed attribtes' do
|
165
|
+
it 'uses the database, not the cache' do
|
166
|
+
story = Story.create!
|
167
|
+
mock(Story).get.never
|
168
|
+
Story.find(:first, :conditions => { :id => story.id, :type => story.type }).should == story
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
describe 'when there is a with_scope' do
|
175
|
+
describe 'when the with_scope has conditions' do
|
176
|
+
describe 'when the scope conditions is a string' do
|
177
|
+
it "uses the database, not the cache" do
|
178
|
+
story = Story.create!(:title => title = 'title')
|
179
|
+
mock(Story.connection).execute.never
|
180
|
+
Story.send :with_scope, :find => { :conditions => "title = '#{title}'"} do
|
181
|
+
Story.find(:first, :conditions => { :id => story.id }).should == story
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe 'when the find conditions is a string' do
|
187
|
+
it "does not use the database" do
|
188
|
+
story = Story.create!(:title => title = 'title')
|
189
|
+
mock(Story.connection).execute.never
|
190
|
+
Story.send :with_scope, :find => { :conditions => { :id => story.id }} do
|
191
|
+
Story.find(:first, :conditions => "title = '#{title}'").should == story
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
describe '#find(1, :conditions => ...)' do
|
197
|
+
it "does not use the database" do
|
198
|
+
story = Story.create!
|
199
|
+
character = Character.create!(:name => name = 'barbara', :story_id => story)
|
200
|
+
mock(Character.connection).execute.never
|
201
|
+
Character.send :with_scope, :find => { :conditions => { :story_id => story.id } } do
|
202
|
+
Character.find(character.id, :conditions => { :name => name }).should == character
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe 'has_many associations' do
|
209
|
+
describe '#find(1)' do
|
210
|
+
it "does not use the database" do
|
211
|
+
story = Story.create!
|
212
|
+
character = story.characters.create!
|
213
|
+
mock(Character.connection).execute.never
|
214
|
+
story.characters.find(character.id).should == character
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
describe '#find(1, 2, ...)' do
|
219
|
+
it "does not use the database" do
|
220
|
+
story = Story.create!
|
221
|
+
character1 = story.characters.create!
|
222
|
+
character2 = story.characters.create!
|
223
|
+
mock(Character.connection).execute.never
|
224
|
+
story.characters.find(character1.id, character2.id).should == [character1, character2]
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
describe '#find_by_attr' do
|
229
|
+
it "does not use the database" do
|
230
|
+
story = Story.create!
|
231
|
+
character = story.characters.create!
|
232
|
+
mock(Character.connection).execute.never
|
233
|
+
story.characters.find_by_id(character.id).should == character
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
describe '#find_by_attr_and_object' do
|
238
|
+
it "does not use the database" do
|
239
|
+
story = Story.create!
|
240
|
+
character = story.characters.create!
|
241
|
+
mock(Character.connection).execute.never
|
242
|
+
story.characters.find_by_id(character).should == character
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
describe '#find(:all)' do
|
249
|
+
it "uses the database, not the cache" do
|
250
|
+
character = Character.create!
|
251
|
+
mock(Character).get.never
|
252
|
+
Character.find(:all).should == [character]
|
253
|
+
end
|
254
|
+
|
255
|
+
describe '#find(:all, :conditions => {...})' do
|
256
|
+
describe 'when the index is not empty' do
|
257
|
+
it 'does not use the database' do
|
258
|
+
story1 = Story.create!(:title => title = "title")
|
259
|
+
story2 = Story.create!(:title => title)
|
260
|
+
mock(Story.connection).execute.never
|
261
|
+
Story.find(:all, :conditions => { :title => story1.title }).should == [story1, story2]
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
describe '#find(:all, :limit => ..., :offset => ...)' do
|
267
|
+
it "cached attributes should support limits and offsets" do
|
268
|
+
character1 = Character.create!(:name => "Sam", :story_id => 1)
|
269
|
+
character2 = Character.create!(:name => "Sam", :story_id => 1)
|
270
|
+
character3 = Character.create!(:name => "Sam", :story_id => 1)
|
271
|
+
mock(Character.connection).execute.never
|
272
|
+
|
273
|
+
Character.find(:all, :conditions => { :name => character1.name, :story_id => character1.story_id }, :limit => 1).should == [character1]
|
274
|
+
Character.find(:all, :conditions => { :name => character1.name, :story_id => character1.story_id }, :offset => 1).should == [character2, character3]
|
275
|
+
Character.find(:all, :conditions => { :name => character1.name, :story_id => character1.story_id }, :limit => 1, :offset => 1).should == [character2]
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
describe '#find([...])' do
|
281
|
+
describe '#find([1, 2, ...], :conditions => ...)' do
|
282
|
+
it "uses the database, not the cache" do
|
283
|
+
story1, story2 = Story.create!, Story.create!
|
284
|
+
mock(Story).get.never
|
285
|
+
Story.find([story1.id, story2.id], :conditions => "type IS NULL").should == [story1, story2]
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
describe '#find([1], :conditions => ...)' do
|
290
|
+
it "uses the database, not the cache" do
|
291
|
+
story1, story2 = Story.create!, Story.create!
|
292
|
+
mock(Story).get.never
|
293
|
+
Story.find([story1.id], :conditions => "type IS NULL").should == [story1]
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
describe '#find_by_attr' do
|
299
|
+
describe 'on indexed attributes' do
|
300
|
+
describe '#find_by_id(id)' do
|
301
|
+
it "does not use the database" do
|
302
|
+
story = Story.create!
|
303
|
+
mock(Story.connection).execute.never
|
304
|
+
Story.find_by_id(story.id).should == story
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
describe '#find_by_id(id)_with_object' do
|
309
|
+
it "does not use the database" do
|
310
|
+
story = Story.create!
|
311
|
+
mock(Story.connection).execute.never
|
312
|
+
Story.find_by_id(story).should == story
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
describe '#find_by_title(title)' do
|
317
|
+
it "does not use the database" do
|
318
|
+
story1 = Story.create!(:title => 'title1')
|
319
|
+
story2 = Story.create!(:title => 'title2')
|
320
|
+
mock(Story.connection).execute.never
|
321
|
+
Story.find_by_title('title1').should == story1
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
describe "Single Table Inheritence" do
|
328
|
+
describe '#find(:all, ...)' do
|
329
|
+
it "does not use the database" do
|
330
|
+
story, epic, oral = Story.create!(:title => title = 'foo'), Epic.create!(:title => title), Oral.create!(:title => title)
|
331
|
+
mock(Story.connection).execute.never
|
332
|
+
Story.find(:all, :conditions => { :title => title }).should == [story, epic, oral]
|
333
|
+
Epic.find(:all, :conditions => { :title => title }).should == [epic, oral]
|
334
|
+
Oral.find(:all, :conditions => { :title => title }).should == [oral]
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
describe '#without_cache' do
|
341
|
+
describe 'when finders are called within the provided block' do
|
342
|
+
it 'uses the database not the cache' do
|
343
|
+
story = Story.create!
|
344
|
+
mock(Story).get.never
|
345
|
+
Story.without_cache do
|
346
|
+
Story.find(story.id).should == story
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
describe 'when the cache is not populated' do
|
354
|
+
before do
|
355
|
+
@story = Story.create!(:title => 'title')
|
356
|
+
$memcache.flush_all
|
357
|
+
end
|
358
|
+
|
359
|
+
describe '#find(:first, ...)' do
|
360
|
+
it 'populates the cache' do
|
361
|
+
Story.find(:first, :conditions => { :title => @story.title })
|
362
|
+
Story.fetch("title/#{@story.title}").should == [@story.id]
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
describe '#find_by_attr' do
|
367
|
+
before(:each) do
|
368
|
+
Story.find_by_title(@story.title) # populates cache for title with [@story.id]
|
369
|
+
end
|
370
|
+
|
371
|
+
it 'populates the cache' do
|
372
|
+
Story.fetch("title/#{@story.title}").should == [@story.id]
|
373
|
+
end
|
374
|
+
|
375
|
+
it 'populates the cache when finding by non-primary-key attribute' do
|
376
|
+
Story.find_by_title(@story.title) # populates cache for id with record
|
377
|
+
|
378
|
+
mock(Story.connection).execute.never # should hit cache only
|
379
|
+
Story.find_by_title(@story.title).id.should == @story.id
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
describe '#find(:all, :conditions => ...)' do
|
384
|
+
it 'populates the cache' do
|
385
|
+
Story.find(:all, :conditions => { :title => @story.title })
|
386
|
+
Story.fetch("title/#{@story.title}").should == [@story.id]
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
describe '#find(:all, :conditions => ..., :order => ...)' do
|
391
|
+
before(:each) do
|
392
|
+
@short1 = Short.create(:title => 'title',
|
393
|
+
:subtitle => 'subtitle')
|
394
|
+
@short2 = Short.create(:title => 'another title',
|
395
|
+
:subtitle => 'subtitle')
|
396
|
+
$memcache.flush_all
|
397
|
+
# debugger
|
398
|
+
Short.find(:all, :conditions => { :subtitle => @short1.subtitle },
|
399
|
+
:order => 'title')
|
400
|
+
end
|
401
|
+
|
402
|
+
it 'populates the cache' do
|
403
|
+
Short.fetch("subtitle/subtitle").should_not be_blank
|
404
|
+
end
|
405
|
+
|
406
|
+
it 'returns objects in the correct order' do
|
407
|
+
Short.fetch("subtitle/subtitle").should ==
|
408
|
+
[@short2.id, @short1.id]
|
409
|
+
end
|
410
|
+
|
411
|
+
it 'finds objects in the correct order' do
|
412
|
+
Short.find(:all, :conditions => { :subtitle => @short1.subtitle },
|
413
|
+
:order => 'title').map(&:id).should == [@short2.id, @short1.id]
|
414
|
+
end
|
415
|
+
|
416
|
+
it 'populates cache for each object' do
|
417
|
+
Short.fetch("id/#{@short1.id}").should == [@short1]
|
418
|
+
Short.fetch("id/#{@short2.id}").should == [@short2]
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
describe '#find(1)' do
|
423
|
+
it 'populates the cache' do
|
424
|
+
Story.find(@story.id)
|
425
|
+
Story.fetch("id/#{@story.id}").should == [@story]
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
describe '#find([1, 2, ...])' do
|
430
|
+
before do
|
431
|
+
@short1 = Short.create(:title => 'title1', :subtitle => 'subtitle')
|
432
|
+
@short2 = Short.create(:title => 'title2', :subtitle => 'subtitle')
|
433
|
+
@short3 = Short.create(:title => 'title3', :subtitle => 'subtitle')
|
434
|
+
$memcache.flush_all
|
435
|
+
end
|
436
|
+
|
437
|
+
it 'populates the cache' do
|
438
|
+
Short.find(@short1.id, @short2.id, @short3.id) == [@short1, @short2, @short3]
|
439
|
+
Short.fetch("id/#{@short1.id}").should == [@short1]
|
440
|
+
Short.fetch("id/#{@short2.id}").should == [@short2]
|
441
|
+
Short.fetch("id/#{@short3.id}").should == [@short3]
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
describe 'when there is a with_scope' do
|
446
|
+
it "uses the database, not the cache" do
|
447
|
+
Story.send :with_scope, :find => { :conditions => { :title => @story.title }} do
|
448
|
+
Story.find(:first, :conditions => { :id => @story.id }).should == @story
|
449
|
+
end
|
450
|
+
end
|
451
|
+
end
|
452
|
+
end
|
453
|
+
end
|
454
|
+
end
|
455
|
+
end
|