cache_value 0.3.0 → 0.4.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/VERSION +1 -1
- data/cache_value.gemspec +3 -3
- data/lib/cache_value/cache_machine.rb +75 -47
- data/lib/cache_value/util.rb +4 -0
- data/lib/cache_value.rb +2 -3
- data/test/cache_machine_test.rb +92 -84
- data/test/util_test.rb +4 -4
- metadata +12 -5
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.4
|
data/cache_value.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{cache_value}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.4.4"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Tobias Crawley"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-06-10}
|
13
13
|
s.email = %q{tcrawley@gmail.com}
|
14
14
|
s.extra_rdoc_files = [
|
15
15
|
"LICENSE",
|
@@ -35,7 +35,7 @@ Gem::Specification.new do |s|
|
|
35
35
|
s.homepage = %q{http://github.com/tobias/cache_value}
|
36
36
|
s.rdoc_options = ["--charset=UTF-8"]
|
37
37
|
s.require_paths = ["lib"]
|
38
|
-
s.rubygems_version = %q{1.3.
|
38
|
+
s.rubygems_version = %q{1.3.6}
|
39
39
|
s.summary = %q{Easy value caching}
|
40
40
|
s.test_files = [
|
41
41
|
"test/cache_machine_test.rb",
|
@@ -1,80 +1,108 @@
|
|
1
1
|
require 'active_support/core_ext/array'
|
2
2
|
require 'active_support/core_ext/class'
|
3
|
+
require 'active_support/core_ext/float'
|
3
4
|
require 'active_support/inflector'
|
4
5
|
require 'active_support/cache'
|
5
6
|
require 'cache_value/util'
|
7
|
+
require 'benchmark'
|
6
8
|
|
7
9
|
module CacheValue
|
8
10
|
class CacheMachine
|
9
|
-
|
10
|
-
|
11
|
+
include Util
|
12
|
+
|
11
13
|
class << self
|
12
14
|
|
13
15
|
def cache_store=(*store_option)
|
14
|
-
@cache_store = store_option ? ActiveSupport::Cache.lookup_store(store_option) : nil
|
15
|
-
end
|
16
|
-
|
17
|
-
def cache_store
|
18
|
-
@cache_store ||= ActiveSupport::Cache.lookup_store(:file_store, default_storage_dir)
|
16
|
+
@cache_store = store_option ? ActiveSupport::Cache.lookup_store(*store_option) : nil
|
19
17
|
end
|
20
18
|
|
21
|
-
def
|
22
|
-
|
23
|
-
raise ConfigurationException.new('Not running under rails. Set the cache_store type ' +
|
24
|
-
'and location manually using CacheValue::CacheMachine.cache_store=')
|
25
|
-
end
|
26
|
-
|
27
|
-
File.join(RAILS_ROOT, 'tmp', 'cache_value_caches')
|
19
|
+
def cache_store
|
20
|
+
@cache_store ||= ActiveSupport::Cache.lookup_store(:mem_cache_store)
|
28
21
|
end
|
29
22
|
|
30
23
|
def lookup(object, method, options, arguments = nil)
|
31
|
-
|
32
|
-
if !last_cached or !cached_value_is_still_valid?(value, last_cached, object, method, options)
|
33
|
-
value = call_and_store_value(object, method, arguments)
|
34
|
-
end
|
35
|
-
|
36
|
-
value
|
24
|
+
new(object, method, options, arguments).lookup
|
37
25
|
end
|
38
|
-
|
39
|
-
|
40
|
-
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_accessor :object, :cached_method, :options, :arguments
|
29
|
+
|
30
|
+
def initialize(object, method, options, arguments)
|
31
|
+
self.object = object
|
32
|
+
self.cached_method = method
|
33
|
+
self.options = options
|
34
|
+
self.arguments = arguments
|
35
|
+
end
|
36
|
+
|
37
|
+
def lookup
|
38
|
+
value = fetch_and_parse
|
39
|
+
value = call_and_store_value unless @fetched
|
40
|
+
value
|
41
|
+
end
|
42
|
+
|
43
|
+
def cache_key
|
44
|
+
if !@cache_key
|
45
|
+
options = process_options
|
46
|
+
if !options[:cache_key] and !object.respond_to?(:cache_key)
|
41
47
|
raise ConfigurationException.new("object of class #{object.class.name} does not respond to :cache_key")
|
42
48
|
end
|
43
|
-
|
44
|
-
|
49
|
+
|
50
|
+
cache_key = options[:cache_key] || object.cache_key
|
51
|
+
key = cache_key.gsub('/', '_') + "_#{cached_method}"
|
45
52
|
key << '_' + hex_digest(arguments) if arguments
|
46
|
-
key
|
53
|
+
#logger.debug "cache_value: cache_key = #{key}"
|
54
|
+
@cache_key = key
|
47
55
|
end
|
56
|
+
@cache_key
|
57
|
+
end
|
48
58
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
59
|
+
def fetch_and_parse
|
60
|
+
key = cache_key
|
61
|
+
data = nil
|
62
|
+
time = Benchmark.realtime do
|
63
|
+
data = self.class.cache_store.fetch(key)
|
64
|
+
end
|
65
|
+
if data
|
66
|
+
@fetched = true
|
67
|
+
logger.info "cache_value: retrieved #{key} (#{(time*1000).round(1)}ms)"
|
68
|
+
YAML::load(data)
|
69
|
+
else
|
70
|
+
@fetched = false
|
71
|
+
nil
|
56
72
|
end
|
73
|
+
end
|
57
74
|
|
58
|
-
|
59
|
-
|
75
|
+
def call_and_store_value
|
76
|
+
without_method = caching_method_names(cached_method).first
|
77
|
+
value = nil
|
78
|
+
time = Benchmark.realtime do
|
60
79
|
value = arguments ? object.send(without_method, *arguments) : object.send(without_method)
|
61
|
-
cache_store.write(cache_key(object, method, arguments), [value, Time.now].to_yaml)
|
62
|
-
|
63
|
-
value
|
64
80
|
end
|
65
81
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
82
|
+
ttl = process_options[:ttl]
|
83
|
+
self.class.cache_store.write(cache_key, value.to_yaml, :expires_in => ttl)
|
84
|
+
logger.info "cache_value: cached #{object.class.name}##{cached_method} for #{ttl}s (will save #{(time*1000).round(1)}ms)"
|
85
|
+
|
86
|
+
value
|
87
|
+
end
|
88
|
+
|
89
|
+
def process_options
|
90
|
+
opts = options
|
91
|
+
opts = object.send(:method, opts) if opts.is_a?(Symbol)
|
92
|
+
opts = opts.call(*([object, cached_method, arguments][0,opts.arity])) if opts.respond_to?(:arity)
|
93
|
+
|
94
|
+
if opts.respond_to?(:to_hash)
|
95
|
+
opts = opts.to_hash
|
96
|
+
else
|
97
|
+
opts = { :ttl => opts, :cache_key => nil }
|
73
98
|
end
|
74
99
|
|
100
|
+
raise ConfigurationException.new('Options must resolve to a hash with a :ttl') unless opts[:ttl]
|
101
|
+
|
102
|
+
opts
|
75
103
|
end
|
76
|
-
end
|
77
104
|
|
78
|
-
class ConfigurationException < StandardError
|
79
105
|
end
|
106
|
+
|
107
|
+
class ConfigurationException < StandardError; end
|
80
108
|
end
|
data/lib/cache_value/util.rb
CHANGED
data/lib/cache_value.rb
CHANGED
data/test/cache_machine_test.rb
CHANGED
@@ -2,62 +2,55 @@ require 'test_helper'
|
|
2
2
|
|
3
3
|
require 'cache_value/cache_machine'
|
4
4
|
|
5
|
+
require 'logger'
|
5
6
|
|
6
7
|
class CacheMachineTest < Test::Unit::TestCase
|
7
|
-
|
8
|
+
::RAILS_DEFAULT_LOGGER = Logger.new('/dev/null')
|
9
|
+
|
8
10
|
def setup
|
9
|
-
@cm = CacheValue::CacheMachine
|
10
11
|
@obj = mock
|
11
12
|
@method = :some_method
|
12
13
|
@value = 2
|
14
|
+
@arguments = [1,2]
|
15
|
+
@cm = CacheValue::CacheMachine.new(@obj, @method, 15, @arguments)
|
13
16
|
end
|
14
17
|
|
15
|
-
context '
|
18
|
+
context 'at the class level' do
|
16
19
|
should 'provide a cache store' do
|
17
|
-
::
|
18
|
-
@cm.cache_store.should_not == nil
|
19
|
-
end
|
20
|
-
|
21
|
-
context 'default storage dir' do
|
22
|
-
should 'raise exception if RAILS_ENV is not set' do
|
23
|
-
assert_raise CacheValue::ConfigurationException do
|
24
|
-
@cm.default_storage_dir
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
should 'point to the tmp in RAILS_ROOT if available' do
|
29
|
-
::RAILS_ROOT = '/tmp'
|
30
|
-
@cm.default_storage_dir.should == '/tmp/tmp/cache_value_caches'
|
31
|
-
end
|
32
|
-
|
33
|
-
|
34
|
-
end
|
35
|
-
|
36
|
-
teardown do
|
37
|
-
Object.send(:remove_const, :RAILS_ROOT) if defined?(RAILS_ROOT)
|
20
|
+
CacheValue::CacheMachine.cache_store.should_not == nil
|
38
21
|
end
|
39
22
|
|
23
|
+
should 'delegate the lookup call to an instance' do
|
24
|
+
CacheValue::CacheMachine.expects(:new).with(1,2,3,4).returns(@cm)
|
25
|
+
@cm.expects(:lookup)
|
26
|
+
CacheValue::CacheMachine.lookup(1,2,3,4)
|
27
|
+
end
|
40
28
|
end
|
41
29
|
|
42
30
|
|
43
31
|
context 'cache key' do
|
44
|
-
should 'raise exception if object does not respond_to cache_key' do
|
32
|
+
should 'raise exception if object does not respond_to cache_key and no cache key provided in the options' do
|
45
33
|
assert_raise CacheValue::ConfigurationException do
|
46
|
-
@cm.cache_key
|
34
|
+
@cm.cache_key
|
47
35
|
end
|
48
36
|
end
|
37
|
+
|
38
|
+
should 'use the cache_key if provided in options' do
|
39
|
+
@cm.options = { :cache_key => 'a_key', :ttl => 1 }
|
40
|
+
@cm.cache_key.should =~ /a_key/
|
41
|
+
end
|
49
42
|
|
50
43
|
context 'with an object that responds to cache_key' do
|
51
44
|
setup do
|
52
45
|
@obj.expects(:cache_key).returns('a/cache/key')
|
53
46
|
end
|
54
47
|
|
55
|
-
should 'include the method name
|
56
|
-
assert_match /
|
48
|
+
should 'include the method name' do
|
49
|
+
assert_match /_some_method/, @cm.cache_key
|
57
50
|
end
|
58
51
|
|
59
52
|
should 'not have any slashes' do
|
60
|
-
assert_no_match %r{/}, @cm.cache_key
|
53
|
+
assert_no_match %r{/}, @cm.cache_key
|
61
54
|
end
|
62
55
|
end
|
63
56
|
|
@@ -66,9 +59,11 @@ class CacheMachineTest < Test::Unit::TestCase
|
|
66
59
|
@obj.expects(:cache_key).returns('a/cache/key')
|
67
60
|
end
|
68
61
|
|
69
|
-
should 'should include the hash in the key' do
|
62
|
+
should 'should include the argument hash in the key' do
|
70
63
|
now = Time.now
|
71
|
-
|
64
|
+
args = [1, 2, now]
|
65
|
+
@cm.arguments = args
|
66
|
+
assert_match /_some_method_#{@cm.send(:hex_digest, args)}$/, @cm.cache_key
|
72
67
|
end
|
73
68
|
|
74
69
|
end
|
@@ -76,18 +71,18 @@ class CacheMachineTest < Test::Unit::TestCase
|
|
76
71
|
|
77
72
|
context 'fetch_and_parse' do
|
78
73
|
setup do
|
79
|
-
@cache_data =
|
74
|
+
@cache_data = 'data'
|
80
75
|
@cm.expects(:cache_key).returns('')
|
81
76
|
end
|
82
77
|
|
83
|
-
should 'return the data
|
84
|
-
@cm.cache_store.expects(:fetch).returns(@cache_data.to_yaml)
|
85
|
-
@cm.fetch_and_parse
|
78
|
+
should 'return the data when found' do
|
79
|
+
@cm.class.cache_store.expects(:fetch).returns(@cache_data.to_yaml)
|
80
|
+
@cm.fetch_and_parse.should == @cache_data
|
86
81
|
end
|
87
82
|
|
88
|
-
should 'return
|
89
|
-
@cm.cache_store.expects(:fetch).returns(nil)
|
90
|
-
@cm.fetch_and_parse
|
83
|
+
should 'return nil when data not found' do
|
84
|
+
@cm.class.cache_store.expects(:fetch).returns(nil)
|
85
|
+
@cm.fetch_and_parse.should == nil
|
91
86
|
end
|
92
87
|
end
|
93
88
|
|
@@ -95,45 +90,51 @@ class CacheMachineTest < Test::Unit::TestCase
|
|
95
90
|
setup do
|
96
91
|
@obj.expects(:some_method_without_value_caching).returns(2)
|
97
92
|
key = 'key'
|
98
|
-
@cm.expects(:cache_key).
|
93
|
+
@cm.expects(:cache_key).returns(key)
|
99
94
|
now = Time.now
|
100
95
|
Time.stubs(:now).returns(now)
|
101
|
-
@cm.cache_store.expects(:write).with(key,
|
96
|
+
@cm.class.cache_store.expects(:write).with(key, 2.to_yaml, { :expires_in => 15 })
|
102
97
|
end
|
103
98
|
|
104
99
|
should 'return the data' do
|
105
|
-
@cm.call_and_store_value
|
100
|
+
@cm.call_and_store_value.should == 2
|
106
101
|
end
|
107
102
|
|
108
103
|
end
|
104
|
+
|
105
|
+
context 'processing the options' do
|
109
106
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
should 'be valid if the value is younger than the age limit' do
|
114
|
-
@cm.cached_value_is_still_valid?(@value, Time.now - 30, @obj, @method, 35).should == true
|
115
|
-
end
|
116
|
-
|
117
|
-
should 'not be valid if the value is older than the age limit' do
|
118
|
-
@cm.cached_value_is_still_valid?(@value, Time.now - 30, @obj, @method, 5).should == false
|
119
|
-
end
|
107
|
+
should 'return a hash if given a hash' do
|
108
|
+
@cm.options = { :ttl => 15 }
|
109
|
+
@cm.process_options.should == { :ttl => 15 }
|
120
110
|
end
|
121
111
|
|
122
112
|
context 'checking with block' do
|
123
113
|
should 'pass the proper fields to the proc' do
|
114
|
+
now = Time.now
|
115
|
+
proc = lambda { |a,b|}
|
116
|
+
proc.expects(:call).with(@obj, @method).returns(1)
|
117
|
+
@cm.options = proc
|
118
|
+
|
119
|
+
@cm.process_options
|
120
|
+
end
|
121
|
+
|
122
|
+
should 'pass the arguments to the proc as well if the arity is high enough' do
|
124
123
|
now = Time.now
|
125
124
|
proc = lambda { |a,b,c|}
|
126
|
-
proc.expects(:call).with(
|
127
|
-
|
128
|
-
|
125
|
+
proc.expects(:call).with(@obj, @method, @arguments).returns(1)
|
126
|
+
@cm.options = proc
|
127
|
+
|
128
|
+
@cm.process_options
|
129
129
|
end
|
130
130
|
|
131
|
-
should 'only pass
|
131
|
+
should 'only pass the object if the proc only takes one arg' do
|
132
132
|
now = Time.now
|
133
133
|
proc = lambda { |a|}
|
134
|
-
proc.expects(:call).with(
|
135
|
-
|
136
|
-
|
134
|
+
proc.expects(:call).with(@obj).returns({ :ttl => 1})
|
135
|
+
@cm.options = proc
|
136
|
+
|
137
|
+
@cm.process_options
|
137
138
|
end
|
138
139
|
end
|
139
140
|
|
@@ -141,49 +142,56 @@ class CacheMachineTest < Test::Unit::TestCase
|
|
141
142
|
setup do
|
142
143
|
@object = Object.new
|
143
144
|
def @object.the_method(a,b)
|
144
|
-
|
145
|
+
{:ttl => 1}
|
145
146
|
end
|
146
147
|
|
147
148
|
end
|
148
149
|
|
149
150
|
should 'call the method for the symbol on the object' do
|
150
|
-
@cm.
|
151
|
+
@cm.options = :the_method
|
152
|
+
@cm.object = @object
|
153
|
+
@cm.process_options.should == { :ttl => 1 }
|
151
154
|
end
|
152
155
|
|
153
156
|
end
|
154
157
|
|
155
|
-
should 'raise
|
156
|
-
|
158
|
+
should 'raise exception if options do not resolve to a hash with :ttl' do
|
159
|
+
@cm.options = lambda { { }}
|
160
|
+
assert_raise CacheValue::ConfigurationException do
|
161
|
+
@cm.process_options
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
157
165
|
end
|
158
166
|
|
159
167
|
context 'cache lookup' do
|
160
|
-
should 'call for the value (and return it) if nothing was cached' do
|
161
|
-
@cm.expects(:fetch_and_parse).
|
162
|
-
@cm.expects(:call_and_store_value).
|
163
|
-
@cm.lookup
|
168
|
+
should 'call for the value (and return it) if nothing was cached' do
|
169
|
+
@cm.expects(:fetch_and_parse).returns(nil)
|
170
|
+
@cm.expects(:call_and_store_value).returns(@value)
|
171
|
+
@cm.lookup.should == @value
|
164
172
|
end
|
165
173
|
|
166
|
-
should '
|
167
|
-
|
168
|
-
@cm.
|
169
|
-
@cm.expects(:cached_value_is_still_valid?).with(@value, now, @obj, @method, 2).returns(false)
|
170
|
-
@cm.expects(:call_and_store_value).with(@obj, @method, nil).returns(@value)
|
171
|
-
@cm.lookup(@obj, @method, 2).should == @value
|
172
|
-
end
|
173
|
-
|
174
|
-
should 'not call and store if the cached value is valid' do
|
175
|
-
@cm.expects(:fetch_and_parse).with(@obj, @method, nil).returns([2, Time.now])
|
176
|
-
@cm.expects(:cached_value_is_still_valid?).returns(true)
|
174
|
+
should 'be able to cache a nil value' do
|
175
|
+
@cm.class.cache_store.expects(:fetch).returns(nil.to_yaml)
|
176
|
+
@cm.stubs(:cache_key).returns(nil)
|
177
177
|
@cm.expects(:call_and_store_value).never
|
178
|
-
@cm.lookup
|
178
|
+
@cm.lookup.should == nil
|
179
179
|
end
|
180
180
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
181
|
+
context 'with a cached value' do
|
182
|
+
setup do
|
183
|
+
@cm.class.cache_store.expects(:fetch).returns(1.to_yaml)
|
184
|
+
@cm.stubs(:cache_key).returns('asf')
|
185
|
+
end
|
186
|
+
|
187
|
+
should 'not try to cache the value' do
|
188
|
+
@cm.expects(:call_and_store_value).never
|
189
|
+
@cm.lookup
|
190
|
+
end
|
191
|
+
|
192
|
+
should 'actually return the cached value' do
|
193
|
+
@cm.lookup.should == 1
|
194
|
+
end
|
186
195
|
end
|
187
|
-
|
188
196
|
end
|
189
197
|
end
|
data/test/util_test.rb
CHANGED
@@ -6,10 +6,10 @@ require 'cache_value/util'
|
|
6
6
|
class UtilTest < Test::Unit::TestCase
|
7
7
|
include CacheValue::Util
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
context 'hex_digest' do
|
10
|
+
should 'return the same digest for identical hashes' do
|
11
|
+
hex_digest({ :ha => 'ha'}).should == hex_digest({ :ha => 'ha'})
|
12
|
+
end
|
11
13
|
end
|
12
14
|
|
13
|
-
should 'have some tests here'
|
14
|
-
|
15
15
|
end
|
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cache_value
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 4
|
8
|
+
- 4
|
9
|
+
version: 0.4.4
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Tobias Crawley
|
@@ -9,7 +14,7 @@ autorequire:
|
|
9
14
|
bindir: bin
|
10
15
|
cert_chain: []
|
11
16
|
|
12
|
-
date: 2010-
|
17
|
+
date: 2010-06-10 00:00:00 -04:00
|
13
18
|
default_executable:
|
14
19
|
dependencies: []
|
15
20
|
|
@@ -51,18 +56,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
51
56
|
requirements:
|
52
57
|
- - ">="
|
53
58
|
- !ruby/object:Gem::Version
|
59
|
+
segments:
|
60
|
+
- 0
|
54
61
|
version: "0"
|
55
|
-
version:
|
56
62
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
63
|
requirements:
|
58
64
|
- - ">="
|
59
65
|
- !ruby/object:Gem::Version
|
66
|
+
segments:
|
67
|
+
- 0
|
60
68
|
version: "0"
|
61
|
-
version:
|
62
69
|
requirements: []
|
63
70
|
|
64
71
|
rubyforge_project:
|
65
|
-
rubygems_version: 1.3.
|
72
|
+
rubygems_version: 1.3.6
|
66
73
|
signing_key:
|
67
74
|
specification_version: 3
|
68
75
|
summary: Easy value caching
|