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 +40 -12
- data/cache_method.gemspec +1 -1
- data/lib/cache_method/cached_result.rb +13 -16
- data/lib/cache_method/config.rb +14 -0
- data/lib/cache_method/{epoch.rb → generation.rb} +8 -19
- data/lib/cache_method/version.rb +1 -1
- data/lib/cache_method.rb +8 -8
- data/test/helper.rb +21 -4
- data/test/test_cache_method.rb +63 -1
- metadata +47 -77
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
|
-
|
24
|
+
# ...
|
20
25
|
end
|
21
|
-
|
22
|
-
# What you get with this gem
|
23
26
|
cache_method :get_latest_entries
|
24
27
|
|
25
|
-
#
|
26
|
-
# It's
|
27
|
-
|
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
|
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 '
|
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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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?
|
39
|
-
[ 'CacheMethod', 'CachedResult', method_signature,
|
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,
|
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
|
58
|
-
|
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
|
data/lib/cache_method/config.rb
CHANGED
@@ -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
|
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(
|
5
|
+
rand(100_000_000).to_s
|
16
6
|
end
|
17
7
|
end
|
18
8
|
|
19
|
-
def initialize(
|
20
|
-
|
21
|
-
|
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', '
|
27
|
+
[ 'CacheMethod', 'Generation', method_signature ].join ','
|
39
28
|
else
|
40
|
-
[ 'CacheMethod', '
|
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 =
|
37
|
+
v = Generation.random_name
|
49
38
|
# never expire!
|
50
39
|
Config.instance.storage.set cache_key, v, 0
|
51
40
|
v
|
data/lib/cache_method/version.rb
CHANGED
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 :
|
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
|
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
|
-
#
|
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.
|
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
|
data/test/test_cache_method.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
19
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
60
|
-
segments:
|
61
|
-
- 0
|
62
|
-
version: "0"
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
63
44
|
type: :development
|
64
|
-
|
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/
|
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
|
-
|
102
|
-
|
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
|
-
|
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.
|
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
|
121
|
-
|
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
|