cache_value 0.3.0 → 0.4.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|