cache_method 0.2.3 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +6 -0
- data/Gemfile +2 -0
- data/README.markdown +126 -55
- data/lib/cache_method/cached_result.rb +34 -59
- data/lib/cache_method/config.rb +8 -3
- data/lib/cache_method/debug.rb +4 -13
- data/lib/cache_method/generation.rb +28 -28
- data/lib/cache_method/version.rb +1 -1
- data/lib/cache_method.rb +47 -13
- data/test/helper.rb +6 -8
- data/test/test_cache_method.rb +236 -236
- metadata +2 -2
data/CHANGELOG
CHANGED
data/Gemfile
CHANGED
data/README.markdown
CHANGED
@@ -6,28 +6,35 @@ Lets you cache the results of calling methods given their arguments. Like memoiz
|
|
6
6
|
|
7
7
|
## Real-world usage
|
8
8
|
|
9
|
-
|
9
|
+
<p><a href="http://brighterplanet.com"><img src="https://s3.amazonaws.com/static.brighterplanet.com/assets/logos/flush-left/inline/green/rasterized/brighter_planet-160-transparent.png" alt="Brighter Planet logo"/></a></p>
|
10
|
+
|
11
|
+
We use `cache_method` for [data science at Brighter Planet](http://brighterplanet.com/research) and in production at
|
12
|
+
|
13
|
+
* [Brighter Planet's impact estimate web service](http://impact.brighterplanet.com)
|
14
|
+
* [Brighter Planet's reference data web service](http://data.brighterplanet.com)
|
15
|
+
|
16
|
+
## Rationale
|
17
|
+
|
18
|
+
* It should be easy to cache instance methods
|
19
|
+
* It should be easy to cache methods that depend on object state
|
20
|
+
* It should be easy to uncache a method without clearing the whole cache
|
21
|
+
* It should be easy to do all that using a default in-process store, memcached, dalli (if you're on heroku), redis, etc. (all supported by this gem through the [cache gem](https://rubygems.org/gems/cache))
|
10
22
|
|
11
23
|
## Example
|
12
24
|
|
13
25
|
require 'cache_method'
|
14
26
|
class Blog
|
15
|
-
attr_reader :name
|
16
|
-
attr_reader :url
|
17
|
-
|
27
|
+
attr_reader :name, :url
|
18
28
|
def initialize(name, url)
|
19
29
|
@name = name
|
20
30
|
@url = url
|
21
31
|
end
|
22
|
-
|
23
|
-
def
|
32
|
+
# The slow method that you want to speed up
|
33
|
+
def entries(date)
|
24
34
|
# ...
|
25
35
|
end
|
26
|
-
cache_method :
|
27
|
-
|
28
|
-
# By default, cache_method derives the cache key for an instance by getting the SHA1 hash of the Marshal.dump
|
29
|
-
# If you need to customize how an instance is recognized, you can define #as_cache_key.
|
30
|
-
# Marshal.load will be called on the result.
|
36
|
+
cache_method :entries
|
37
|
+
# Not always required
|
31
38
|
def as_cache_key
|
32
39
|
{ :name => name, :url => url }
|
33
40
|
end
|
@@ -35,37 +42,20 @@ In production use at [impact.brighterplanet.com](http://impact.brighterplanet.co
|
|
35
42
|
|
36
43
|
Then you can do
|
37
44
|
|
38
|
-
my_blog.
|
39
|
-
my_blog.
|
45
|
+
my_blog.entries(Date.today) => first time won't be cached
|
46
|
+
my_blog.entries(Date.today) => second time will come from cache
|
40
47
|
|
41
48
|
And clear them too
|
42
49
|
|
43
|
-
my_blog.cache_method_clear :
|
50
|
+
my_blog.cache_method_clear :entries
|
44
51
|
|
45
52
|
(which doesn't delete the rest of your cache)
|
46
53
|
|
47
|
-
## ActiveRecord
|
48
|
-
|
49
|
-
If you're caching methods ActiveRecord objects (aka instances of `ActiveRecord::Base`), then you should probably define something like:
|
50
|
-
|
51
|
-
class ActiveRecord::Base
|
52
|
-
def as_cache_key
|
53
|
-
attributes
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
## Debug
|
58
|
-
|
59
|
-
CacheMethod can warn you if your obj or args cache keys are suspiciously long.
|
60
|
-
|
61
|
-
require 'cache_method'
|
62
|
-
require 'cache_method/debug'
|
63
|
-
|
64
|
-
Then watch your logs.
|
65
|
-
|
66
54
|
## Configuration (and supported cache clients)
|
67
55
|
|
68
|
-
|
56
|
+
By default, an in-process, non-shared cache is used.
|
57
|
+
|
58
|
+
You can set where the cache will be stored:
|
69
59
|
|
70
60
|
CacheMethod.config.storage = Memcached.new '127.0.0.1:11211'
|
71
61
|
|
@@ -79,13 +69,30 @@ or this might even work...
|
|
79
69
|
|
80
70
|
See `Config` for the full list of supported caches.
|
81
71
|
|
82
|
-
##
|
72
|
+
## Cache keys
|
73
|
+
|
74
|
+
Caching a method involves getting cache keys for
|
75
|
+
|
76
|
+
1. the object where the method is defined - for example, `my_blog.as_cache_key`
|
77
|
+
2. the arguments passed to the method - for example, `Marshal.dump(Date.today)`, because `Date#as_cache_key` is not defined
|
78
|
+
|
79
|
+
Then the cache keys are SHA-1 hashed and combined for an overall key:
|
80
|
+
|
81
|
+
method signature + obj digest + args digest
|
82
|
+
Blog#entries + SHA1(Marshal.dump({:name="Seamus's blog",:url=>"http://numbers.brighterplanet.com"}) + SHA1(Marshal.dump(Date.today))
|
83
|
+
|
84
|
+
Technically the full cache key is
|
85
|
+
|
86
|
+
# when caching class methods
|
87
|
+
method signature + generation + args digest
|
88
|
+
# when caching instance methods
|
89
|
+
method signature + obj digest + generation + args digest
|
83
90
|
|
84
|
-
|
91
|
+
(see "Generational caching" below for an explanation of the generation part)
|
85
92
|
|
86
|
-
|
93
|
+
### #as_cache_key
|
87
94
|
|
88
|
-
|
95
|
+
As above, you can define a custom cache key for an object:
|
89
96
|
|
90
97
|
class Blog
|
91
98
|
# [...]
|
@@ -95,26 +102,97 @@ get_latest_entries doesn't take any arguments, so it must depend on my_blog.url
|
|
95
102
|
# [...]
|
96
103
|
end
|
97
104
|
|
98
|
-
If you don't define `#as_cache_key`,
|
105
|
+
If you don't define `#as_cache_key`, the default is to `Marshal.dump` the whole object. (That's not as terrible as it seems - marshalling is fast!)
|
99
106
|
|
100
|
-
|
107
|
+
### #to_cache_key (danger!)
|
101
108
|
|
102
|
-
|
109
|
+
There's another way to define a cache key, but it should be used with caution because it gives you total control.
|
103
110
|
|
104
|
-
|
111
|
+
The key is to make sure your `#to_cache_key` method identifies both the class and the instance!
|
105
112
|
|
106
|
-
|
107
|
-
Foo.bar(user.groups) # you're passing an association as an argument
|
113
|
+
### Comparison
|
108
114
|
|
115
|
+
<table>
|
116
|
+
<tr>
|
117
|
+
<th>Method</th>
|
118
|
+
<th>Must uniquely identify class</th>
|
119
|
+
<th>Must uniquely identify instance</th>
|
120
|
+
</tr>
|
121
|
+
<tr>
|
122
|
+
<td><code>#as_cache_key</code></td>
|
123
|
+
<td>N†</td>
|
124
|
+
<td>Y</td>
|
125
|
+
</tr>
|
126
|
+
<tr>
|
127
|
+
<td><code>#to_cache_key</code></td>
|
128
|
+
<td>Y</td>
|
129
|
+
<td>Y</td>
|
130
|
+
</tr>
|
131
|
+
</table>
|
132
|
+
|
133
|
+
† The class name is automatically inserted for you by calling `object.class.name`, which is what causes all the trouble with `ActiveRecord::Associations::CollectionProxy`, etc.
|
134
|
+
|
135
|
+
## ActiveRecord
|
136
|
+
|
137
|
+
If you're caching methods on instances of `ActiveRecord::Base`, and/or using them as arguments to other cached methods, then you should probably define something like:
|
138
|
+
|
139
|
+
class ActiveRecord::Base
|
140
|
+
def as_cache_key
|
141
|
+
attributes
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
If you find yourself passing association proxies as arguments to cached methods, this might be helpful:
|
146
|
+
|
147
|
+
# For use in ActiveRecord 3.0.x
|
109
148
|
class ActiveRecord::Associations::AssociationCollection
|
110
|
-
#
|
111
|
-
# also note that this example is based on ActiveRecord 3.0.10 ... YMMV
|
149
|
+
# rare use of to_cache_key
|
112
150
|
def to_cache_key
|
113
|
-
[
|
151
|
+
[
|
152
|
+
'ActiveRecord::Associations::AssociationCollection',
|
153
|
+
proxy_owner.class.name,
|
154
|
+
proxy_owner.id,
|
155
|
+
proxy_reflection.name,
|
156
|
+
conditions
|
157
|
+
].join('/')
|
114
158
|
end
|
115
159
|
end
|
116
160
|
|
117
|
-
|
161
|
+
# For use in ActiveRecord 3.2.x
|
162
|
+
class ActiveRecord::Associations::CollectionProxy
|
163
|
+
# rare use of to_cache_key
|
164
|
+
def to_cache_key
|
165
|
+
owner = proxy_association.owner
|
166
|
+
[
|
167
|
+
'ActiveRecord::Associations::CollectionProxy', # [included because we're using #to_cache_key instead of #as_cache_key ]
|
168
|
+
owner.class.name, # User
|
169
|
+
owner.id, # 'seamusabshere'
|
170
|
+
proxy_association.reflection.name, # :comments
|
171
|
+
scoped.where_sql # "WHERE `comments`.`user_id` = 'seamusabshere'" [maybe a little bit redundant, but play it safe]
|
172
|
+
].join('/')
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
Otherwise, `cache_method` will call `user.comments.class.name` which causes the proxy to load the target, i.e. load all the Comment objects into memory. You probably don't want to load 1000 AR objects just to generate a cache key.
|
177
|
+
|
178
|
+
## Generational caching
|
179
|
+
|
180
|
+
Generational caching allows clearing the cached results for only one method, for example
|
181
|
+
|
182
|
+
my_blog.cache_method_clear :entries
|
183
|
+
|
184
|
+
You can disable it to get a little speed boost
|
185
|
+
|
186
|
+
CacheMethod.config.generational = false
|
187
|
+
|
188
|
+
## Debug
|
189
|
+
|
190
|
+
CacheMethod can warn you if your obj or args cache keys are suspiciously long.
|
191
|
+
|
192
|
+
require 'cache_method'
|
193
|
+
require 'cache_method/debug'
|
194
|
+
|
195
|
+
Then watch your logs.
|
118
196
|
|
119
197
|
## Module methods
|
120
198
|
|
@@ -143,13 +221,6 @@ Rest assured that `Tiger.my_module_method` and `Lion.my_module_method` will be c
|
|
143
221
|
# cache_method :my_module_method
|
144
222
|
end
|
145
223
|
|
146
|
-
## Rationale
|
147
|
-
|
148
|
-
* 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))
|
149
|
-
* It should be easy to uncache a method without clearing the whole cache
|
150
|
-
* It should be easy to cache instance methods
|
151
|
-
* It should be easy to cache methods that depend on object state (hence `#as_cache_key`)
|
152
|
-
|
153
224
|
## Copyright
|
154
225
|
|
155
226
|
Copyright 2012 Seamus Abshere
|
@@ -1,91 +1,66 @@
|
|
1
|
-
require 'digest/sha1'
|
2
1
|
module CacheMethod
|
3
2
|
class CachedResult #:nodoc: all
|
4
|
-
|
5
|
-
def resolve_cache_key(obj)
|
6
|
-
case obj
|
7
|
-
when ::Array
|
8
|
-
obj.map do |v|
|
9
|
-
resolve_cache_key v
|
10
|
-
end
|
11
|
-
when ::Hash
|
12
|
-
obj.inject({}) do |memo, (k, v)|
|
13
|
-
kk = resolve_cache_key k
|
14
|
-
vv = resolve_cache_key v
|
15
|
-
memo[kk] = vv
|
16
|
-
memo
|
17
|
-
end
|
18
|
-
else
|
19
|
-
if obj.respond_to?(:to_cache_key)
|
20
|
-
# this is meant to be used sparingly, usually when a proxy class is involved
|
21
|
-
obj.to_cache_key
|
22
|
-
elsif obj.respond_to?(:as_cache_key)
|
23
|
-
[obj.class.name, obj.as_cache_key]
|
24
|
-
else
|
25
|
-
obj
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
CACHE_KEY_JOINER = ','
|
32
|
-
|
33
|
-
def initialize(obj, method_id, original_method_id, ttl, args)
|
3
|
+
def initialize(obj, method_id, original_method_id, ttl, args, &blk)
|
34
4
|
@obj = obj
|
35
5
|
@method_id = method_id
|
6
|
+
@method_signature = CacheMethod.method_signature obj, method_id
|
36
7
|
@original_method_id = original_method_id
|
37
|
-
@ttl = ttl
|
8
|
+
@ttl = ttl || CacheMethod.config.default_ttl
|
38
9
|
@args = args
|
10
|
+
@args_digest = args.empty? ? 'empty' : CacheMethod.digest(args)
|
11
|
+
@blk = blk
|
12
|
+
@fetch_mutex = ::Mutex.new
|
39
13
|
end
|
40
14
|
|
41
15
|
attr_reader :obj
|
42
16
|
attr_reader :method_id
|
17
|
+
attr_reader :method_signature
|
43
18
|
attr_reader :original_method_id
|
44
19
|
attr_reader :args
|
20
|
+
attr_reader :args_digest
|
21
|
+
attr_reader :blk
|
22
|
+
attr_reader :ttl
|
45
23
|
|
46
24
|
# Store things wrapped in an Array so that nil is accepted
|
47
25
|
def fetch
|
48
|
-
if
|
49
|
-
|
26
|
+
if wrapped_v = get_wrapped
|
27
|
+
wrapped_v.first
|
50
28
|
else
|
51
|
-
|
52
|
-
|
53
|
-
|
29
|
+
@fetch_mutex.synchronize do
|
30
|
+
(get_wrapped || set_wrapped).first
|
31
|
+
end
|
54
32
|
end
|
55
33
|
end
|
56
34
|
|
57
35
|
def exist?
|
58
|
-
|
36
|
+
CacheMethod.config.storage.exist?(cache_key)
|
59
37
|
end
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
end
|
64
|
-
|
38
|
+
|
39
|
+
private
|
40
|
+
|
65
41
|
def cache_key
|
66
42
|
if obj.is_a?(::Class) or obj.is_a?(::Module)
|
67
43
|
[ 'CacheMethod', 'CachedResult', method_signature, current_generation, args_digest ].compact.join CACHE_KEY_JOINER
|
68
44
|
else
|
69
|
-
[ 'CacheMethod', 'CachedResult', method_signature,
|
45
|
+
[ 'CacheMethod', 'CachedResult', method_signature, CacheMethod.digest(obj), current_generation, args_digest ].compact.join CACHE_KEY_JOINER
|
70
46
|
end
|
71
47
|
end
|
72
|
-
|
73
|
-
def method_signature
|
74
|
-
@method_signature ||= ::CacheMethod.method_signature(obj, method_id)
|
75
|
-
end
|
76
|
-
|
77
|
-
def obj_digest
|
78
|
-
@obj_digest ||= ::Digest::SHA1.hexdigest(::Marshal.dump(CachedResult.resolve_cache_key(obj)))
|
79
|
-
end
|
80
|
-
|
81
|
-
def args_digest
|
82
|
-
@args_digest ||= args.empty? ? 'empty' : ::Digest::SHA1.hexdigest(::Marshal.dump(CachedResult.resolve_cache_key(args)))
|
83
|
-
end
|
84
|
-
|
48
|
+
|
85
49
|
def current_generation
|
86
|
-
if
|
87
|
-
|
50
|
+
if CacheMethod.config.generational?
|
51
|
+
Generation.new(obj, method_id).fetch
|
88
52
|
end
|
89
53
|
end
|
54
|
+
|
55
|
+
def get_wrapped
|
56
|
+
CacheMethod.config.storage.get cache_key
|
57
|
+
end
|
58
|
+
|
59
|
+
def set_wrapped
|
60
|
+
v = obj.send(*([original_method_id]+args), &blk)
|
61
|
+
wrapped_v = [v]
|
62
|
+
CacheMethod.config.storage.set cache_key, wrapped_v, ttl
|
63
|
+
wrapped_v
|
64
|
+
end
|
90
65
|
end
|
91
66
|
end
|
data/lib/cache_method/config.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'cache'
|
2
|
-
|
2
|
+
|
3
3
|
module CacheMethod
|
4
4
|
# Here's where you set config options.
|
5
5
|
#
|
@@ -9,7 +9,10 @@ module CacheMethod
|
|
9
9
|
#
|
10
10
|
# You'd probably put this in your Rails config/initializers, for example.
|
11
11
|
class Config
|
12
|
-
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@mutex = ::Mutex.new
|
15
|
+
end
|
13
16
|
|
14
17
|
# Whether to use "generational" caching. Default is true.
|
15
18
|
#
|
@@ -44,7 +47,9 @@ module CacheMethod
|
|
44
47
|
end
|
45
48
|
|
46
49
|
def storage #:nodoc:
|
47
|
-
@storage
|
50
|
+
@storage || @mutex.synchronize do
|
51
|
+
@storage ||= ::Cache.new
|
52
|
+
end
|
48
53
|
end
|
49
54
|
|
50
55
|
# TTL for method caches. Defaults to 24 hours or 86,400 seconds.
|
data/lib/cache_method/debug.rb
CHANGED
@@ -1,17 +1,8 @@
|
|
1
1
|
module CacheMethod
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
$stderr.puts "Warn: #{l} long obj digest. #{obj.class} -> #{CachedResult.resolve_cache_key(obj).inspect}"
|
6
|
-
end
|
7
|
-
@obj_digest ||= ::Digest::SHA1.hexdigest(::Marshal.dump(CachedResult.resolve_cache_key(obj)))
|
8
|
-
end
|
9
|
-
|
10
|
-
def args_digest
|
11
|
-
if (l = ::Marshal.dump(CachedResult.resolve_cache_key(args)).length) > 500
|
12
|
-
$stderr.puts "Warn: #{l} long args digest. #{method_signature}(#{CachedResult.resolve_cache_key(args).inspect})"
|
13
|
-
end
|
14
|
-
@args_digest ||= args.empty? ? 'empty' : ::Digest::SHA1.hexdigest(::Marshal.dump(CachedResult.resolve_cache_key(args)))
|
2
|
+
def CacheMethod.digest(obj)
|
3
|
+
if (l = ::Marshal.dump(resolve_cache_key(obj)).length) > 500
|
4
|
+
$stderr.puts "[cache_method] Warn: #{l} long digest. #{obj.class} -> #{resolve_cache_key(obj).inspect}"
|
15
5
|
end
|
6
|
+
::Digest::SHA1.hexdigest ::Marshal.dump(resolve_cache_key(obj))
|
16
7
|
end
|
17
8
|
end
|
@@ -1,49 +1,49 @@
|
|
1
|
-
require 'digest/sha1'
|
2
1
|
module CacheMethod
|
3
2
|
class Generation #:nodoc: all
|
4
|
-
class << self
|
5
|
-
def random_name
|
6
|
-
rand(100_000_000).to_s
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
3
|
def initialize(obj, method_id)
|
11
4
|
@obj = obj
|
12
5
|
@method_id = method_id
|
6
|
+
@method_signature = CacheMethod.method_signature obj, method_id
|
7
|
+
@fetch_mutex = ::Mutex.new
|
13
8
|
end
|
14
9
|
|
15
10
|
attr_reader :obj
|
16
11
|
attr_reader :method_id
|
17
|
-
|
18
|
-
|
19
|
-
|
12
|
+
attr_reader :method_signature
|
13
|
+
|
14
|
+
def fetch
|
15
|
+
if existing = get
|
16
|
+
existing
|
17
|
+
else
|
18
|
+
@fetch_mutex.synchronize do
|
19
|
+
get || set
|
20
|
+
end
|
21
|
+
end
|
20
22
|
end
|
21
23
|
|
22
|
-
def
|
23
|
-
|
24
|
+
def mark_passing
|
25
|
+
CacheMethod.config.storage.delete cache_key
|
24
26
|
end
|
25
|
-
|
27
|
+
|
28
|
+
private
|
29
|
+
|
26
30
|
def cache_key
|
27
31
|
if obj.is_a?(::Class) or obj.is_a?(::Module)
|
28
|
-
[ 'CacheMethod', 'Generation', method_signature ].join
|
32
|
+
[ 'CacheMethod', 'Generation', method_signature ].join CACHE_KEY_JOINER
|
29
33
|
else
|
30
|
-
[ 'CacheMethod', 'Generation', method_signature,
|
34
|
+
[ 'CacheMethod', 'Generation', method_signature, CacheMethod.digest(obj) ].join CACHE_KEY_JOINER
|
31
35
|
end
|
32
36
|
end
|
33
|
-
|
34
|
-
def
|
35
|
-
|
36
|
-
cached_v
|
37
|
-
else
|
38
|
-
v = Generation.random_name
|
39
|
-
# never expire!
|
40
|
-
Config.instance.storage.set cache_key, v, 0
|
41
|
-
v
|
42
|
-
end
|
37
|
+
|
38
|
+
def get
|
39
|
+
CacheMethod.config.storage.get cache_key
|
43
40
|
end
|
44
|
-
|
45
|
-
def
|
46
|
-
|
41
|
+
|
42
|
+
def set
|
43
|
+
random_name = ::Kernel.rand(1e11).to_s
|
44
|
+
# never expire!
|
45
|
+
CacheMethod.config.storage.set cache_key, random_name, 0
|
46
|
+
random_name
|
47
47
|
end
|
48
48
|
end
|
49
49
|
end
|
data/lib/cache_method/version.rb
CHANGED
data/lib/cache_method.rb
CHANGED
@@ -1,24 +1,60 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'digest/sha1'
|
3
|
+
|
1
4
|
require 'cache_method/config'
|
2
5
|
require 'cache_method/cached_result'
|
3
6
|
require 'cache_method/generation'
|
4
7
|
|
5
|
-
# See the README.rdoc for more info!
|
6
8
|
module CacheMethod
|
7
|
-
|
8
|
-
|
9
|
+
MUTEX = ::Mutex.new
|
10
|
+
CACHE_KEY_JOINER = ','
|
11
|
+
|
12
|
+
def CacheMethod.config #:nodoc:
|
13
|
+
@config || MUTEX.synchronize do
|
14
|
+
@config ||= Config.new
|
15
|
+
end
|
9
16
|
end
|
10
17
|
|
11
|
-
def
|
18
|
+
def CacheMethod.klass_name(obj) #:nodoc:
|
12
19
|
(obj.is_a?(::Class) or obj.is_a?(::Module)) ? obj.to_s : obj.class.to_s
|
13
20
|
end
|
14
21
|
|
15
|
-
def
|
22
|
+
def CacheMethod.method_delimiter(obj) #:nodoc:
|
16
23
|
(obj.is_a?(::Class) or obj.is_a?(::Module)) ? '.' : '#'
|
17
24
|
end
|
18
25
|
|
19
|
-
def
|
26
|
+
def CacheMethod.method_signature(obj, method_id) #:nodoc:
|
20
27
|
[ klass_name(obj), method_id ].join method_delimiter(obj)
|
21
28
|
end
|
29
|
+
|
30
|
+
def CacheMethod.resolve_cache_key(obj)
|
31
|
+
case obj
|
32
|
+
when ::Array
|
33
|
+
obj.map do |v|
|
34
|
+
resolve_cache_key v
|
35
|
+
end
|
36
|
+
when ::Hash
|
37
|
+
obj.inject({}) do |memo, (k, v)|
|
38
|
+
kk = resolve_cache_key k
|
39
|
+
vv = resolve_cache_key v
|
40
|
+
memo[kk] = vv
|
41
|
+
memo
|
42
|
+
end
|
43
|
+
else
|
44
|
+
if obj.respond_to?(:to_cache_key)
|
45
|
+
# this is meant to be used sparingly, usually when a proxy class is involved
|
46
|
+
obj.to_cache_key
|
47
|
+
elsif obj.respond_to?(:as_cache_key)
|
48
|
+
[obj.class.name, obj.as_cache_key]
|
49
|
+
else
|
50
|
+
obj
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def CacheMethod.digest(obj)
|
56
|
+
::Digest::SHA1.hexdigest ::Marshal.dump(resolve_cache_key(obj))
|
57
|
+
end
|
22
58
|
|
23
59
|
# All Objects, including instances and Classes, get the <tt>#cache_method_clear</tt> method.
|
24
60
|
module InstanceMethods
|
@@ -59,15 +95,13 @@ module CacheMethod
|
|
59
95
|
def cache_method(method_id, ttl = nil)
|
60
96
|
original_method_id = "_uncached_#{method_id}"
|
61
97
|
alias_method original_method_id, method_id
|
62
|
-
define_method method_id do |*args|
|
63
|
-
::CacheMethod::CachedResult.new(self, method_id, original_method_id, ttl, args).fetch
|
98
|
+
define_method method_id do |*args, &blk|
|
99
|
+
::CacheMethod::CachedResult.new(self, method_id, original_method_id, ttl, args, &blk).fetch
|
64
100
|
end
|
65
101
|
end
|
66
102
|
end
|
67
103
|
end
|
68
104
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
::Module.send :include, ::CacheMethod::ClassMethods
|
73
|
-
end
|
105
|
+
::Object.send :include, ::CacheMethod::InstanceMethods
|
106
|
+
::Class.send :include, ::CacheMethod::ClassMethods
|
107
|
+
::Module.send :include, ::CacheMethod::ClassMethods
|
data/test/helper.rb
CHANGED
@@ -1,19 +1,17 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require 'bundler'
|
3
|
-
|
4
|
-
require '
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'minitest/spec'
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'minitest/reporters'
|
6
|
+
MiniTest::Unit.runner = MiniTest::SuiteRunner.new
|
7
|
+
MiniTest::Unit.runner.reporters << MiniTest::Reporters::SpecReporter.new
|
5
8
|
|
6
9
|
if ::Bundler.definition.specs['ruby-debug19'].first or ::Bundler.definition.specs['ruby-debug'].first
|
7
10
|
require 'ruby-debug'
|
8
11
|
end
|
9
12
|
|
10
|
-
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
11
|
-
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
12
13
|
require 'cache_method'
|
13
14
|
|
14
|
-
class Test::Unit::TestCase
|
15
|
-
end
|
16
|
-
|
17
15
|
class CopyCat1
|
18
16
|
attr_reader :name
|
19
17
|
def initialize(name)
|