ngmoco-cache-money 0.2.9

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.
@@ -0,0 +1,67 @@
1
+ require File.join(File.dirname(__FILE__), '..', '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
+ end
40
+ end
41
+ end
42
+
43
+ describe 'when the cache is not populated' do
44
+ describe '#count(:all, ...)' do
45
+ describe '#count(:all)' do
46
+ it 'uses the database, not the cache' do
47
+ mock(Story).get.never
48
+ Story.count
49
+ end
50
+ end
51
+
52
+ describe '#count(:all, :conditions => ...)' do
53
+ before do
54
+ Story.create!(:title => @title = 'title')
55
+ $memcache.flush_all
56
+ end
57
+
58
+ it "populates the count correctly" do
59
+ Story.count(:all, :conditions => { :title => @title }).should == 1
60
+ Story.fetch("title/#{@title}/count", :raw => true).should =~ /\s*1\s*/
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,364 @@
1
+ require File.join(File.dirname(__FILE__), '..', '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 = ?')" 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 = #{story.id}").should == story
38
+ Story.find(:first, :conditions => "`stories`.id = #{story.id}").should == story
39
+ Story.find(:first, :conditions => "`stories`.`id` = #{story.id}").should == story
40
+ end
41
+ end
42
+
43
+ describe '#find(:first, :readonly => false) and any other options other than conditions are nil' do
44
+ it "does not use the database" do
45
+ story = Story.create!
46
+ mock(Story.connection).execute.never
47
+ Story.find(:first, :conditions => { :id => story.id }, :readonly => false, :limit => nil, :offset => nil, :joins => nil, :include => nil).should == story
48
+ end
49
+ end
50
+
51
+ describe '#find(:first, :readonly => true)' do
52
+ it "uses the database, not the cache" do
53
+ story = Story.create!
54
+ mock(Story).get.never
55
+ Story.find(:first, :conditions => { :id => story.id }, :readonly => true).should == story
56
+ end
57
+ end
58
+
59
+ describe '#find(:first, :join => ...) or find(..., :include => ...)' 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 }, :joins => 'AS stories').should == story
64
+ Story.find(:first, :conditions => { :id => story.id }, :include => :characters).should == story
65
+ end
66
+ end
67
+
68
+ describe '#find(:first)' do
69
+ it 'uses the database, not the cache' do
70
+ mock(Story).get.never
71
+ Story.find(:first)
72
+ end
73
+ end
74
+
75
+ describe '#find(:first, :conditions => "...")' do
76
+ describe 'on unindexed attributes' do
77
+ it 'uses the database, not the cache' do
78
+ story = Story.create!
79
+ mock(Story).get.never
80
+ Story.find(:first, :conditions => "type IS NULL")
81
+ end
82
+ end
83
+
84
+ describe 'on indexed attributes' do
85
+ describe 'when the attributes are integers' do
86
+ it 'does not use the database' do
87
+ story = Story.create!
88
+ mock(Story.connection).execute.never
89
+ Story.find(:first, :conditions => "`stories`.id = #{story.id}") \
90
+ .should == story
91
+ end
92
+ end
93
+
94
+ describe 'when the attributes are non-integers' do
95
+ it 'uses the database, not the cache' do
96
+ story = Story.create!(:title => "title")
97
+ mock(Story.connection).execute.never
98
+ Story.find(:first, :conditions => "`stories`.title = '#{story.title }'") \
99
+ .should == story
100
+ end
101
+ end
102
+
103
+ describe 'when the attributes must be coerced to sql values' do
104
+ it 'does not use the database' do
105
+ story1 = Story.create!(:published => true)
106
+ story2 = Story.create!(:published => false)
107
+ mock(Story.connection).execute.never
108
+ Story.find(:first, :conditions => 'published = 0').should == story2
109
+ end
110
+ end
111
+ end
112
+
113
+ describe '#find(:first, :conditions => [...])' do
114
+ describe 'with one indexed attribute' do
115
+ it 'does not use the database' do
116
+ story = Story.create!
117
+ mock(Story.connection).execute.never
118
+ Story.find(:first, :conditions => ['id = ?', story.id]).should == story
119
+ end
120
+ end
121
+
122
+ describe 'with two attributes that match a combo-index' do
123
+ it 'does not use the database' do
124
+ story = Story.create!(:title => 'title')
125
+ mock(Story.connection).execute.never
126
+ Story.find(:first, :conditions => ['id = ? AND title = ?', story.id, story.title]).should == story
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ describe '#find(:first, :conditions => {...})' do
133
+ it "does not use the database" do
134
+ story = Story.create!(:title => "Sam")
135
+ mock(Story.connection).execute.never
136
+ Story.find(:first, :conditions => { :id => story.id, :title => story.title }).should == story
137
+ end
138
+
139
+ describe 'regardless of hash order' do
140
+ it 'does not use the database' do
141
+ story = Story.create!(:title => "Sam")
142
+ mock(Story.connection).execute.never
143
+ Story.find(:first, :conditions => { :id => story.id, :title => story.title }).should == story
144
+ Story.find(:first, :conditions => { :title => story.title, :id => story.id }).should == story
145
+ end
146
+ end
147
+
148
+ describe 'on unindexed attribtes' do
149
+ it 'uses the database, not the cache' do
150
+ story = Story.create!
151
+ mock(Story).get.never
152
+ Story.find(:first, :conditions => { :id => story.id, :type => story.type }).should == story
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ describe 'when there is a with_scope' do
159
+ describe 'when the with_scope has conditions' do
160
+ describe 'when the scope conditions is a string' do
161
+ it "uses the database, not the cache" do
162
+ story = Story.create!(:title => title = 'title')
163
+ mock(Story.connection).execute.never
164
+ Story.send :with_scope, :find => { :conditions => "title = '#{title}'"} do
165
+ Story.find(:first, :conditions => { :id => story.id }).should == story
166
+ end
167
+ end
168
+ end
169
+
170
+ describe 'when the find conditions is a string' do
171
+ it "does not use the database" do
172
+ story = Story.create!(:title => title = 'title')
173
+ mock(Story.connection).execute.never
174
+ Story.send :with_scope, :find => { :conditions => { :id => story.id }} do
175
+ Story.find(:first, :conditions => "title = '#{title}'").should == story
176
+ end
177
+ end
178
+ end
179
+
180
+ describe '#find(1, :conditions => ...)' do
181
+ it "does not use the database" do
182
+ story = Story.create!
183
+ character = Character.create!(:name => name = 'barbara', :story_id => story)
184
+ mock(Character.connection).execute.never
185
+ Character.send :with_scope, :find => { :conditions => { :story_id => story.id } } do
186
+ Character.find(character.id, :conditions => { :name => name }).should == character
187
+ end
188
+ end
189
+ end
190
+ end
191
+
192
+ describe 'has_many associations' do
193
+ describe '#find(1)' do
194
+ it "does not use the database" do
195
+ story = Story.create!
196
+ character = story.characters.create!
197
+ mock(Character.connection).execute.never
198
+ story.characters.find(character.id).should == character
199
+ end
200
+ end
201
+
202
+ describe '#find(1, 2, ...)' do
203
+ it "does not use the database" do
204
+ story = Story.create!
205
+ character1 = story.characters.create!
206
+ character2 = story.characters.create!
207
+ mock(Character.connection).execute.never
208
+ story.characters.find(character1.id, character2.id).should == [character1, character2]
209
+ end
210
+ end
211
+
212
+ describe '#find_by_attr' do
213
+ it "does not use the database" do
214
+ story = Story.create!
215
+ character = story.characters.create!
216
+ mock(Character.connection).execute.never
217
+ story.characters.find_by_id(character.id).should == character
218
+ end
219
+ end
220
+ end
221
+ end
222
+
223
+ describe '#find(:all)' do
224
+ it "uses the database, not the cache" do
225
+ character = Character.create!
226
+ mock(Character).get.never
227
+ Character.find(:all).should == [character]
228
+ end
229
+
230
+ describe '#find(:all, :conditions => {...})' do
231
+ describe 'when the index is not empty' do
232
+ it 'does not use the database' do
233
+ story1 = Story.create!(:title => title = "title")
234
+ story2 = Story.create!(:title => title)
235
+ mock(Story.connection).execute.never
236
+ Story.find(:all, :conditions => { :title => story1.title }).should == [story1, story2]
237
+ end
238
+ end
239
+ end
240
+
241
+ describe '#find(:all, :limit => ..., :offset => ...)' do
242
+ it "cached attributes should support limits and offsets" do
243
+ character1 = Character.create!(:name => "Sam", :story_id => 1)
244
+ character2 = Character.create!(:name => "Sam", :story_id => 1)
245
+ character3 = Character.create!(:name => "Sam", :story_id => 1)
246
+ mock(Character.connection).execute.never
247
+
248
+ Character.find(:all, :conditions => { :name => character1.name, :story_id => character1.story_id }, :limit => 1).should == [character1]
249
+ Character.find(:all, :conditions => { :name => character1.name, :story_id => character1.story_id }, :offset => 1).should == [character2, character3]
250
+ Character.find(:all, :conditions => { :name => character1.name, :story_id => character1.story_id }, :limit => 1, :offset => 1).should == [character2]
251
+ end
252
+ end
253
+ end
254
+
255
+ describe '#find([...])' do
256
+ describe '#find([1, 2, ...], :conditions => ...)' do
257
+ it "uses the database, not the cache" do
258
+ story1, story2 = Story.create!, Story.create!
259
+ mock(Story).get.never
260
+ Story.find([story1.id, story2.id], :conditions => "type IS NULL").should == [story1, story2]
261
+ end
262
+ end
263
+
264
+ describe '#find([1], :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], :conditions => "type IS NULL").should == [story1]
269
+ end
270
+ end
271
+ end
272
+
273
+ describe '#find_by_attr' do
274
+ describe 'on indexed attributes' do
275
+ describe '#find_by_id(id)' do
276
+ it "does not use the database" do
277
+ story = Story.create!
278
+ mock(Story.connection).execute.never
279
+ Story.find_by_id(story.id).should == story
280
+ end
281
+ end
282
+
283
+ describe '#find_by_title(title)' do
284
+ it "does not use the database" do
285
+ story1 = Story.create!(:title => 'title1')
286
+ story2 = Story.create!(:title => 'title2')
287
+ mock(Story.connection).execute.never
288
+ Story.find_by_title('title1').should == story1
289
+ end
290
+ end
291
+ end
292
+ end
293
+
294
+ describe "Single Table Inheritence" do
295
+ describe '#find(:all, ...)' do
296
+ it "does not use the database" do
297
+ story, epic, oral = Story.create!(:title => title = 'foo'), Epic.create!(:title => title), Oral.create!(:title => title)
298
+ mock(Story.connection).execute.never
299
+ Story.find(:all, :conditions => { :title => title }).should == [story, epic, oral]
300
+ Epic.find(:all, :conditions => { :title => title }).should == [epic, oral]
301
+ Oral.find(:all, :conditions => { :title => title }).should == [oral]
302
+ end
303
+ end
304
+ end
305
+ end
306
+
307
+ describe '#without_cache' do
308
+ describe 'when finders are called within the provided block' do
309
+ it 'uses the database not the cache' do
310
+ story = Story.create!
311
+ mock(Story).get.never
312
+ Story.without_cache do
313
+ Story.find(story.id).should == story
314
+ end
315
+ end
316
+ end
317
+ end
318
+ end
319
+
320
+ describe 'when the cache is not populated' do
321
+ before do
322
+ @story = Story.create!(:title => 'title')
323
+ $memcache.flush_all
324
+ end
325
+
326
+ describe '#find(:first, ...)' do
327
+ it 'populates the cache' do
328
+ Story.find(:first, :conditions => { :title => @story.title })
329
+ Story.fetch("title/#{@story.title}").should == [@story.id]
330
+ end
331
+ end
332
+
333
+ describe '#find_by_attr' do
334
+ it 'populates the cache' do
335
+ Story.find_by_title(@story.title)
336
+ Story.fetch("title/#{@story.title}").should == [@story.id]
337
+ end
338
+ end
339
+
340
+ describe '#find(:all, :conditions => ...)' do
341
+ it 'populates the cache' do
342
+ Story.find(:all, :conditions => { :title => @story.title })
343
+ Story.fetch("title/#{@story.title}").should == [@story.id]
344
+ end
345
+ end
346
+
347
+ describe '#find(1)' do
348
+ it 'populates the cache' do
349
+ Story.find(@story.id)
350
+ Story.fetch("id/#{@story.id}").should == [@story]
351
+ end
352
+ end
353
+
354
+ describe 'when there is a with_scope' do
355
+ it "uses the database, not the cache" do
356
+ Story.send :with_scope, :find => { :conditions => { :title => @story.title }} do
357
+ Story.find(:first, :conditions => { :id => @story.id }).should == @story
358
+ end
359
+ end
360
+ end
361
+ end
362
+ end
363
+ end
364
+ end
@@ -0,0 +1,109 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ module Cash
4
+ describe Lock do
5
+ describe '#synchronize' do
6
+ it "yields the block" do
7
+ block_was_called = false
8
+ $lock.synchronize('lock_key') do
9
+ block_was_called = true
10
+ end
11
+ block_was_called.should == true
12
+ end
13
+
14
+ it "acquires the specified lock before the block is run" do
15
+ $memcache.get("lock/lock_key").should == nil
16
+ $lock.synchronize('lock_key') do
17
+ $memcache.get("lock/lock_key").should_not == nil
18
+ end
19
+ end
20
+
21
+ it "releases the lock after the block is run" do
22
+ $memcache.get("lock/lock_key").should == nil
23
+ $lock.synchronize('lock_key') {}
24
+ $memcache.get("lock/lock_key").should == nil
25
+
26
+ end
27
+
28
+ it "releases the lock even if the block raises" do
29
+ $memcache.get("lock/lock_key").should == nil
30
+ $lock.synchronize('lock_key') { raise } rescue nil
31
+ $memcache.get("lock/lock_key").should == nil
32
+ end
33
+
34
+ it "does not block on recursive lock acquisition" do
35
+ $lock.synchronize('lock_key') do
36
+ lambda { $lock.synchronize('lock_key') {} }.should_not raise_error
37
+ end
38
+ end
39
+ end
40
+
41
+ describe '#acquire_lock' do
42
+ it "creates a lock at a given cache key" do
43
+ $memcache.get("lock/lock_key").should == nil
44
+ $lock.acquire_lock("lock_key")
45
+ $memcache.get("lock/lock_key").should_not == nil
46
+ end
47
+
48
+ describe 'when given a timeout for the lock' do
49
+ it "correctly sets timeout on memcache entries" do
50
+ mock($memcache).add('lock/lock_key', Process.pid, timeout = 10) { true }
51
+ # $lock.acquire_lock('lock_key', timeout)
52
+ lambda { $lock.acquire_lock('lock_key', timeout, 1) }.should raise_error
53
+ end
54
+ end
55
+
56
+ describe 'when to processes contend for a lock' do
57
+ it "prevents two processes from acquiring the same lock at the same time" do
58
+ $lock.acquire_lock('lock_key')
59
+ as_another_process do
60
+ stub($lock).exponential_sleep
61
+ lambda { $lock.acquire_lock('lock_key') }.should raise_error
62
+ end
63
+ end
64
+
65
+ describe 'when given a number of times to retry' do
66
+ it "retries specified number of times" do
67
+ $lock.acquire_lock('lock_key')
68
+ as_another_process do
69
+ mock($memcache).add("lock/lock_key", Process.pid, timeout = 10) { false }.times(retries = 3)
70
+ stub($lock).exponential_sleep
71
+ lambda { $lock.acquire_lock('lock_key', timeout, retries) }.should raise_error
72
+ end
73
+ end
74
+ end
75
+
76
+ describe 'when given an initial wait' do
77
+ it 'sleeps exponentially starting with the initial wait' do
78
+ stub($lock).sleep(initial_wait = 0.123)
79
+ stub($lock).sleep(2 * initial_wait)
80
+ stub($lock).sleep(4 * initial_wait)
81
+ stub($lock).sleep(8 * initial_wait)
82
+ $lock.acquire_lock('lock_key')
83
+ as_another_process do
84
+ lambda { $lock.acquire_lock('lock_key', Lock::DEFAULT_EXPIRY, Lock::DEFAULT_RETRY, initial_wait) }.should raise_error
85
+ end
86
+ end
87
+ end
88
+
89
+ def as_another_process
90
+ current_pid = Process.pid
91
+ stub(Process).pid { current_pid + 1 }
92
+ yield
93
+ end
94
+
95
+ end
96
+
97
+ end
98
+
99
+ describe '#release_lock' do
100
+ it "deletes the lock for a given cache key" do
101
+ $memcache.get("lock/lock_key").should == nil
102
+ $lock.acquire_lock("lock_key")
103
+ $memcache.get("lock/lock_key").should_not == nil
104
+ $lock.release_lock("lock_key")
105
+ $memcache.get("lock/lock_key").should == nil
106
+ end
107
+ end
108
+ end
109
+ end