ninjudd-method_cache 0.6.2

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/README.rdoc ADDED
@@ -0,0 +1,42 @@
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
+ sudo gem install ninjudd-memcache -s http://gems.github.com
37
+ sudo gem install ninjudd-cache_version -s http://gems.github.com
38
+ sudo gem install ninjudd-method_cache -s http://gems.github.com
39
+
40
+ == License:
41
+
42
+ Copyright (c) 2009 Justin Balthrop, Geni.com; Published under The MIT License, see LICENSE
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :patch: 2
3
+ :major: 0
4
+ :minor: 6
@@ -0,0 +1,172 @@
1
+ module MethodCache
2
+ class Proxy
3
+ attr_reader :method_name, :opts, :args, :target
4
+
5
+ def initialize(method_name, opts)
6
+ @method_name = method_name
7
+ @opts = opts
8
+ end
9
+
10
+ def bind(target, args)
11
+ self.clone.bind!(target, args)
12
+ end
13
+
14
+ def bind!(target, args)
15
+ @target = target
16
+ @args = args
17
+ @key = nil
18
+ self
19
+ end
20
+
21
+ def invalidate
22
+ if block_given?
23
+ # Only invalidate if the block returns true.
24
+ value = cache[key]
25
+ return if value and not yield(value)
26
+ end
27
+ cache.delete(key)
28
+ end
29
+
30
+ def context
31
+ opts[:context]
32
+ end
33
+
34
+ def version
35
+ dynamic_opt(:version)
36
+ end
37
+
38
+ def cached?
39
+ not cache[key].nil?
40
+ end
41
+
42
+ def update
43
+ value = block_given? ? yield(cache[key]) : target.send(method_name_without_caching, *args)
44
+ write_to_cache(key, value)
45
+ value
46
+ end
47
+
48
+ def value
49
+ value = cache[key]
50
+ value = nil unless valid?(:load, value)
51
+
52
+ if value.nil?
53
+ value = target.send(method_name_without_caching, *args)
54
+ write_to_cache(key, value) if valid?(:save, value)
55
+ end
56
+
57
+ value = nil if value == NULL
58
+ if clone? and value
59
+ value.clone
60
+ else
61
+ value
62
+ end
63
+ end
64
+
65
+ NULL = 'NULL'
66
+ def method_with_caching
67
+ proxy = self # Need access to the proxy in the closure.
68
+
69
+ lambda do |*args|
70
+ proxy.bind(self, args).value
71
+ end
72
+ end
73
+
74
+ def method_name_without_caching
75
+ @method_name_without_caching ||= begin
76
+ base_name, punctuation = method_name.to_s.sub(/([?!=])$/, ''), $1
77
+ "#{base_name}_without_caching#{punctuation}"
78
+ end
79
+ end
80
+
81
+ def cache
82
+ if @cache.nil?
83
+ @cache = opts[:cache] || MethodCache.default_cache
84
+ @cache = MemCache.pool[@cache] if @cache.kind_of?(Symbol)
85
+ end
86
+ @cache
87
+ end
88
+
89
+ def local?
90
+ cache.kind_of?(Hash)
91
+ end
92
+
93
+ def clone?
94
+ !!opts[:clone]
95
+ end
96
+
97
+ def key
98
+ if @key.nil?
99
+ arg_string = ([method_name, target] + args).collect do |arg|
100
+ object_key(arg)
101
+ end.join('|')
102
+ @key = ['m', version, arg_string].compact.join('|')
103
+ end
104
+ @key
105
+ end
106
+
107
+ private
108
+
109
+ def expiry(value)
110
+ dynamic_opt(:expiry, value).to_i
111
+ end
112
+
113
+ def valid?(type, value)
114
+ name = "#{type}_validation".to_sym
115
+ return true unless opts[name]
116
+ return unless value
117
+
118
+ dynamic_opt(name, value)
119
+ end
120
+
121
+ def dynamic_opt(name, value = nil)
122
+ if opts[name].kind_of?(Proc)
123
+ proc = opts[name].bind(target)
124
+ case proc.arity
125
+ when 0: proc.call()
126
+ when 1: proc.call(value)
127
+ else
128
+ proc.call(value, *args)
129
+ end
130
+ else
131
+ opts[name]
132
+ end
133
+ end
134
+
135
+ def write_to_cache(key, value)
136
+ if cache.kind_of?(Hash)
137
+ raise 'expiry not permitted when cache is a Hash' if opts[:expiry]
138
+ cache[key] = value
139
+ else
140
+ value = value.nil? ? NULL : value
141
+ cache.set(key, value, expiry(value))
142
+ end
143
+ end
144
+
145
+ def object_key(arg)
146
+ return "#{class_key(arg.class)}-#{arg.string_hash}" if arg.respond_to?(:string_hash)
147
+
148
+ case arg
149
+ when NilClass : 'nil'
150
+ when TrueClass : 'true'
151
+ when FalseClass : 'false'
152
+ when Numeric : arg.to_s
153
+ when Symbol : ":#{arg}"
154
+ when String : "'#{arg}'"
155
+ when Class, Module : class_key(arg)
156
+ when Hash
157
+ '{' + arg.collect {|key, value| "#{object_key(key)}=#{object_key(value)}"}.sort.join(',') + '}'
158
+ when Array
159
+ '[' + arg.collect {|item| object_key(item)}.join(',') + ']'
160
+ when defined?(ActiveRecord::Base) && ActiveRecord::Base
161
+ "#{class_key(arg.class)}-#{arg.id}"
162
+ else
163
+ hash = local? ? arg.hash : Marshal.dump(arg).hash
164
+ "#{class_key(arg.class)}-#{hash}"
165
+ end
166
+ end
167
+
168
+ def class_key(klass)
169
+ klass.respond_to?(:version) ? "#{klass.name}_#{klass.version(context)}" : klass.name
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,162 @@
1
+ require 'memcache'
2
+
3
+ $:.unshift(File.dirname(__FILE__))
4
+ require 'method_cache/proxy'
5
+
6
+ module MethodCache
7
+ def cache_method(method_name, opts = {})
8
+ method_name = method_name.to_sym
9
+ proxy = opts.kind_of?(Proxy) ? opts : Proxy.new(method_name, opts)
10
+
11
+ if self.class == Class
12
+ return if instance_methods.include?(proxy.method_name_without_caching)
13
+
14
+ if cached_instance_methods.empty?
15
+ include(InvalidationMethods)
16
+ extend(MethodAdded)
17
+ end
18
+
19
+ cached_instance_methods[method_name] = nil
20
+ begin
21
+ # Replace instance method.
22
+ alias_method proxy.method_name_without_caching, method_name
23
+ define_method method_name, proxy.method_with_caching
24
+ rescue NameError => e
25
+ # The method has not been defined yet. We will alias it in method_added.
26
+ # pp e, e.backtrace
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
+ begin
51
+ # Replace class method.
52
+ (class << self; self; end).module_eval do
53
+ alias_method proxy.method_name_without_caching, method_name
54
+ define_method method_name, proxy.method_with_caching
55
+ end
56
+ rescue NameError => e
57
+ # The method has not been defined yet. We will alias it in singleton_method_added.
58
+ # pp e, e.backtrace
59
+ end
60
+ cached_class_methods[method_name] = proxy
61
+ end
62
+
63
+ def self.default_cache
64
+ @default_cache ||= {}
65
+ end
66
+
67
+ def cached_instance_methods(method_name = nil)
68
+ if method_name
69
+ method_name = method_name.to_sym
70
+ ancestors.each do |klass|
71
+ next unless klass.kind_of?(MethodCache)
72
+ proxy = klass.cached_instance_methods[method_name]
73
+ return proxy if proxy
74
+ end
75
+ nil
76
+ else
77
+ @cached_instance_methods ||= {}
78
+ end
79
+ end
80
+
81
+ def cached_class_methods(method_name = nil)
82
+ if method_name
83
+ method_name = method_name.to_sym
84
+ ancestors.each do |klass|
85
+ next unless klass.kind_of?(MethodCache)
86
+ proxy = klass.cached_class_methods[method_name]
87
+ return proxy if proxy
88
+ end
89
+ nil
90
+ else
91
+ @cached_class_methods ||= {}
92
+ end
93
+ end
94
+
95
+ def cached_module_methods(method_name = nil)
96
+ if method_name
97
+ cached_module_methods[method_name.to_sym]
98
+ else
99
+ @cached_module_methods ||= {}
100
+ end
101
+ end
102
+
103
+ module InvalidationMethods
104
+ def invalidate_cached_method(method_name, *args, &block)
105
+ cached_method(method_name, args).invalidate(&block)
106
+ end
107
+
108
+ def method_value_cached?(method_name, *args)
109
+ cached_method(method_name, args).cached?
110
+ end
111
+
112
+ def update_cached_method(method_name, *args, &block)
113
+ cached_method(method_name, args).update(&block)
114
+ end
115
+
116
+ private
117
+
118
+ def cached_method(method_name, args)
119
+ if self.kind_of?(Class) or self.kind_of?(Module)
120
+ proxy = cached_class_methods(method_name)
121
+ else
122
+ proxy = self.class.send(:cached_instance_methods, method_name)
123
+ end
124
+ raise "method '#{method_name}' not cached" unless proxy
125
+ proxy.bind(self, args)
126
+ end
127
+ end
128
+
129
+ module MethodAdded
130
+ def method_added(method_name)
131
+ if proxy = cached_instance_methods(method_name)
132
+ cache_method(method_name, proxy)
133
+ end
134
+ super
135
+ end
136
+ end
137
+
138
+ module SingletonMethodAdded
139
+ def singleton_method_added(method_name)
140
+ if proxy = cached_class_methods(method_name)
141
+ cache_class_method(method_name, proxy)
142
+ end
143
+ super
144
+ end
145
+ end
146
+
147
+ module ModuleAdded
148
+ def extended(mod)
149
+ mod.extend(MethodCache)
150
+ cached_module_methods.each do |method_name, proxy|
151
+ mod.cache_class_method(method_name, proxy)
152
+ end
153
+ end
154
+
155
+ def included(mod)
156
+ mod.extend(MethodCache)
157
+ cached_module_methods.each do |method_name, proxy|
158
+ mod.cache_method(method_name, proxy)
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,114 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class Foo
4
+ extend MethodCache
5
+
6
+ def foo(i)
7
+ @i ||= 0
8
+ @i += i
9
+ end
10
+ cache_method :foo
11
+
12
+ cache_method :bar
13
+ def bar
14
+ @i ||= 0
15
+ @i += 1
16
+ end
17
+
18
+ @@i = 0
19
+ def baz(i)
20
+ @@i += i
21
+ end
22
+ cache_method :baz, :cache => :remote
23
+ end
24
+
25
+ module Bar
26
+ extend MethodCache
27
+
28
+ cache_method :foo
29
+ def foo(i)
30
+ @i ||= 0
31
+ @i += i
32
+ end
33
+ end
34
+
35
+ class Baz
36
+ include Bar
37
+ end
38
+
39
+ class TestMethodCache < Test::Unit::TestCase
40
+ should 'cache methods locally' do
41
+ a = Foo.new
42
+ f1 = a.foo(1)
43
+ f2 = a.foo(2)
44
+
45
+ assert_equal 1, f1
46
+ assert_equal 3, f2
47
+
48
+ assert f1 == a.foo(1)
49
+ assert f1 != f2
50
+ assert f2 == a.foo(2)
51
+
52
+ b = a.bar
53
+ assert b == a.bar
54
+ assert b == a.bar
55
+ end
56
+
57
+ should 'cache methods remotely' do
58
+ a = Foo.new
59
+ b1 = a.baz(1)
60
+ b2 = a.baz(2)
61
+
62
+ assert_equal 1, b1
63
+ assert_equal 3, b2
64
+
65
+ assert b1 == a.baz(1)
66
+ assert b1 != b2
67
+ assert b2 == a.baz(2)
68
+ end
69
+
70
+ should 'cache methods for mixins' do
71
+ a = Baz.new
72
+
73
+ assert_equal 1, a.foo(1)
74
+ assert_equal 1, a.foo(1)
75
+ assert_equal 3, a.foo(2)
76
+ assert_equal 3, a.foo(2)
77
+ end
78
+
79
+ should 'invalidate cached method' do
80
+ a = Foo.new
81
+
82
+ assert_equal 1, a.foo(1)
83
+ assert_equal 3, a.foo(2)
84
+
85
+ a.invalidate_cached_method(:foo, 1)
86
+
87
+ assert_equal 4, a.foo(1)
88
+ assert_equal 3, a.foo(2)
89
+ end
90
+
91
+ should 'use consistent local keys' do
92
+ a = Foo.new
93
+ o = Object.new
94
+ a_hash = a.hash
95
+ o_hash = o.hash
96
+
97
+ 5.times do
98
+ key = a.send(:cached_method, :bar, [{'a' => 3, 'b' => [5,6], 'c' => o}, [1,nil,{:o => o}]]).key
99
+ assert_equal "m|:bar|Foo-#{a_hash}|{'a'=3,'b'=[5,6],'c'=Object-#{o_hash}}|[1,nil,{:o=Object-#{o_hash}}]", key
100
+ end
101
+ end
102
+
103
+ should 'use consistent remote keys' do
104
+ a = Foo.new
105
+ o = Object.new
106
+ a_hash = Marshal.dump(a).hash
107
+ o_hash = Marshal.dump(o).hash
108
+
109
+ 5.times do
110
+ key = a.send(:cached_method, :baz, [{:a => 3, :b => [5,6], :c => o}, [false,true,{:o => o}]]).key
111
+ assert_equal "m|:baz|Foo-#{a_hash}|{:a=3,:b=[5,6],:c=Object-#{o_hash}}|[false,true,{:o=Object-#{o_hash}}]", key
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,13 @@
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
+ end
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ninjudd-method_cache
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.6.2
5
+ platform: ruby
6
+ authors:
7
+ - Justin Balthrop
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-07-29 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Simple memcache-based memoization library for Ruby
17
+ email: code@justinbalthrop.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - README.rdoc
26
+ - VERSION.yml
27
+ - lib/method_cache
28
+ - lib/method_cache/proxy.rb
29
+ - lib/method_cache.rb
30
+ - test/method_cache_test.rb
31
+ - test/test_helper.rb
32
+ has_rdoc: true
33
+ homepage: http://github.com/ninjudd/method_cache
34
+ licenses:
35
+ post_install_message:
36
+ rdoc_options:
37
+ - --inline-source
38
+ - --charset=UTF-8
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ version:
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ requirements: []
54
+
55
+ rubyforge_project:
56
+ rubygems_version: 1.3.5
57
+ signing_key:
58
+ specification_version: 2
59
+ summary: Simple memcache-based memoization library for Ruby
60
+ test_files: []
61
+