viximo-cache-money 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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