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.
Files changed (51) hide show
  1. data/LICENSE +201 -0
  2. data/README +204 -0
  3. data/README.markdown +204 -0
  4. data/TODO +17 -0
  5. data/UNSUPPORTED_FEATURES +13 -0
  6. data/config/environment.rb +8 -0
  7. data/config/memcached.yml +4 -0
  8. data/db/schema.rb +18 -0
  9. data/init.rb +1 -0
  10. data/lib/cache_money.rb +105 -0
  11. data/lib/cash/accessor.rb +83 -0
  12. data/lib/cash/adapter/memcache_client.rb +36 -0
  13. data/lib/cash/adapter/memcached.rb +127 -0
  14. data/lib/cash/adapter/redis.rb +144 -0
  15. data/lib/cash/buffered.rb +137 -0
  16. data/lib/cash/config.rb +78 -0
  17. data/lib/cash/fake.rb +83 -0
  18. data/lib/cash/finders.rb +50 -0
  19. data/lib/cash/index.rb +211 -0
  20. data/lib/cash/local.rb +105 -0
  21. data/lib/cash/lock.rb +63 -0
  22. data/lib/cash/mock.rb +158 -0
  23. data/lib/cash/query/abstract.rb +219 -0
  24. data/lib/cash/query/calculation.rb +45 -0
  25. data/lib/cash/query/primary_key.rb +50 -0
  26. data/lib/cash/query/select.rb +16 -0
  27. data/lib/cash/request.rb +3 -0
  28. data/lib/cash/transactional.rb +43 -0
  29. data/lib/cash/util/array.rb +9 -0
  30. data/lib/cash/util/marshal.rb +19 -0
  31. data/lib/cash/version.rb +3 -0
  32. data/lib/cash/write_through.rb +71 -0
  33. data/lib/mem_cached_session_store.rb +49 -0
  34. data/lib/mem_cached_support_store.rb +143 -0
  35. data/rails/init.rb +1 -0
  36. data/spec/cash/accessor_spec.rb +186 -0
  37. data/spec/cash/active_record_spec.rb +224 -0
  38. data/spec/cash/buffered_spec.rb +9 -0
  39. data/spec/cash/calculations_spec.rb +78 -0
  40. data/spec/cash/finders_spec.rb +455 -0
  41. data/spec/cash/local_buffer_spec.rb +9 -0
  42. data/spec/cash/local_spec.rb +9 -0
  43. data/spec/cash/lock_spec.rb +110 -0
  44. data/spec/cash/marshal_spec.rb +60 -0
  45. data/spec/cash/order_spec.rb +172 -0
  46. data/spec/cash/transactional_spec.rb +602 -0
  47. data/spec/cash/window_spec.rb +195 -0
  48. data/spec/cash/without_caching_spec.rb +32 -0
  49. data/spec/cash/write_through_spec.rb +252 -0
  50. data/spec/spec_helper.rb +87 -0
  51. 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