cache_method 0.2.3 → 0.2.4
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.
- 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)
|