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 CHANGED
@@ -1 +1 @@
1
- 0.3.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.3.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-01-07}
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.5}
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
- extend Util
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 default_storage_dir
22
- if !defined?(RAILS_ROOT)
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
- value, last_cached = fetch_and_parse(object, method, arguments)
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
- def cache_key(object, method, arguments = nil)
40
- if !object.respond_to?(:cache_key)
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
- key = object.cache_key.gsub('/', '_') + "_#{method}"
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
- def fetch_and_parse(object, method, arguments = nil)
50
- data = cache_store.fetch(cache_key(object, method, arguments))
51
- if data
52
- YAML::load(data)
53
- else
54
- [nil, nil]
55
- end
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
- def call_and_store_value(object, method, arguments = nil)
59
- without_method = caching_method_names(method).first
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
- def cached_value_is_still_valid?(value, cached_age, object, method, options)
67
- options = object.send(:method, options) if options.is_a?(Symbol)
68
- if options.respond_to?(:arity)
69
- options.call(*([cached_age, object, method][0,options.arity]))
70
- else
71
- cached_age > Time.now - options
72
- end
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
@@ -13,6 +13,10 @@ module CacheValue
13
13
  Digest::SHA1.hexdigest(stringify_value(values))
14
14
  end
15
15
 
16
+ def logger
17
+ ::RAILS_DEFAULT_LOGGER
18
+ end
19
+
16
20
  protected
17
21
  def stringify_value(value)
18
22
  if value.respond_to?(:to_str)
data/lib/cache_value.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require 'cache_value/cache_value'
2
2
 
3
- module CacheValue
4
- ActiveRecord::Base.send(:extend, CacheValue::ClassMethods) if defined?(ActiveRecord::Base)
5
- end
3
+ ActiveRecord::Base.send(:extend, CacheValue::ClassMethods) if defined?(ActiveRecord::Base)
4
+
@@ -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 'cache store' do
18
+ context 'at the class level' do
16
19
  should 'provide a cache store' do
17
- ::RAILS_ROOT = '/tmp'
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(@obj, @method)
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 at the end' do
56
- assert_match /_method$/, @cm.cache_key(@obj, :method)
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(@obj, :method)
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
- assert_match /_method_#{@cm.send(:hex_digest, [1, 2, now])}$/, @cm.cache_key(@obj, :method, [1, 2, now])
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 = ['data', Time.now]
74
+ @cache_data = 'data'
80
75
  @cm.expects(:cache_key).returns('')
81
76
  end
82
77
 
83
- should 'return the data and timestamp when found' do
84
- @cm.cache_store.expects(:fetch).returns(@cache_data.to_yaml)
85
- @cm.fetch_and_parse(@obj, @method).should == @cache_data
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 nils when data not found' do
89
- @cm.cache_store.expects(:fetch).returns(nil)
90
- @cm.fetch_and_parse(@obj, @method).should == [nil, nil]
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).with(@obj, :some_method, nil).returns(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, [2, Time.now].to_yaml)
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(@obj, :some_method).should == 2
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
- context 'validity of stored value' do
111
-
112
- context 'checking by age in seconds' do
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(now, @obj, @method)
127
-
128
- @cm.cached_value_is_still_valid?(@value, now, @obj, @method, proc)
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 time if the proc only takes two args' do
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(now)
135
-
136
- @cm.cached_value_is_still_valid?(@value, now, @obj, @method, proc)
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
- 'blah'
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.cached_value_is_still_valid?(@value, Time.now, @object, @method, :the_method).should == 'blah'
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 exceptions if options is incorrect'
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).with(@obj, @method, nil).returns(nil, nil)
162
- @cm.expects(:call_and_store_value).with(@obj, @method, nil).returns(@value)
163
- @cm.lookup(@obj, @method, nil).should == @value
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 'call for the value (and return it) if the value is no longer valid' do
167
- now = Time.now
168
- @cm.expects(:fetch_and_parse).with(@obj, @method, nil).returns([2, now])
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(@obj, @method, 2).should == @value
178
+ @cm.lookup.should == nil
179
179
  end
180
180
 
181
- should 'be able to cache a nil value' do
182
- @cm.expects(:fetch_and_parse).with(@obj, @method, nil).returns([nil, Time.now])
183
- @cm.expects(:cached_value_is_still_valid?).returns(true)
184
- @cm.expects(:call_and_store_value).never
185
- @cm.lookup(@obj, @method, 2).should == nil
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
- should 'be true' do
10
- assert true
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
- version: 0.3.0
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-01-07 00:00:00 -05:00
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.5
72
+ rubygems_version: 1.3.6
66
73
  signing_key:
67
74
  specification_version: 3
68
75
  summary: Easy value caching