timchen-cache-money 0.2.25.1

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