cache_method 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  It's like <tt>alias_method</tt>, but it's <tt>cache_method</tt>!
4
4
 
5
+ Lets you cache the results of calling methods given their arguments. Like memoization, but stored in Memcached, Redis, etc. so that the cached results can be shared between processes and hosts.
6
+
7
+ == Real-world usage
8
+
9
+ In production use at {impact.brighterplanet.com}[http://impact.brighterplanet.com] and {data.brighterplanet.com}[http://data.brighterplanet.com].
10
+
5
11
  == Example
6
12
 
7
13
  require 'cache_method'
@@ -14,17 +20,16 @@ It's like <tt>alias_method</tt>, but it's <tt>cache_method</tt>!
14
20
  @url = url
15
21
  end
16
22
 
17
- # The method we're going to cache
18
23
  def get_latest_entries
19
- sleep 5
24
+ # ...
20
25
  end
21
-
22
- # What you get with this gem
23
26
  cache_method :get_latest_entries
24
27
 
25
- # Per Ruby convention, a "hash code" representing the internal state of an instance.
26
- # It's recommended that you construct a String or a Hash and then call #hash on it.
27
- def hash
28
+ # cache_method defaults to using the #hash method
29
+ # It's a "hash code" (integer! always integer!) representing the internal state of an instance.
30
+ # If you need to customize it, you can define #method_cache_hash.
31
+ # In that case, it's recommended that you construct a String or a Hash and then call #hash on it (because you should return an integer)
32
+ def method_cache_hash
28
33
  { :name => name, :url => url }.hash
29
34
  end
30
35
  end
@@ -40,10 +45,6 @@ And clear them too
40
45
 
41
46
  (which doesn't delete the rest of your cache)
42
47
 
43
- == Real-world usage
44
-
45
- In production use at {carbon.brighterplanet.com}[http://carbon.brighterplanet.com], the Brighter Planet emission estimate web service.
46
-
47
48
  == Configuration (and supported cache clients)
48
49
 
49
50
  You need to set where the cache will be stored:
@@ -70,7 +71,7 @@ get_latest_entries doesn't take any arguments, so it must depend on my_blog.url
70
71
 
71
72
  class Blog
72
73
  # [...]
73
- def hash
74
+ def method_cache_hash
74
75
  { :name => name, :url => url }.hash
75
76
  end
76
77
  # [...]
@@ -86,6 +87,33 @@ Note: this is NOT the same thing as <tt>#to_hash</tt>! That returns a <b><tt>Has
86
87
 
87
88
  If you don't want to modify #hash, use #method_cache_hash instead.
88
89
 
90
+ == Module methods
91
+
92
+ You can put <tt>#cache_method</tt> right into your module declarations:
93
+
94
+ module MyModule
95
+ def my_module_method(args)
96
+ # [...]
97
+ end
98
+ cache_method :my_module_method
99
+ end
100
+
101
+ class Tiger
102
+ extend MyModule
103
+ end
104
+
105
+ class Lion
106
+ extend MyModule
107
+ end
108
+
109
+ Rest assured that <tt>Tiger.my_module_method</tt> and <tt>Lion.my_module_method</tt> will be cached correctly and separately. This, on the other hand, won't work:
110
+
111
+ class Tiger
112
+ extend MyModule
113
+ # wrong - will raise NameError Exception: undefined method `my_module_method' for class `Tiger'
114
+ # cache_method :my_module_method
115
+ end
116
+
89
117
  == Rationale
90
118
 
91
119
  * It should be easy to cache a method using memcached, dalli (if you're on heroku), redis, etc. (that's why I made the {cache gem}[https://rubygems.org/gems/cache])
data/cache_method.gemspec CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |s|
20
20
 
21
21
  s.add_dependency 'cache', '>=0.2.1'
22
22
  s.add_development_dependency 'memcached'
23
- s.add_development_dependency 'activesupport'
23
+ s.add_development_dependency 'rake'
24
24
  # if RUBY_VERSION >= '1.9'
25
25
  # s.add_development_dependency 'ruby-debug19'
26
26
  # else
@@ -1,17 +1,12 @@
1
1
  require 'digest/md5'
2
2
  module CacheMethod
3
3
  class CachedResult #:nodoc: all
4
- class << self
5
- def fetch(options = {})
6
- cached_result = new options
7
- cached_result.fetch
8
- end
9
- end
10
-
11
- def initialize(options = {})
12
- options.each do |k, v|
13
- instance_variable_set "@#{k}", v
14
- end
4
+ def initialize(obj, method_id, original_method_id, ttl, args)
5
+ @obj = obj
6
+ @method_id = method_id
7
+ @original_method_id = original_method_id
8
+ @ttl = ttl
9
+ @args = args
15
10
  end
16
11
 
17
12
  attr_reader :obj
@@ -35,10 +30,10 @@ module CacheMethod
35
30
  end
36
31
 
37
32
  def cache_key
38
- if obj.is_a? ::Class or obj.is_a? ::Module
39
- [ 'CacheMethod', 'CachedResult', method_signature, current_epoch, args_digest ].join ','
33
+ if obj.is_a?(::Class) or obj.is_a?(::Module)
34
+ [ 'CacheMethod', 'CachedResult', method_signature, current_generation, args_digest ].compact.join ','
40
35
  else
41
- [ 'CacheMethod', 'CachedResult', method_signature, obj_hash, current_epoch, args_digest ].join ','
36
+ [ 'CacheMethod', 'CachedResult', method_signature, obj_hash, current_generation, args_digest ].compact.join ','
42
37
  end
43
38
  end
44
39
 
@@ -54,8 +49,10 @@ module CacheMethod
54
49
  @args_digest ||= args.empty? ? 'empty' : ::Digest::MD5.hexdigest(args.join)
55
50
  end
56
51
 
57
- def current_epoch
58
- @current_epoch ||= Epoch.current(:obj => obj, :method_id => method_id)
52
+ def current_generation
53
+ if Config.instance.generational?
54
+ @current_generation ||= Generation.new(obj, method_id).current
55
+ end
59
56
  end
60
57
  end
61
58
  end
@@ -11,6 +11,20 @@ module CacheMethod
11
11
  class Config
12
12
  include ::Singleton
13
13
 
14
+ # Whether to use "generational" caching. Default is true.
15
+ #
16
+ # Pro: enables clearing/flushing/expiring specific methods
17
+ # Con: requires an extra trip to memcached to get the current "generation"
18
+ #
19
+ # Set to false if you just flush everything and don't need to selectively flush particular methods
20
+ def generational=(boolean)
21
+ @generational = boolean
22
+ end
23
+
24
+ def generational? #:nodoc:
25
+ @generational == true or @generational.nil?
26
+ end
27
+
14
28
  # Storage for the cache.
15
29
  #
16
30
  # Supported memcached clients:
@@ -1,25 +1,14 @@
1
1
  module CacheMethod
2
- class Epoch #:nodoc: all
2
+ class Generation #:nodoc: all
3
3
  class << self
4
- def current(options = {})
5
- epoch = new options
6
- epoch.current
7
- end
8
-
9
- def mark_passing(options = {})
10
- epoch = new options
11
- epoch.mark_passing
12
- end
13
-
14
4
  def random_name
15
- rand(1_000_000).to_s
5
+ rand(100_000_000).to_s
16
6
  end
17
7
  end
18
8
 
19
- def initialize(options = {})
20
- options.each do |k, v|
21
- instance_variable_set "@#{k}", v
22
- end
9
+ def initialize(obj, method_id)
10
+ @obj = obj
11
+ @method_id = method_id
23
12
  end
24
13
 
25
14
  attr_reader :obj
@@ -35,9 +24,9 @@ module CacheMethod
35
24
 
36
25
  def cache_key
37
26
  if obj.is_a? ::Class or obj.is_a? ::Module
38
- [ 'CacheMethod', 'Epoch', method_signature ].join ','
27
+ [ 'CacheMethod', 'Generation', method_signature ].join ','
39
28
  else
40
- [ 'CacheMethod', 'Epoch', method_signature, obj_hash ].join ','
29
+ [ 'CacheMethod', 'Generation', method_signature, obj_hash ].join ','
41
30
  end
42
31
  end
43
32
 
@@ -45,7 +34,7 @@ module CacheMethod
45
34
  if cached_v = Config.instance.storage.get(cache_key)
46
35
  cached_v
47
36
  else
48
- v = Epoch.random_name
37
+ v = Generation.random_name
49
38
  # never expire!
50
39
  Config.instance.storage.set cache_key, v, 0
51
40
  v
@@ -1,3 +1,3 @@
1
1
  module CacheMethod
2
- VERSION = "0.1.5"
2
+ VERSION = "0.1.6"
3
3
  end
data/lib/cache_method.rb CHANGED
@@ -3,7 +3,7 @@ require 'cache_method/version'
3
3
  module CacheMethod
4
4
  autoload :Config, 'cache_method/config'
5
5
  autoload :CachedResult, 'cache_method/cached_result'
6
- autoload :Epoch, 'cache_method/epoch'
6
+ autoload :Generation, 'cache_method/generation'
7
7
 
8
8
  def self.config #:nodoc:
9
9
  Config.instance
@@ -30,7 +30,11 @@ module CacheMethod
30
30
  # Example:
31
31
  # my_blog.clear_method_cache :get_latest_entries
32
32
  def clear_method_cache(method_id)
33
- ::CacheMethod::Epoch.mark_passing :obj => self, :method_id => method_id
33
+ if ::CacheMethod.config.generational?
34
+ ::CacheMethod::Generation.new(self, method_id).mark_passing
35
+ else
36
+ raise ::RuntimeError, "[cache_method] clear_method_cache called, but you have disabled generational caching. Check your setting for CacheMethod.config.generational"
37
+ end
34
38
  end
35
39
  end
36
40
 
@@ -44,20 +48,16 @@ module CacheMethod
44
48
  #
45
49
  # Example:
46
50
  # class Blog
47
- # # [...]
48
51
  # def get_latest_entries
49
- # sleep 5
52
+ # # [...]
50
53
  # end
51
- # # [...]
52
54
  # cache_method :get_latest_entries
53
- # # if you wanted a different ttl...
54
- # # cache_method :get_latest_entries, 800 #seconds
55
55
  # end
56
56
  def cache_method(method_id, ttl = nil)
57
57
  original_method_id = "_uncached_#{method_id}"
58
58
  alias_method original_method_id, method_id
59
59
  define_method method_id do |*args|
60
- ::CacheMethod::CachedResult.fetch :obj => self, :method_id => method_id, :original_method_id => original_method_id, :ttl => ttl, :args => args
60
+ ::CacheMethod::CachedResult.new(self, method_id, original_method_id, ttl, args).fetch
61
61
  end
62
62
  end
63
63
  end
data/test/helper.rb CHANGED
@@ -52,6 +52,22 @@ class CopyCat1a < CopyCat1
52
52
  end
53
53
  end
54
54
 
55
+ module Say
56
+ def say_count
57
+ @say_count ||= 0
58
+ end
59
+
60
+ def say_count=(x)
61
+ @say_count = x
62
+ end
63
+
64
+ def say(msg)
65
+ self.say_count += 1
66
+ msg
67
+ end
68
+ cache_method :say
69
+ end
70
+
55
71
  class CopyCat2
56
72
  class << self
57
73
  attr_writer :echo_count
@@ -122,11 +138,7 @@ class Blog2
122
138
  end
123
139
  end
124
140
 
125
- require 'active_support'
126
- require 'active_support/core_ext/module'
127
141
  module BlogM
128
- # mattr_accessor :request_count
129
- # self.request_count = 0
130
142
  def self.request_count
131
143
  @request_count ||= 0
132
144
  end
@@ -141,3 +153,8 @@ module BlogM
141
153
  cache_method :get_latest_entries
142
154
  end
143
155
  end
156
+
157
+ CopyCat1.extend Say
158
+ CopyCat2.extend Say
159
+
160
+ CopyCat1.send :include, Say
@@ -6,9 +6,12 @@ class TestCacheMethod < Test::Unit::TestCase
6
6
  def setup
7
7
  Blog2.request_count = 0
8
8
  CopyCat2.echo_count = 0
9
- my_cache = Memcached.new '127.0.0.1:11211', :binary => false
9
+ CopyCat1.say_count = 0
10
+ CopyCat2.say_count = 0
11
+ my_cache = Memcached.new '127.0.0.1:11211', :binary => true
10
12
  my_cache.flush
11
13
  CacheMethod.config.storage = my_cache
14
+ CacheMethod.config.generational = true
12
15
  end
13
16
 
14
17
  def test_cache_instance_method_with_args
@@ -233,4 +236,63 @@ class TestCacheMethod < Test::Unit::TestCase
233
236
  a.echo 'hi'
234
237
  end
235
238
  end
239
+
240
+ def test_method_added_by_extension
241
+ assert_equal 'hi', CopyCat2.say('hi')
242
+ assert_equal 1, CopyCat2.say_count
243
+
244
+ assert_equal 'hi', CopyCat2.say('hi')
245
+ assert_equal 1, CopyCat2.say_count
246
+ end
247
+
248
+ def test_method_added_by_inclusion
249
+ a = CopyCat1.new 'sayer'
250
+
251
+ assert_equal 'hi', a.say('hi')
252
+ assert_equal 1, a.say_count
253
+
254
+ assert_equal 'hi', a.say('hi')
255
+ assert_equal 1, a.say_count
256
+ end
257
+
258
+ def test_not_confused_by_module
259
+ assert_equal 'hi', CopyCat2.say('hi')
260
+ assert_equal 1, CopyCat2.say_count
261
+
262
+ assert_equal 'hi', CopyCat2.say('hi')
263
+ assert_equal 1, CopyCat2.say_count
264
+
265
+ assert_equal 'hi', CopyCat1.say('hi')
266
+ assert_equal 1, CopyCat1.say_count
267
+
268
+ assert_equal 'hi', CopyCat1.say('hi')
269
+ assert_equal 1, CopyCat1.say_count
270
+ end
271
+
272
+ def test_disable_generational_caching
273
+ CacheMethod.config.generational = false
274
+
275
+ a = CopyCat1.new 'mimo'
276
+
277
+ assert_equal 'hi', a.echo('hi')
278
+ assert_equal 1, a.echo_count
279
+
280
+ assert_equal 'hi', a.echo('hi')
281
+ assert_equal 1, a.echo_count
282
+ end
283
+
284
+ def test_cant_clear_method_cache_without_generational_caching
285
+ CacheMethod.config.generational = false
286
+
287
+ a = new_instance_of_my_blog
288
+ assert_equal ["hello from #{a.name}"], a.get_latest_entries
289
+ assert_equal 1, a.request_count
290
+
291
+ assert_raises(::RuntimeError) do
292
+ a.clear_method_cache :get_latest_entries
293
+ end
294
+
295
+ assert_equal ["hello from #{a.name}"], a.get_latest_entries
296
+ assert_equal 1, a.request_count
297
+ end
236
298
  end
metadata CHANGED
@@ -1,77 +1,56 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: cache_method
3
- version: !ruby/object:Gem::Version
4
- hash: 17
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.6
5
5
  prerelease:
6
- segments:
7
- - 0
8
- - 1
9
- - 5
10
- version: 0.1.5
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Seamus Abshere
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2011-03-12 23:00:00 -06:00
19
- default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
12
+ date: 2011-11-17 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
22
15
  name: cache
23
- prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: &2155074680 !ruby/object:Gem::Requirement
25
17
  none: false
26
- requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- hash: 21
30
- segments:
31
- - 0
32
- - 2
33
- - 1
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
34
21
  version: 0.2.1
35
22
  type: :runtime
36
- version_requirements: *id001
37
- - !ruby/object:Gem::Dependency
38
- name: memcached
39
23
  prerelease: false
40
- requirement: &id002 !ruby/object:Gem::Requirement
24
+ version_requirements: *2155074680
25
+ - !ruby/object:Gem::Dependency
26
+ name: memcached
27
+ requirement: &2155073740 !ruby/object:Gem::Requirement
41
28
  none: false
42
- requirements:
43
- - - ">="
44
- - !ruby/object:Gem::Version
45
- hash: 3
46
- segments:
47
- - 0
48
- version: "0"
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
49
33
  type: :development
50
- version_requirements: *id002
51
- - !ruby/object:Gem::Dependency
52
- name: activesupport
53
34
  prerelease: false
54
- requirement: &id003 !ruby/object:Gem::Requirement
35
+ version_requirements: *2155073740
36
+ - !ruby/object:Gem::Dependency
37
+ name: rake
38
+ requirement: &2155072900 !ruby/object:Gem::Requirement
55
39
  none: false
56
- requirements:
57
- - - ">="
58
- - !ruby/object:Gem::Version
59
- hash: 3
60
- segments:
61
- - 0
62
- version: "0"
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
63
44
  type: :development
64
- version_requirements: *id003
45
+ prerelease: false
46
+ version_requirements: *2155072900
65
47
  description: Like alias_method, but it's cache_method!
66
- email:
48
+ email:
67
49
  - seamus@abshere.net
68
50
  executables: []
69
-
70
51
  extensions: []
71
-
72
52
  extra_rdoc_files: []
73
-
74
- files:
53
+ files:
75
54
  - .gitignore
76
55
  - Gemfile
77
56
  - README.rdoc
@@ -80,44 +59,35 @@ files:
80
59
  - lib/cache_method.rb
81
60
  - lib/cache_method/cached_result.rb
82
61
  - lib/cache_method/config.rb
83
- - lib/cache_method/epoch.rb
62
+ - lib/cache_method/generation.rb
84
63
  - lib/cache_method/version.rb
85
64
  - test/helper.rb
86
65
  - test/test_cache_method.rb
87
- has_rdoc: true
88
66
  homepage: https://github.com/seamusabshere/cache_method
89
67
  licenses: []
90
-
91
68
  post_install_message:
92
69
  rdoc_options: []
93
-
94
- require_paths:
70
+ require_paths:
95
71
  - lib
96
- required_ruby_version: !ruby/object:Gem::Requirement
72
+ required_ruby_version: !ruby/object:Gem::Requirement
97
73
  none: false
98
- requirements:
99
- - - ">="
100
- - !ruby/object:Gem::Version
101
- hash: 3
102
- segments:
103
- - 0
104
- version: "0"
105
- required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
79
  none: false
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- hash: 3
111
- segments:
112
- - 0
113
- version: "0"
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
114
84
  requirements: []
115
-
116
85
  rubyforge_project: cache_method
117
- rubygems_version: 1.6.2
86
+ rubygems_version: 1.8.11
118
87
  signing_key:
119
88
  specification_version: 3
120
- summary: Lets you cache methods (to memcached, redis, etc.) sort of like you can memoize them
121
- test_files:
89
+ summary: Lets you cache methods (to memcached, redis, etc.) sort of like you can memoize
90
+ them
91
+ test_files:
122
92
  - test/helper.rb
123
93
  - test/test_cache_method.rb