jashmenn-method_cache 0.7.1.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.
- data/LICENSE +20 -0
- data/README.rdoc +40 -0
- data/Rakefile +45 -0
- data/VERSION.yml +5 -0
- data/lib/method_cache.rb +186 -0
- data/lib/method_cache/proxy.rb +220 -0
- data/test/method_cache_remote_test.rb +25 -0
- data/test/method_cache_test.rb +201 -0
- data/test/test_helper.rb +18 -0
- metadata +55 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Justin Balthrop
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
= MethodCache
|
2
|
+
|
3
|
+
MethodCache lets you easily cache the results of any instance method or class method in
|
4
|
+
Ruby.
|
5
|
+
|
6
|
+
== Usage:
|
7
|
+
|
8
|
+
class Foo
|
9
|
+
extend MethodCache
|
10
|
+
|
11
|
+
cache_method :bar
|
12
|
+
def bar
|
13
|
+
# do expensive calculation
|
14
|
+
end
|
15
|
+
|
16
|
+
cache_class_method :baz, :clone => true, :expiry => 1.day
|
17
|
+
def self.baz
|
18
|
+
# do some expensive calculation that will be invalid tomorrow
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
foo = Foo.new
|
23
|
+
foo.bar # does calculation
|
24
|
+
foo.bar # cached
|
25
|
+
|
26
|
+
Foo.baz # does calculation
|
27
|
+
Foo.baz # cached
|
28
|
+
|
29
|
+
Foo.invalidate_cached_method(:baz)
|
30
|
+
|
31
|
+
Foo.baz # does calculation
|
32
|
+
Foo.baz # cached
|
33
|
+
|
34
|
+
== Install:
|
35
|
+
|
36
|
+
gem install method_cache
|
37
|
+
|
38
|
+
== License:
|
39
|
+
|
40
|
+
Copyright (c) 2010 Justin Balthrop, Geni.com; Published under The MIT License, see LICENSE
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'jeweler'
|
7
|
+
Jeweler::Tasks.new do |s|
|
8
|
+
s.name = "jashmenn-method_cache"
|
9
|
+
s.summary = %Q{Simple memcache-based memoization library for Ruby}
|
10
|
+
s.email = "code@justinbalthrop.com"
|
11
|
+
s.homepage = "http://github.com/ninjudd/method_cache"
|
12
|
+
s.description = "Simple memcache-based memoization library for Ruby"
|
13
|
+
s.authors = ["Justin Balthrop"]
|
14
|
+
# s.add_dependency('memcache', '>= 1.0.0')
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
19
|
+
end
|
20
|
+
|
21
|
+
Rake::TestTask.new do |t|
|
22
|
+
t.libs << ['lib', 'test']
|
23
|
+
t.pattern = 'test/**/*_test.rb'
|
24
|
+
t.verbose = false
|
25
|
+
end
|
26
|
+
|
27
|
+
Rake::RDocTask.new do |rdoc|
|
28
|
+
rdoc.rdoc_dir = 'rdoc'
|
29
|
+
rdoc.title = 'method_cache'
|
30
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
31
|
+
rdoc.rdoc_files.include('README*')
|
32
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
33
|
+
end
|
34
|
+
|
35
|
+
begin
|
36
|
+
require 'rcov/rcovtask'
|
37
|
+
Rcov::RcovTask.new do |t|
|
38
|
+
t.libs << 'test'
|
39
|
+
t.test_files = FileList['test/**/*_test.rb']
|
40
|
+
t.verbose = true
|
41
|
+
end
|
42
|
+
rescue LoadError
|
43
|
+
end
|
44
|
+
|
45
|
+
task :default => :test
|
data/VERSION.yml
ADDED
data/lib/method_cache.rb
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__))
|
2
|
+
require 'method_cache/proxy'
|
3
|
+
|
4
|
+
module MethodCache
|
5
|
+
def cache_method(method_name, opts = {})
|
6
|
+
method_name = method_name.to_sym
|
7
|
+
proxy = opts.kind_of?(Proxy) ? opts : Proxy.new(method_name, opts)
|
8
|
+
|
9
|
+
if self.class == Class
|
10
|
+
return if instance_methods.include?(proxy.method_name_without_caching)
|
11
|
+
|
12
|
+
if cached_instance_methods.empty?
|
13
|
+
include(InvalidationMethods)
|
14
|
+
extend(MethodAdded)
|
15
|
+
end
|
16
|
+
|
17
|
+
cached_instance_methods[method_name] = nil
|
18
|
+
if method_defined?(method_name) or private_method_defined?(method_name)
|
19
|
+
if proxy.opts[:counter]
|
20
|
+
define_method "increment_#{method_name}", proxy.counter_method(:increment)
|
21
|
+
define_method "decrement_#{method_name}", proxy.counter_method(:decrement)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Replace instance method.
|
25
|
+
alias_method proxy.method_name_without_caching, method_name
|
26
|
+
define_method method_name, proxy.method_with_caching
|
27
|
+
end
|
28
|
+
cached_instance_methods[method_name] = proxy
|
29
|
+
|
30
|
+
elsif self.class == Module
|
31
|
+
# We will alias all methods when the module is mixed-in.
|
32
|
+
extend(ModuleAdded) if cached_module_methods.empty?
|
33
|
+
cached_module_methods[method_name.to_sym] = proxy
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def cache_class_method(method_name, opts = {})
|
38
|
+
method_name = method_name.to_sym
|
39
|
+
proxy = opts.kind_of?(Proxy) ? opts : Proxy.new(method_name, opts)
|
40
|
+
|
41
|
+
return if methods.include?(proxy.method_name_without_caching)
|
42
|
+
|
43
|
+
if cached_class_methods.empty?
|
44
|
+
extend(InvalidationMethods)
|
45
|
+
extend(SingletonMethodAdded)
|
46
|
+
end
|
47
|
+
|
48
|
+
method_name = method_name.to_sym
|
49
|
+
cached_class_methods[method_name] = nil
|
50
|
+
if class_method_defined?(method_name)
|
51
|
+
(class << self; self; end).module_eval do
|
52
|
+
if proxy.opts[:counter]
|
53
|
+
define_method "increment_#{method_name}", proxy.counter_method(:increment)
|
54
|
+
define_method "decrement_#{method_name}", proxy.counter_method(:decrement)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Replace class method.
|
58
|
+
alias_method proxy.method_name_without_caching, method_name
|
59
|
+
define_method method_name, proxy.method_with_caching
|
60
|
+
end
|
61
|
+
end
|
62
|
+
cached_class_methods[method_name] = proxy
|
63
|
+
end
|
64
|
+
|
65
|
+
def class_method_defined?(method_name)
|
66
|
+
method(method_name)
|
67
|
+
true
|
68
|
+
rescue NameError
|
69
|
+
false
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.default_cache
|
73
|
+
@default_cache ||= {}
|
74
|
+
end
|
75
|
+
|
76
|
+
def cached_instance_methods(method_name = nil)
|
77
|
+
if method_name
|
78
|
+
method_name = method_name.to_sym
|
79
|
+
ancestors.each do |klass|
|
80
|
+
next unless klass.kind_of?(MethodCache)
|
81
|
+
proxy = klass.cached_instance_methods[method_name]
|
82
|
+
return proxy if proxy
|
83
|
+
end
|
84
|
+
nil
|
85
|
+
else
|
86
|
+
@cached_instance_methods ||= {}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def cached_class_methods(method_name = nil)
|
91
|
+
if method_name
|
92
|
+
method_name = method_name.to_sym
|
93
|
+
ancestors.each do |klass|
|
94
|
+
next unless klass.kind_of?(MethodCache)
|
95
|
+
proxy = klass.cached_class_methods[method_name]
|
96
|
+
return proxy if proxy
|
97
|
+
end
|
98
|
+
nil
|
99
|
+
else
|
100
|
+
@cached_class_methods ||= {}
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def cached_module_methods(method_name = nil)
|
105
|
+
if method_name
|
106
|
+
cached_module_methods[method_name.to_sym]
|
107
|
+
else
|
108
|
+
@cached_module_methods ||= {}
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.disable(&block)
|
113
|
+
@disabled, old = true, @disabled
|
114
|
+
yield
|
115
|
+
ensure
|
116
|
+
@disabled = old
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.disabled?
|
120
|
+
@disabled
|
121
|
+
end
|
122
|
+
|
123
|
+
module InvalidationMethods
|
124
|
+
def invalidate_cached_method(method_name, *args, &block)
|
125
|
+
cached_method(method_name, args).invalidate(&block)
|
126
|
+
end
|
127
|
+
|
128
|
+
def method_value_cached?(method_name, *args)
|
129
|
+
cached_method(method_name, args).cached?
|
130
|
+
end
|
131
|
+
|
132
|
+
def update_cached_method(method_name, *args, &block)
|
133
|
+
cached_method(method_name, args).update(&block)
|
134
|
+
end
|
135
|
+
|
136
|
+
def without_method_cache(&block)
|
137
|
+
MethodCache.disable(&block)
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
def cached_method(method_name, args)
|
143
|
+
if self.kind_of?(Class) or self.kind_of?(Module)
|
144
|
+
proxy = cached_class_methods(method_name)
|
145
|
+
else
|
146
|
+
proxy = self.class.send(:cached_instance_methods, method_name)
|
147
|
+
end
|
148
|
+
raise "method '#{method_name}' not cached" unless proxy
|
149
|
+
proxy.bind(self, args)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
module MethodAdded
|
154
|
+
def method_added(method_name)
|
155
|
+
if proxy = cached_instance_methods(method_name)
|
156
|
+
cache_method(method_name, proxy)
|
157
|
+
end
|
158
|
+
super
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
module SingletonMethodAdded
|
163
|
+
def singleton_method_added(method_name)
|
164
|
+
if proxy = cached_class_methods(method_name)
|
165
|
+
cache_class_method(method_name, proxy)
|
166
|
+
end
|
167
|
+
super
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
module ModuleAdded
|
172
|
+
def extended(mod)
|
173
|
+
mod.extend(MethodCache)
|
174
|
+
cached_module_methods.each do |method_name, proxy|
|
175
|
+
mod.cache_class_method(method_name, proxy)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def included(mod)
|
180
|
+
mod.extend(MethodCache)
|
181
|
+
cached_module_methods.each do |method_name, proxy|
|
182
|
+
mod.cache_method(method_name, proxy)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
class Object
|
4
|
+
def metaclass; class << self; self; end; end
|
5
|
+
end
|
6
|
+
|
7
|
+
module MethodCache
|
8
|
+
class Proxy
|
9
|
+
attr_reader :method_name, :opts, :args, :target
|
10
|
+
NULL = 'NULL'
|
11
|
+
|
12
|
+
def initialize(method_name, opts)
|
13
|
+
opts[:cache] ||= :counters if opts[:counter]
|
14
|
+
@method_name = method_name
|
15
|
+
@opts = opts
|
16
|
+
end
|
17
|
+
|
18
|
+
def bind(target, args)
|
19
|
+
self.clone.bind!(target, args)
|
20
|
+
end
|
21
|
+
|
22
|
+
def bind!(target, args)
|
23
|
+
@target = target
|
24
|
+
@args = args
|
25
|
+
@key = nil
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def invalidate
|
30
|
+
if block_given?
|
31
|
+
# Only invalidate if the block returns true.
|
32
|
+
value = cache[key]
|
33
|
+
return if value and not yield(value)
|
34
|
+
end
|
35
|
+
cache.delete(key)
|
36
|
+
end
|
37
|
+
|
38
|
+
def context
|
39
|
+
opts[:context]
|
40
|
+
end
|
41
|
+
|
42
|
+
def version
|
43
|
+
dynamic_opt(:version)
|
44
|
+
end
|
45
|
+
|
46
|
+
def cached?
|
47
|
+
not cache[key].nil?
|
48
|
+
end
|
49
|
+
|
50
|
+
def update
|
51
|
+
value = block_given? ? yield(cache[key]) : target.send(method_name_without_caching, *args)
|
52
|
+
write_to_cache(key, value)
|
53
|
+
value
|
54
|
+
end
|
55
|
+
|
56
|
+
def value
|
57
|
+
value = opts[:counter] ? cache.count(key) : cache[key] unless MethodCache.disabled?
|
58
|
+
value = nil unless valid?(:load, value)
|
59
|
+
|
60
|
+
if value.nil?
|
61
|
+
value = target.send(method_name_without_caching, *args)
|
62
|
+
raise "non-integer value returned by counter method" if opts[:counter] and not value.kind_of?(Fixnum)
|
63
|
+
write_to_cache(key, value) if valid?(:save, value)
|
64
|
+
end
|
65
|
+
|
66
|
+
value = nil if value == NULL
|
67
|
+
if clone? and value
|
68
|
+
value.clone
|
69
|
+
else
|
70
|
+
value
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def method_with_caching
|
75
|
+
proxy = self # Need access to the proxy in the closure.
|
76
|
+
|
77
|
+
lambda do |*args|
|
78
|
+
proxy.bind(self, args).value
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def counter_method(method_name)
|
83
|
+
proxy = self # Need access to the proxy in the closure.
|
84
|
+
|
85
|
+
lambda do |*args|
|
86
|
+
if args.last.kind_of?(Hash) and args.last.keys == [:by]
|
87
|
+
amount = args.last[:by]
|
88
|
+
args.pop
|
89
|
+
end
|
90
|
+
proxy.bind(self, args).send(method_name, amount || 1)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def method_name_without_caching
|
95
|
+
@method_name_without_caching ||= begin
|
96
|
+
base_name, punctuation = method_name.to_s.sub(/([?!=])$/, ''), $1
|
97
|
+
"#{base_name}_without_caching#{punctuation}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def cache
|
102
|
+
if @cache.nil?
|
103
|
+
@cache = opts[:cache] || MethodCache.default_cache
|
104
|
+
@cache = Memcache.pool[@cache] if @cache.kind_of?(Symbol)
|
105
|
+
if not @cache.respond_to?(:[]) and @cache.respond_to?(:get)
|
106
|
+
@cache.metaclass.module_eval do
|
107
|
+
define_method :[] do |key|
|
108
|
+
get(key)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
@cache
|
114
|
+
end
|
115
|
+
|
116
|
+
def local?
|
117
|
+
cache.kind_of?(Hash)
|
118
|
+
end
|
119
|
+
|
120
|
+
def clone?
|
121
|
+
!!opts[:clone]
|
122
|
+
end
|
123
|
+
|
124
|
+
def key
|
125
|
+
if @key.nil?
|
126
|
+
arg_string = ([method_name, target] + args).collect do |arg|
|
127
|
+
object_key(arg)
|
128
|
+
end.join('|')
|
129
|
+
@key = ['m', version, arg_string].compact.join('|')
|
130
|
+
@key = "m|#{Digest::SHA1.hexdigest(@key)}" if @key.length > 250
|
131
|
+
end
|
132
|
+
@key
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def expiry(value)
|
138
|
+
value = dynamic_opt(:expiry, value).to_i
|
139
|
+
if defined?(Memcache) and cache.kind_of?(Memcache)
|
140
|
+
{:expiry => value}
|
141
|
+
else
|
142
|
+
value
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def valid?(type, value)
|
147
|
+
name = "#{type}_validation".to_sym
|
148
|
+
return true unless opts[name]
|
149
|
+
return unless value
|
150
|
+
|
151
|
+
dynamic_opt(name, value)
|
152
|
+
end
|
153
|
+
|
154
|
+
def dynamic_opt(name, value = nil)
|
155
|
+
if opts[name].kind_of?(Proc)
|
156
|
+
proc = opts[name].bind(target)
|
157
|
+
case proc.arity
|
158
|
+
when 0 then proc.call()
|
159
|
+
when 1 then proc.call(value)
|
160
|
+
else
|
161
|
+
proc.call(value, *args)
|
162
|
+
end
|
163
|
+
else
|
164
|
+
opts[name]
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def write_to_cache(key, value)
|
169
|
+
unless opts[:counter]
|
170
|
+
value = value.nil? ? NULL : value
|
171
|
+
end
|
172
|
+
if cache.kind_of?(Hash)
|
173
|
+
raise 'expiry not permitted when cache is a Hash' if opts[:expiry]
|
174
|
+
raise 'counter cache not permitted when cache is a Hash' if opts[:counter]
|
175
|
+
cache[key] = value
|
176
|
+
elsif opts[:counter]
|
177
|
+
cache.write(key, value.to_s, expiry(value))
|
178
|
+
else
|
179
|
+
cache.set(key, value, expiry(value))
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def increment(amount)
|
184
|
+
raise "cannot increment non-counter method" unless opts[:counter]
|
185
|
+
cache.incr(key, amount)
|
186
|
+
end
|
187
|
+
|
188
|
+
def decrement(amount)
|
189
|
+
raise "cannot decrement non-counter method" unless opts[:counter]
|
190
|
+
cache.decr(key, amount)
|
191
|
+
end
|
192
|
+
|
193
|
+
def object_key(arg)
|
194
|
+
return "#{class_key(arg.class)}-#{arg.string_hash}" if arg.respond_to?(:string_hash)
|
195
|
+
|
196
|
+
case arg
|
197
|
+
when NilClass then 'nil'
|
198
|
+
when TrueClass then 'true'
|
199
|
+
when FalseClass then 'false'
|
200
|
+
when Numeric then arg.to_s
|
201
|
+
when Symbol then ":#{arg}"
|
202
|
+
when String then "'#{arg}'"
|
203
|
+
when Class, Module then class_key(arg)
|
204
|
+
when Hash
|
205
|
+
'{' + arg.collect {|key, value| "#{object_key(key)}=#{object_key(value)}"}.sort.join(',') + '}'
|
206
|
+
when Array
|
207
|
+
'[' + arg.collect {|item| object_key(item)}.join(',') + ']'
|
208
|
+
when defined?(ActiveRecord::Base) && ActiveRecord::Base
|
209
|
+
"#{class_key(arg.class)}-#{arg.id}"
|
210
|
+
else
|
211
|
+
hash = local? ? arg.hash : Marshal.dump(arg).hash
|
212
|
+
"#{class_key(arg.class)}-#{hash}"
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def class_key(klass)
|
217
|
+
klass.respond_to?(:version) ? "#{klass.name}_#{klass.version(context)}" : klass.name
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'dalli'
|
3
|
+
|
4
|
+
PORT = 19112
|
5
|
+
$client = Dalli::Client.new("localhost:#{PORT}")
|
6
|
+
|
7
|
+
class FooBar
|
8
|
+
extend MethodCache
|
9
|
+
|
10
|
+
cache_method :foo, :cache => $client
|
11
|
+
def foo
|
12
|
+
'bar'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class MethodCacheRemoteTest < Test::Unit::TestCase
|
17
|
+
|
18
|
+
should 'work with dalli client' do
|
19
|
+
start_memcache(PORT)
|
20
|
+
$client.flush
|
21
|
+
|
22
|
+
f = FooBar.new
|
23
|
+
assert_equal 'bar', f.foo
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'memcache'
|
3
|
+
|
4
|
+
class Foo
|
5
|
+
extend MethodCache
|
6
|
+
|
7
|
+
def foo(i)
|
8
|
+
@i ||= 0
|
9
|
+
@i += i
|
10
|
+
end
|
11
|
+
cache_method :foo
|
12
|
+
|
13
|
+
cache_method :bar
|
14
|
+
def bar
|
15
|
+
@i ||= 0
|
16
|
+
@i += 1
|
17
|
+
end
|
18
|
+
|
19
|
+
@@i = 0
|
20
|
+
def baz(i)
|
21
|
+
@@i += i
|
22
|
+
end
|
23
|
+
cache_method :baz, :cache => :remote
|
24
|
+
|
25
|
+
cache_class_method :bap
|
26
|
+
def self.bap(i)
|
27
|
+
@i ||= 0
|
28
|
+
@i += i
|
29
|
+
end
|
30
|
+
|
31
|
+
cache_class_method :zap, :counter => true
|
32
|
+
def self.zap
|
33
|
+
0
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_accessor :z
|
37
|
+
def zang
|
38
|
+
self.z ||= 1
|
39
|
+
self.z += 1
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
cache_method :zang
|
43
|
+
end
|
44
|
+
|
45
|
+
module Bar
|
46
|
+
extend MethodCache
|
47
|
+
|
48
|
+
cache_method :foo
|
49
|
+
def foo(i)
|
50
|
+
@i ||= 0
|
51
|
+
@i += i
|
52
|
+
end
|
53
|
+
|
54
|
+
cache_method :foo_count, :counter => true, :cache => :default
|
55
|
+
def foo_count(key)
|
56
|
+
100
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class Baz
|
61
|
+
include Bar
|
62
|
+
extend Bar
|
63
|
+
end
|
64
|
+
|
65
|
+
class TestMethodCache < Test::Unit::TestCase
|
66
|
+
should 'cache methods locally' do
|
67
|
+
a = Foo.new
|
68
|
+
f1 = a.foo(1)
|
69
|
+
f2 = a.foo(2)
|
70
|
+
|
71
|
+
assert_equal 1, f1
|
72
|
+
assert_equal 3, f2
|
73
|
+
|
74
|
+
assert f1 == a.foo(1)
|
75
|
+
assert f1 != f2
|
76
|
+
assert f2 == a.foo(2)
|
77
|
+
|
78
|
+
b = a.bar
|
79
|
+
assert b == a.bar
|
80
|
+
assert b == a.bar
|
81
|
+
end
|
82
|
+
|
83
|
+
should 'disable method_cache' do
|
84
|
+
a = Foo.new
|
85
|
+
f1 = a.foo(1)
|
86
|
+
|
87
|
+
f2 = a.without_method_cache do
|
88
|
+
a.foo(1)
|
89
|
+
end
|
90
|
+
|
91
|
+
f3 = MethodCache.disable do
|
92
|
+
a.foo(1)
|
93
|
+
end
|
94
|
+
|
95
|
+
assert f1 != f2
|
96
|
+
assert f1 != f3
|
97
|
+
assert f2 != f3
|
98
|
+
end
|
99
|
+
|
100
|
+
should 'cache methods remotely' do
|
101
|
+
a = Foo.new
|
102
|
+
b1 = a.baz(1)
|
103
|
+
b2 = a.baz(2)
|
104
|
+
|
105
|
+
assert_equal 1, b1
|
106
|
+
assert_equal 3, b2
|
107
|
+
|
108
|
+
assert b1 == a.baz(1)
|
109
|
+
assert b1 != b2
|
110
|
+
assert b2 == a.baz(2)
|
111
|
+
end
|
112
|
+
|
113
|
+
should 'cache class methods' do
|
114
|
+
assert_equal 10, Foo.bap(10)
|
115
|
+
assert_equal 23, Foo.bap(13)
|
116
|
+
assert_equal 10, Foo.bap(10)
|
117
|
+
assert_equal 23, Foo.bap(13)
|
118
|
+
end
|
119
|
+
|
120
|
+
should 'cache methods for mixins' do
|
121
|
+
a = Baz.new
|
122
|
+
|
123
|
+
assert_equal 1, a.foo(1)
|
124
|
+
assert_equal 1, a.foo(1)
|
125
|
+
assert_equal 3, a.foo(2)
|
126
|
+
assert_equal 3, a.foo(2)
|
127
|
+
end
|
128
|
+
|
129
|
+
should 'cache class methods for mixins' do
|
130
|
+
assert_equal 1, Baz.foo(1)
|
131
|
+
assert_equal 1, Baz.foo(1)
|
132
|
+
assert_equal 3, Baz.foo(2)
|
133
|
+
assert_equal 3, Baz.foo(2)
|
134
|
+
end
|
135
|
+
|
136
|
+
should 'invalidate cached method' do
|
137
|
+
a = Foo.new
|
138
|
+
|
139
|
+
assert_equal 1, a.foo(1)
|
140
|
+
assert_equal 3, a.foo(2)
|
141
|
+
|
142
|
+
a.invalidate_cached_method(:foo, 1)
|
143
|
+
|
144
|
+
assert_equal 4, a.foo(1)
|
145
|
+
assert_equal 3, a.foo(2)
|
146
|
+
end
|
147
|
+
|
148
|
+
should 'cache counters' do
|
149
|
+
b = Baz.new
|
150
|
+
|
151
|
+
assert_equal 100, b.foo_count(:bar)
|
152
|
+
b.increment_foo_count(:bar, :by => 42)
|
153
|
+
assert_equal 142, b.foo_count(:bar)
|
154
|
+
b.decrement_foo_count(:bar, :by => 99)
|
155
|
+
assert_equal 43, b.foo_count(:bar)
|
156
|
+
b.increment_foo_count(:bar)
|
157
|
+
assert_equal 44, b.foo_count(:bar)
|
158
|
+
|
159
|
+
assert_equal 100, b.foo_count(:baz)
|
160
|
+
b.increment_foo_count(:baz)
|
161
|
+
assert_equal 101, b.foo_count(:baz)
|
162
|
+
assert_equal 44, b.foo_count(:bar) # make sure :bar wasn't affected
|
163
|
+
|
164
|
+
assert_equal 0, Foo.zap
|
165
|
+
Foo.increment_zap(:by => 3)
|
166
|
+
assert_equal 3, Foo.zap
|
167
|
+
Foo.decrement_zap
|
168
|
+
assert_equal 2, Foo.zap
|
169
|
+
end
|
170
|
+
|
171
|
+
should 'use consistent local keys' do
|
172
|
+
a = Foo.new
|
173
|
+
o = Object.new
|
174
|
+
a_hash = a.hash
|
175
|
+
o_hash = o.hash
|
176
|
+
|
177
|
+
5.times do
|
178
|
+
key = a.send(:cached_method, :bar, [{'a' => 3, 'b' => [5,6], 'c' => o}, [1,nil,{:o => o}]]).key
|
179
|
+
assert_equal "m|:bar|Foo-#{a_hash}|{'a'=3,'b'=[5,6],'c'=Object-#{o_hash}}|[1,nil,{:o=Object-#{o_hash}}]", key
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
should 'use consistent remote keys' do
|
184
|
+
a = Foo.new
|
185
|
+
o = Object.new
|
186
|
+
a_hash = Marshal.dump(a).hash
|
187
|
+
o_hash = Marshal.dump(o).hash
|
188
|
+
|
189
|
+
5.times do
|
190
|
+
key = a.send(:cached_method, :baz, [{:a => 3, :b => [5,6], :c => o}, [false,true,{:o => o}]]).key
|
191
|
+
assert_equal "m|:baz|Foo-#{a_hash}|{:a=3,:b=[5,6],:c=Object-#{o_hash}}|[false,true,{:o=Object-#{o_hash}}]", key
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
should 'cache nil locally' do
|
196
|
+
a = Foo.new
|
197
|
+
a.zang
|
198
|
+
a.zang
|
199
|
+
assert_equal 2, a.z
|
200
|
+
end
|
201
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'shoulda'
|
4
|
+
require 'mocha'
|
5
|
+
require 'pp'
|
6
|
+
|
7
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib"
|
8
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + "/../../memcache/lib"
|
9
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + "/../../cache_version/lib"
|
10
|
+
require 'method_cache'
|
11
|
+
|
12
|
+
class Test::Unit::TestCase
|
13
|
+
def start_memcache(port)
|
14
|
+
system("memcached -p #{port} -U 0 -d -P /tmp/memcached_#{port}.pid")
|
15
|
+
sleep 1
|
16
|
+
File.read("/tmp/memcached_#{port}.pid")
|
17
|
+
end
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jashmenn-method_cache
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.7.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Justin Balthrop
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-17 00:00:00.000000000Z
|
13
|
+
dependencies: []
|
14
|
+
description: Simple memcache-based memoization library for Ruby
|
15
|
+
email: code@justinbalthrop.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files:
|
19
|
+
- LICENSE
|
20
|
+
- README.rdoc
|
21
|
+
files:
|
22
|
+
- LICENSE
|
23
|
+
- README.rdoc
|
24
|
+
- Rakefile
|
25
|
+
- VERSION.yml
|
26
|
+
- lib/method_cache.rb
|
27
|
+
- lib/method_cache/proxy.rb
|
28
|
+
- test/method_cache_remote_test.rb
|
29
|
+
- test/method_cache_test.rb
|
30
|
+
- test/test_helper.rb
|
31
|
+
homepage: http://github.com/ninjudd/method_cache
|
32
|
+
licenses: []
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options: []
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ! '>='
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ! '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubyforge_project:
|
51
|
+
rubygems_version: 1.8.6
|
52
|
+
signing_key:
|
53
|
+
specification_version: 3
|
54
|
+
summary: Simple memcache-based memoization library for Ruby
|
55
|
+
test_files: []
|