cache_fu 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,281 @@
1
+ require File.join(File.dirname(__FILE__), 'helper')
2
+
3
+ context "A Ruby class acting as cached (in general)" do
4
+ include StoryCacheSpecSetup
5
+
6
+ specify "should be able to retrieve a cached instance from the cache" do
7
+ Story.get_cache(1).should.equal Story.find(1)
8
+ end
9
+
10
+ specify "should set to the cache if its not already set when getting" do
11
+ Story.should.not.have.cached 1
12
+ Story.get_cache(1).should.equal Story.find(1)
13
+ Story.should.have.cached 1
14
+ end
15
+
16
+ specify "should not set to the cache if is already set when getting" do
17
+ Story.expects(:set_cache).never
18
+ Story.should.have.cached 2
19
+ Story.get_cache(2).should.equal Story.find(2)
20
+ Story.should.have.cached 2
21
+ end
22
+
23
+ specify "should be able to tell if a key is cached" do
24
+ Story.is_cached?(1).should.equal false
25
+ Story.should.not.have.cached 1
26
+ Story.should.have.cached 2
27
+ end
28
+
29
+ specify "should be able to cache arbitrary methods using #caches" do
30
+ Story.cache_store.expects(:get).returns(nil)
31
+ Story.cache_store.expects(:set).with('Story:something_cool', :redbull, 1500)
32
+ Story.caches(:something_cool).should.equal :redbull
33
+
34
+ Story.cache_store.expects(:get).returns(:redbull)
35
+ Story.cache_store.expects(:set).never
36
+ Story.caches(:something_cool).should.equal :redbull
37
+ end
38
+
39
+ specify "should be able to cache arbitrary methods with arguments using #caches and :with" do
40
+ with = :mongrel
41
+
42
+ Story.cache_store.expects(:get).returns(nil)
43
+ Story.cache_store.expects(:set).with("Story:block_on:#{with}", with, 1500)
44
+ Story.caches(:block_on, :with => with).should.equal with
45
+
46
+ Story.cache_store.expects(:get).with("Story:block_on:#{with}").returns(:okay)
47
+ Story.cache_store.expects(:set).never
48
+ Story.caches(:block_on, :with => with).should.equal :okay
49
+ end
50
+
51
+ specify "should be able to cache arbitrary methods with a nil argument using #caches and :with" do
52
+ with = nil
53
+
54
+ Story.cache_store.expects(:get).returns(nil)
55
+ Story.cache_store.expects(:set).with("Story:pass_through:#{with}", :_nil, 1500)
56
+ Story.caches(:pass_through, :with => with).should.equal with
57
+ end
58
+
59
+ specify "should be able to cache arbitrary methods with arguments using #caches and :withs" do
60
+ withs = [ :first, :second ]
61
+
62
+ cached_string = "first: #{withs.first} | second: #{withs.last}"
63
+
64
+ Story.cache_store.expects(:get).returns(nil)
65
+ Story.cache_store.expects(:set).with("Story:two_params:#{withs}", cached_string, 1500)
66
+ Story.caches(:two_params, :withs => withs).should.equal cached_string
67
+
68
+ Story.cache_store.expects(:get).with("Story:two_params:#{withs}").returns(:okay)
69
+ Story.cache_store.expects(:set).never
70
+ Story.caches(:two_params, :withs => withs).should.equal :okay
71
+ end
72
+
73
+ specify "should set nil when trying to set nil" do
74
+ Story.set_cache(3, nil).should.equal nil
75
+ Story.get_cache(3).should.equal nil
76
+ end
77
+
78
+ specify "should set false when trying to set false" do
79
+ Story.set_cache(3, false).should.equal false
80
+ Story.get_cache(3).should.equal false
81
+ end
82
+
83
+ specify "should be able to expire a cache key" do
84
+ Story.should.have.cached 2
85
+ Story.expire_cache(2).should.equal true
86
+ Story.should.not.have.cached 2
87
+ end
88
+
89
+ specify "should return true when trying to expire the cache" do
90
+ Story.should.not.have.cached 1
91
+ Story.expire_cache(1).should.equal true
92
+ Story.should.have.cached 2
93
+ Story.expire_cache(2).should.equal true
94
+ end
95
+
96
+ specify "should be able to reset a cache key, returning the cached object if successful" do
97
+ Story.expects(:find).with(2).returns(@story2)
98
+ Story.should.have.cached 2
99
+ Story.reset_cache(2).should.equal @story2
100
+ Story.should.have.cached 2
101
+ end
102
+
103
+ specify "should be able to cache the value of a block" do
104
+ Story.should.not.have.cached :block
105
+ Story.get_cache(:block) { "this is a block" }
106
+ Story.should.have.cached :block
107
+ Story.get_cache(:block).should.equal "this is a block"
108
+ end
109
+
110
+ specify "should be able to define a class level ttl" do
111
+ ttl = 1124
112
+ Story.cache_config[:ttl] = ttl
113
+ Story.cache_config[:store].expects(:set).with(Story.cache_key(1), @story, ttl)
114
+ Story.get_cache(1)
115
+ end
116
+
117
+ specify "should be able to define a per-key ttl" do
118
+ ttl = 3262
119
+ Story.cache_config[:store].expects(:set).with(Story.cache_key(1), @story, ttl)
120
+ Story.get_cache(1, :ttl => ttl)
121
+ end
122
+
123
+ specify "should be able to skip cache gets" do
124
+ Story.should.have.cached 2
125
+ ActsAsCached.skip_cache_gets = true
126
+ Story.expects(:find).at_least_once
127
+ Story.get_cache(2)
128
+ ActsAsCached.skip_cache_gets = false
129
+ end
130
+
131
+ specify "should be able to use an arbitrary finder method via :finder" do
132
+ Story.expire_cache(4)
133
+ Story.cache_config[:finder] = :find_live
134
+ Story.expects(:find_live).with(4).returns(false)
135
+ Story.get_cache(4)
136
+ end
137
+
138
+ specify "should raise an exception if no finder method is found" do
139
+ Story.cache_config[:finder] = :find_penguins
140
+ proc { Story.get_cache(1) }.should.raise(NoMethodError)
141
+ end
142
+
143
+ specify "should be able to use an abitrary cache_id method via :cache_id" do
144
+ Story.expire_cache(4)
145
+ Story.cache_config[:cache_id] = :title
146
+ story = Story.get_cache(1)
147
+ story.cache_id.should.equal story.title
148
+ end
149
+
150
+ specify "should modify its cache key to reflect a :version option" do
151
+ Story.cache_config[:version] = 'new'
152
+ Story.cache_key(1).should.equal 'Story:new:1'
153
+ end
154
+
155
+ specify "should truncate the key normally if we dont have a namespace" do
156
+ Story.stubs(:cache_namespace).returns(nil)
157
+ key = "a" * 260
158
+ Story.cache_key(key).length.should == 250
159
+ end
160
+
161
+ specify "should truncate key with length over 250, including namespace if set" do
162
+ Story.stubs(:cache_namespace).returns("37-power-moves-app" )
163
+ key = "a" * 260
164
+ (Story.cache_namespace + Story.cache_key(key)).length.should == (250 - 1)
165
+ end
166
+
167
+ specify "should raise an informative error message when trying to set_cache with a proc" do
168
+ Story.cache_config[:store].expects(:set).raises(TypeError.new("Can't marshal Proc"))
169
+ proc { Story.set_cache('proc:d', proc { nil }) }.should.raise(ActsAsCached::MarshalError)
170
+ end
171
+ end
172
+
173
+ context "Passing an array of ids to get_cache" do
174
+ include StoryCacheSpecSetup
175
+
176
+ setup do
177
+ @grab_stories = proc do
178
+ @stories = Story.get_cache(1, 2, 3)
179
+ end
180
+
181
+ @keys = 'Story:1', 'Story:2', 'Story:3'
182
+ @hash = {
183
+ 'Story:1' => nil,
184
+ 'Story:2' => $stories[2],
185
+ 'Story:3' => nil
186
+ }
187
+
188
+ # TODO: doh, probably need to clean this up...
189
+ @cache = $with_memcache ? CACHE : $cache
190
+
191
+ @cache.expects(:get_multi).with(*@keys).returns(@hash)
192
+ end
193
+
194
+ specify "should try to fetch those ids using get_multi" do
195
+ @grab_stories.call
196
+
197
+ @stories.size.should.equal 3
198
+ @stories.should.be.an.instance_of Hash
199
+ @stories.each { |id, story| story.should.be.an.instance_of Story }
200
+ end
201
+
202
+ specify "should pass the cache miss ids to #find" do
203
+ Story.expects(:find).with(%w(1 3)).returns($stories[1], $stories[3])
204
+ @grab_stories.call
205
+ end
206
+ end
207
+
208
+ context "Passing an array of ids to get_cache using a cache which doesn't support get_multi" do
209
+ include StoryCacheSpecSetup
210
+
211
+ setup do
212
+ @grab_stories = proc do
213
+ @stories = Story.get_cache(1, 2, 3)
214
+ end
215
+
216
+ # TODO: doh, probably need to clean this up...
217
+ @cache = $with_memcache ? CACHE : $cache
218
+ end
219
+
220
+ specify "should raise an exception" do
221
+ class << @cache; undef :get_multi end
222
+ proc { @grab_stories.call }.should.raise(ActsAsCached::NoGetMulti)
223
+ end
224
+ end
225
+
226
+ context "A Ruby object acting as cached" do
227
+ include StoryCacheSpecSetup
228
+
229
+ specify "should be able to retrieve a cached version of itself" do
230
+ Story.expects(:get_cache).with(1, {}).at_least_once
231
+ @story.get_cache
232
+ end
233
+
234
+ specify "should be able to set itself to the cache" do
235
+ Story.expects(:set_cache).with(1, @story, nil).at_least_once
236
+ @story.set_cache
237
+ end
238
+
239
+ specify "should cache the value of a passed block" do
240
+ @story.should.not.have.cached :block
241
+ @story.get_cache(:block) { "this is a block" }
242
+ @story.should.have.cached :block
243
+ @story.get_cache(:block).should.equal "this is a block"
244
+ end
245
+
246
+ specify "should allow setting custom options by passing them to get_cache" do
247
+ Story.expects(:set_cache).with('1:options', 'cached value', 1.hour)
248
+ @story.get_cache(:options, :ttl => 1.hour) { 'cached value' }
249
+ end
250
+
251
+ specify "should be able to expire its cache" do
252
+ Story.expects(:expire_cache).with(2)
253
+ @story2.expire_cache
254
+ end
255
+
256
+ specify "should be able to reset its cache" do
257
+ Story.expects(:reset_cache).with(2)
258
+ @story2.reset_cache
259
+ end
260
+
261
+ specify "should be able to tell if it is cached" do
262
+ @story.should.not.be.cached
263
+ @story2.should.be.cached
264
+ end
265
+
266
+ specify "should be able to set itself to the cache with an arbitrary ttl" do
267
+ ttl = 1500
268
+ Story.expects(:set_cache).with(1, @story, ttl)
269
+ @story.set_cache(ttl)
270
+ end
271
+
272
+ specify "should be able to cache arbitrary instance methods using caches" do
273
+ Story.cache_store.expects(:get).returns(nil)
274
+ Story.cache_store.expects(:set).with('Story:1:something_flashy', :molassy, 1500)
275
+ @story.caches(:something_flashy).should.equal :molassy
276
+
277
+ Story.cache_store.expects(:get).returns(:molassy)
278
+ Story.cache_store.expects(:set).never
279
+ @story.caches(:something_flashy).should.equal :molassy
280
+ end
281
+ end
@@ -0,0 +1,128 @@
1
+ require File.join(File.dirname(__FILE__), 'helper')
2
+
3
+ context "The global cache configuration" do
4
+ # Pass in a hash to update the config.
5
+ # If the first arg is a symbol, an expectation will be set.
6
+ def setup_config(*args)
7
+ options = args.last.is_a?(Hash) ? args.pop : {}
8
+ @config[RAILS_ENV].update options.stringify_keys
9
+ ActsAsCached::Config.expects(args.first) if args.first.is_a? Symbol
10
+ ActsAsCached.config = @config
11
+ end
12
+
13
+ setup do
14
+ ActsAsCached.config.clear
15
+ @config = YAML.load_file('defaults/memcached.yml.default')
16
+ @config['test'] = @config['development'].merge('benchmarking' => false, 'disabled' => false)
17
+ end
18
+
19
+ specify "should be able to set itself as the session store" do
20
+ setup_config :setup_session_store, :sessions => true
21
+ end
22
+
23
+ specify "should be able to set itself as the fragment store" do
24
+ setup_config :setup_fragment_store!, :fragments => true
25
+ end
26
+
27
+ specify "should construct a namespace from the environment and a config value" do
28
+ setup_config
29
+ ActsAsCached.config[:namespace].should.equal "app-#{RAILS_ENV}"
30
+ end
31
+
32
+ specify "should be able to set a global default ttl" do
33
+ setup_config
34
+ Story.send :acts_as_cached
35
+ ActsAsCached.config[:ttl].should.not.be.nil
36
+ Story.cache_config[:ttl].should.equal ActsAsCached.config[:ttl]
37
+ end
38
+
39
+ specify "should be able to swallow errors" do
40
+ setup_config :raise_errors => false
41
+ Story.send :acts_as_cached
42
+ Story.stubs(:find).returns(Story.new)
43
+ Story.cache_config[:store].expects(:get).raises(MemCache::MemCacheError)
44
+ Story.cache_config[:store].expects(:set).returns(true)
45
+ proc { Story.get_cache(1) }.should.not.raise(MemCache::MemCacheError)
46
+ end
47
+
48
+ specify "should not swallow marshal errors" do
49
+ setup_config :raise_errors => false
50
+ Story.send :acts_as_cached
51
+ Story.stubs(:find).returns(Story.new)
52
+ Story.cache_config[:store].expects(:get).returns(nil)
53
+ Story.cache_config[:store].expects(:set).raises(TypeError.new("Some kind of Proc error"))
54
+ proc { Story.get_cache(1) }.should.raise(ActsAsCached::MarshalError)
55
+ end
56
+
57
+ specify "should be able to re-raise errors" do
58
+ setup_config :raise_errors => true
59
+ Story.send :acts_as_cached
60
+ Story.cache_config[:store].expects(:get).raises(MemCache::MemCacheError)
61
+ proc { Story.get_cache(1) }.should.raise(MemCache::MemCacheError)
62
+ end
63
+
64
+ specify "should be able to enable benchmarking" do
65
+ setup_config :benchmarking => true
66
+ ActsAsCached.config[:benchmarking].should.equal true
67
+ Story.send :acts_as_cached
68
+ Story.methods.should.include 'fetch_cache_with_benchmarking'
69
+ end
70
+
71
+ specify "should be able to disable all caching" do
72
+ setup_config :disabled => true
73
+ Story.send :acts_as_cached
74
+ Story.should.respond_to :fetch_cache_with_disabled
75
+ ActsAsCached.config[:disabled].should.equal true
76
+ end
77
+
78
+ specify "should be able to use a global store other than memcache" do
79
+ setup_config :store => 'HashStore'
80
+ ActsAsCached.config[:store].should.equal HashStore.new
81
+ Story.send :acts_as_cached
82
+ Story.cache_config[:store].should.be ActsAsCached.config[:store]
83
+ end
84
+
85
+ specify "should be able to override the memcache-client hashing algorithm" do
86
+ setup_config :fast_hash => true
87
+ ActsAsCached.config[:fast_hash].should.equal true
88
+ CACHE.hash_for('eatingsnotcheating').should.equal 1919
89
+ end
90
+
91
+ specify "should be able to override the memcache-client hashing algorithm" do
92
+ setup_config :fastest_hash => true
93
+ ActsAsCached.config[:fastest_hash].should.equal true
94
+ CACHE.hash_for(string = 'eatingsnotcheating').should.equal string.hash
95
+ end
96
+
97
+ end
98
+
99
+
100
+ context "The class configuration" do
101
+ # Setups up the Story class with acts_as_cached using options
102
+ def setup_cached(options = {})
103
+ Story.send :acts_as_cached, options
104
+ end
105
+
106
+ specify "should save unknown keys as options and not config" do
107
+ setup_cached :pengiuns => true
108
+ Story.cache_options.should.include :pengiuns
109
+ Story.cache_config.should.not.include :pengiuns
110
+ end
111
+
112
+ specify "should be able to override the default finder" do
113
+ setup_cached :finder => :find_by_title
114
+ Story.cache_config[:finder].should.equal :find_by_title
115
+ end
116
+
117
+ specify "should be able to override the default cache_id" do
118
+ setup_cached :cache_id => :title
119
+ Story.cache_config[:cache_id].should.equal :title
120
+ end
121
+
122
+ specify "should be able to override the default finder and the cache_id using find_by" do
123
+ setup_cached :find_by => :title
124
+ Story.cache_config.should.not.include :find
125
+ Story.cache_config[:finder].should.equal :find_by_title
126
+ Story.cache_config[:cache_id].should.equal :title
127
+ end
128
+ end
@@ -0,0 +1,40 @@
1
+ require File.join(File.dirname(__FILE__), 'helper')
2
+
3
+ context "When the cache is disabled" do
4
+ setup do
5
+ @story = Story.new(:id => 1, :title => "acts_as_cached 2 released!")
6
+ @story2 = Story.new(:id => 2, :title => "BDD is something you can use")
7
+ $stories = { 1 => @story, 2 => @story2 }
8
+
9
+ config = YAML.load_file('defaults/memcached.yml.default')
10
+ config['test'] = config['development'].merge('disabled' => true, 'benchmarking' => false)
11
+ ActsAsCached.config = config
12
+ Story.send :acts_as_cached
13
+ end
14
+
15
+ specify "get_cache should call through to the finder" do
16
+ Story.expects(:find).at_least_once.returns(@story2)
17
+ @story2.get_cache.should.equal @story2
18
+ end
19
+
20
+ specify "expire_cache should return true" do
21
+ $cache.expects(:delete).never
22
+ @story2.expire_cache.should.equal true
23
+ end
24
+
25
+ specify "reset_cache should return the object" do
26
+ $cache.expects(:set).never
27
+ Story.expects(:find).at_least_once.returns(@story2)
28
+ @story2.reset_cache.should.equal @story2
29
+ end
30
+
31
+ specify "set_cache should just return the object" do
32
+ $cache.expects(:set).never
33
+ @story2.set_cache.should.equal @story2
34
+ end
35
+
36
+ specify "cached? should return false" do
37
+ $cache.expects(:get).never
38
+ @story2.should.not.be.cached
39
+ end
40
+ end
@@ -0,0 +1,33 @@
1
+ require File.join(File.dirname(__FILE__), 'helper')
2
+
3
+ module ActsAsCached
4
+ module Extensions
5
+ module ClassMethods
6
+ def user_defined_class_method
7
+ true
8
+ end
9
+ end
10
+
11
+ module InstanceMethods
12
+ def user_defined_instance_method
13
+ true
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ context "When Extensions::ClassMethods exists" do
20
+ include StoryCacheSpecSetup
21
+
22
+ specify "caching classes should extend it" do
23
+ Story.singleton_methods.should.include 'user_defined_class_method'
24
+ end
25
+ end
26
+
27
+ context "When Extensions::InstanceMethods exists" do
28
+ include StoryCacheSpecSetup
29
+
30
+ specify "caching classes should include it" do
31
+ Story.instance_methods.should.include 'user_defined_instance_method'
32
+ end
33
+ end