seifertd-cache-money 0.2.5.2

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