revo-cache_fu 1.0.0

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,43 @@
1
+ require 'yaml'
2
+ require 'erb'
3
+
4
+ namespace :memcached do
5
+ desc "Start memcached locally"
6
+ task :start do
7
+ memcached config_args
8
+ puts "memcached started"
9
+ end
10
+
11
+ desc "Restart memcached locally"
12
+ task :restart do
13
+ Rake::Task['memcached:stop'].invoke
14
+ Rake::Task['memcached:start'].invoke
15
+ end
16
+
17
+ desc "Stop memcached locally"
18
+ task :stop do
19
+ `killall memcached`
20
+ puts "memcached killed"
21
+ end
22
+ end
23
+
24
+ def config
25
+ return @config if @config
26
+ config = YAML.load(ERB.new(IO.read(File.dirname(__FILE__) + '/../../../../config/memcached.yml')).result)
27
+ @config = config['defaults'].merge(config['development'])
28
+ end
29
+
30
+ def config_args
31
+ args = {
32
+ '-p' => Array(config['servers']).first.split(':').last,
33
+ '-c' => config['c_threshold'],
34
+ '-m' => config['memory'],
35
+ '-d' => ''
36
+ }
37
+
38
+ args.to_a * ' '
39
+ end
40
+
41
+ def memcached(*args)
42
+ `/usr/bin/env memcached #{args * ' '}`
43
+ end
@@ -0,0 +1,36 @@
1
+ require File.join(File.dirname(__FILE__), 'helper')
2
+
3
+ ActsAsCached.config.clear
4
+ config = YAML.load_file(File.join(File.dirname(__FILE__), '../defaults/memcached.yml.default'))
5
+ config['test'] = config['development']
6
+ ActsAsCached.config = config
7
+ Story.send :acts_as_cached
8
+
9
+ context "When benchmarking is enabled" do
10
+ specify "ActionController::Base should respond to rendering_runtime_with_memcache" do
11
+ ActionController::Base.new.should.respond_to :rendering_runtime_with_memcache
12
+ end
13
+
14
+ specify "cachable Ruby classes should be respond to :logger" do
15
+ Story.should.respond_to :logger
16
+ end
17
+
18
+ specify "a cached object should gain a fetch_cache with and without benchmarking methods" do
19
+ Story.should.respond_to :fetch_cache_with_benchmarking
20
+ Story.should.respond_to :fetch_cache_without_benchmarking
21
+ end
22
+
23
+ specify "cache_benchmark should yield and time any action" do
24
+ ActsAsCached::Benchmarking.cache_runtime.should.equal 0.0
25
+
26
+ level = Class.new { |k| def k.method_missing(*args) true end }
27
+ Story.stubs(:logger).returns(level)
28
+
29
+ Story.cache_benchmark("Seriously, nothing.", true) {
30
+ sleep 0.01
31
+ "Nothing."
32
+ }.should.equal "Nothing."
33
+
34
+ ActsAsCached::Benchmarking.cache_runtime.should.be > 0.0
35
+ end
36
+ end
@@ -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