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 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