garner 0.4.5 → 0.5.0
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.
- checksums.yaml +7 -0
- data/.gitignore +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +35 -0
- data/.travis.yml +13 -0
- data/CHANGELOG.md +130 -0
- data/CONTRIBUTING.md +118 -0
- data/Gemfile +3 -0
- data/README.md +1 -0
- data/Rakefile +39 -0
- data/UPGRADING.md +118 -0
- data/garner.gemspec +44 -0
- data/lib/garner.rb +21 -21
- data/lib/garner/cache.rb +13 -6
- data/lib/garner/cache/binding.rb +6 -14
- data/lib/garner/cache/context.rb +11 -12
- data/lib/garner/cache/identity.rb +1 -1
- data/lib/garner/config.rb +12 -7
- data/lib/garner/mixins/active_record.rb +3 -3
- data/lib/garner/mixins/active_record/base.rb +2 -2
- data/lib/garner/mixins/mongoid.rb +4 -4
- data/lib/garner/mixins/mongoid/document.rb +8 -12
- data/lib/garner/mixins/mongoid/identity.rb +5 -6
- data/lib/garner/mixins/rack.rb +1 -2
- data/lib/garner/strategies/binding/invalidation/base.rb +2 -4
- data/lib/garner/strategies/binding/invalidation/binding_index.rb +1 -3
- data/lib/garner/strategies/binding/invalidation/touch.rb +0 -2
- data/lib/garner/strategies/binding/key/base.rb +1 -3
- data/lib/garner/strategies/binding/key/binding_index.rb +3 -4
- data/lib/garner/strategies/binding/key/cache_key.rb +0 -2
- data/lib/garner/strategies/binding/key/safe_cache_key.rb +2 -3
- data/lib/garner/strategies/context/key/base.rb +1 -3
- data/lib/garner/strategies/context/key/caller.rb +9 -12
- data/lib/garner/strategies/context/key/jsonp.rb +3 -6
- data/lib/garner/strategies/context/key/request_get.rb +2 -4
- data/lib/garner/strategies/context/key/request_path.rb +1 -3
- data/lib/garner/strategies/context/key/request_post.rb +2 -4
- data/lib/garner/version.rb +1 -1
- data/spec/garner/cache/context_spec.rb +38 -0
- data/spec/garner/cache/identity_spec.rb +68 -0
- data/spec/garner/cache_spec.rb +49 -0
- data/spec/garner/config_spec.rb +17 -0
- data/spec/garner/mixins/mongoid/document_spec.rb +80 -0
- data/spec/garner/mixins/mongoid/identity_spec.rb +140 -0
- data/spec/garner/mixins/rack_spec.rb +48 -0
- data/spec/garner/strategies/binding/invalidation/binding_index_spec.rb +14 -0
- data/spec/garner/strategies/binding/invalidation/touch_spec.rb +23 -0
- data/spec/garner/strategies/binding/key/binding_index_spec.rb +245 -0
- data/spec/garner/strategies/binding/key/cache_key_spec.rb +29 -0
- data/spec/garner/strategies/binding/key/safe_cache_key_spec.rb +61 -0
- data/spec/garner/strategies/context/key/caller_spec.rb +106 -0
- data/spec/garner/strategies/context/key/jsonp_spec.rb +22 -0
- data/spec/garner/strategies/context/key/request_get_spec.rb +33 -0
- data/spec/garner/strategies/context/key/request_path_spec.rb +28 -0
- data/spec/garner/strategies/context/key/request_post_spec.rb +34 -0
- data/spec/garner/version_spec.rb +11 -0
- data/spec/integration/active_record_spec.rb +43 -0
- data/spec/integration/grape_spec.rb +33 -0
- data/spec/integration/mongoid_spec.rb +355 -0
- data/spec/integration/rack_spec.rb +77 -0
- data/spec/integration/sinatra_spec.rb +29 -0
- data/spec/performance/strategy_benchmark.rb +59 -0
- data/spec/performance/support/benchmark_context.rb +31 -0
- data/spec/performance/support/benchmark_context_wrapper.rb +67 -0
- data/spec/shared/binding_invalidation_strategy.rb +17 -0
- data/spec/shared/binding_key_strategy.rb +35 -0
- data/spec/shared/conditional_get.rb +48 -0
- data/spec/shared/context_key_strategy.rb +24 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/spec_support.rb +5 -0
- data/spec/support/active_record.rb +36 -0
- data/spec/support/cache.rb +15 -0
- data/spec/support/garner.rb +5 -0
- data/spec/support/mongoid.rb +71 -0
- metadata +155 -157
@@ -3,15 +3,13 @@ module Garner
|
|
3
3
|
module Binding
|
4
4
|
module Key
|
5
5
|
class Base
|
6
|
-
|
7
6
|
# Compute a cache key from an object binding.
|
8
7
|
#
|
9
8
|
# @param binding [Object] The object from which to compute a key.
|
10
9
|
# @return [String] A cache key string.
|
11
|
-
def self.apply(
|
10
|
+
def self.apply(_binding)
|
12
11
|
nil
|
13
12
|
end
|
14
|
-
|
15
13
|
end
|
16
14
|
end
|
17
15
|
end
|
@@ -87,7 +87,6 @@ module Garner
|
|
87
87
|
(binding.is_a?(Class) && binding.include?(Mongoid::Document))
|
88
88
|
end
|
89
89
|
|
90
|
-
private
|
91
90
|
def self.index_key_for(binding)
|
92
91
|
if binding.respond_to?(:identity_string)
|
93
92
|
binding_key = binding.identity_string
|
@@ -96,12 +95,12 @@ module Garner
|
|
96
95
|
end
|
97
96
|
|
98
97
|
{
|
99
|
-
:
|
100
|
-
:
|
98
|
+
strategy: self,
|
99
|
+
proxied_binding: binding_key
|
101
100
|
}
|
102
101
|
end
|
103
102
|
|
104
|
-
def self.new_cache_key_for(
|
103
|
+
def self.new_cache_key_for(_binding)
|
105
104
|
SecureRandom.hex(RANDOM_KEY_LENGTH)
|
106
105
|
end
|
107
106
|
end
|
@@ -3,7 +3,6 @@ module Garner
|
|
3
3
|
module Binding
|
4
4
|
module Key
|
5
5
|
class CacheKey < Base
|
6
|
-
|
7
6
|
# Compute a cache key from an object binding.
|
8
7
|
#
|
9
8
|
# @param binding [Object] The object from which to compute a key.
|
@@ -11,7 +10,6 @@ module Garner
|
|
11
10
|
def self.apply(binding)
|
12
11
|
binding.cache_key if binding.respond_to?(:cache_key)
|
13
12
|
end
|
14
|
-
|
15
13
|
end
|
16
14
|
end
|
17
15
|
end
|
@@ -3,7 +3,7 @@ module Garner
|
|
3
3
|
module Binding
|
4
4
|
module Key
|
5
5
|
class SafeCacheKey < Base
|
6
|
-
VALID_FORMAT =
|
6
|
+
VALID_FORMAT = %r{^(?<model>[^\/]+)\/(?<id>.+)-(?<timestamp>[0-9]{14,})$}
|
7
7
|
|
8
8
|
# Compute a cache key from an object binding. Only return a key if
|
9
9
|
# :cache_key and :updated_at are both defined and present on the
|
@@ -24,10 +24,9 @@ module Garner
|
|
24
24
|
return unless binding.cache_key =~ VALID_FORMAT
|
25
25
|
|
26
26
|
decimal_portion = binding.updated_at.utc.to_f % 1
|
27
|
-
decimal_string =
|
27
|
+
decimal_string = format('%.10f', decimal_portion).gsub(/^0/, '')
|
28
28
|
"#{binding.cache_key}#{decimal_string}"
|
29
29
|
end
|
30
|
-
|
31
30
|
end
|
32
31
|
end
|
33
32
|
end
|
@@ -3,17 +3,15 @@ module Garner
|
|
3
3
|
module Context
|
4
4
|
module Key
|
5
5
|
class Base
|
6
|
-
|
7
6
|
# Compute a hash of key-value pairs from a given ruby context,
|
8
7
|
# and apply it to a cache identity.
|
9
8
|
#
|
10
9
|
# @param identity [Garner::Cache::Identity] The cache identity.
|
11
10
|
# @param ruby_context [Object] An optional Ruby context.
|
12
11
|
# @return [Garner::Cache::Identity] The modified identity.
|
13
|
-
def self.apply(identity,
|
12
|
+
def self.apply(identity, _ruby_context = nil)
|
14
13
|
identity
|
15
14
|
end
|
16
|
-
|
17
15
|
end
|
18
16
|
end
|
19
17
|
end
|
@@ -3,7 +3,6 @@ module Garner
|
|
3
3
|
module Context
|
4
4
|
module Key
|
5
5
|
class Caller < Base
|
6
|
-
|
7
6
|
def self.field
|
8
7
|
:caller
|
9
8
|
end
|
@@ -21,12 +20,12 @@ module Garner
|
|
21
20
|
::Rails.root.realpath.to_s
|
22
21
|
else
|
23
22
|
# Try to use the nearest ancestor directory containing a Gemfile.
|
24
|
-
requiring_caller = send(:caller).
|
25
|
-
!line.include?(File.join(
|
23
|
+
requiring_caller = send(:caller).find do |line|
|
24
|
+
!line.include?(File.join('lib', 'garner'))
|
26
25
|
end
|
27
26
|
return nil unless requiring_caller
|
28
27
|
|
29
|
-
requiring_file = requiring_caller.split(
|
28
|
+
requiring_file = requiring_caller.split(':')[0]
|
30
29
|
gemfile_root(File.dirname(requiring_file))
|
31
30
|
end
|
32
31
|
end
|
@@ -43,13 +42,13 @@ module Garner
|
|
43
42
|
ruby_context.send(:caller).compact.each do |line|
|
44
43
|
parts = line.match(/(?<filename>[^:]+)\:(?<lineno>[^:]+)/)
|
45
44
|
file = (Pathname.new(parts[:filename]).realpath.to_s rescue nil)
|
46
|
-
next if file.nil? || file ==
|
47
|
-
next if file.include?(File.join(
|
45
|
+
next if file.nil? || file == ''
|
46
|
+
next if file.include?(File.join('lib', 'garner'))
|
48
47
|
|
49
48
|
if (root = Garner.config.caller_root)
|
50
49
|
root += File::SEPARATOR unless root[-1] == File::SEPARATOR
|
51
50
|
next unless file =~ /^#{root}/
|
52
|
-
value = "#{file.gsub(root ||
|
51
|
+
value = "#{file.gsub(root || '', '')}:#{parts[:lineno]}"
|
53
52
|
else
|
54
53
|
value = "#{file}:#{parts[:lineno]}"
|
55
54
|
end
|
@@ -58,24 +57,22 @@ module Garner
|
|
58
57
|
end
|
59
58
|
end
|
60
59
|
|
61
|
-
value ? identity.key(field => value) :
|
60
|
+
value ? identity.key(field => value) : super
|
62
61
|
end
|
63
62
|
|
64
|
-
private
|
65
63
|
def self.gemfile_root(path)
|
66
64
|
path = Pathname.new(path).realpath.to_s
|
67
|
-
newpath = Pathname.new(File.join(path,
|
65
|
+
newpath = Pathname.new(File.join(path, '..')).realpath.to_s
|
68
66
|
if newpath == path
|
69
67
|
# We've reached the filesystem root; return
|
70
68
|
return nil
|
71
|
-
elsif File.exist?(File.join(newpath,
|
69
|
+
elsif File.exist?(File.join(newpath, 'Gemfile'))
|
72
70
|
# We've struck Gemfile gold; return current path
|
73
71
|
return newpath
|
74
72
|
else
|
75
73
|
return gemfile_root(newpath)
|
76
74
|
end
|
77
75
|
end
|
78
|
-
|
79
76
|
end
|
80
77
|
end
|
81
78
|
end
|
@@ -3,7 +3,6 @@ module Garner
|
|
3
3
|
module Context
|
4
4
|
module Key
|
5
5
|
class Jsonp < Base
|
6
|
-
|
7
6
|
def self.field
|
8
7
|
:request_params
|
9
8
|
end
|
@@ -15,14 +14,12 @@ module Garner
|
|
15
14
|
# @return [Garner::Cache::Identity] The modified identity.
|
16
15
|
def self.apply(identity, ruby_context = nil)
|
17
16
|
key_hash = identity.key_hash
|
18
|
-
return
|
19
|
-
|
17
|
+
return super unless key_hash[field]
|
20
18
|
|
21
|
-
key_hash[field].delete(
|
22
|
-
key_hash[field].delete(
|
19
|
+
key_hash[field].delete('callback')
|
20
|
+
key_hash[field].delete('_')
|
23
21
|
identity
|
24
22
|
end
|
25
|
-
|
26
23
|
end
|
27
24
|
end
|
28
25
|
end
|
@@ -3,7 +3,6 @@ module Garner
|
|
3
3
|
module Context
|
4
4
|
module Key
|
5
5
|
class RequestGet < Base
|
6
|
-
|
7
6
|
def self.field
|
8
7
|
:request_params
|
9
8
|
end
|
@@ -14,15 +13,14 @@ module Garner
|
|
14
13
|
# @param ruby_context [Object] An optional Ruby context.
|
15
14
|
# @return [Garner::Cache::Identity] The modified identity.
|
16
15
|
def self.apply(identity, ruby_context = nil)
|
17
|
-
return
|
16
|
+
return super unless ruby_context.respond_to?(:request)
|
18
17
|
|
19
18
|
request = ruby_context.request
|
20
|
-
if request &&
|
19
|
+
if request && %w(GET HEAD).include?(request.request_method)
|
21
20
|
identity.key(field => request.GET.dup)
|
22
21
|
end
|
23
22
|
identity
|
24
23
|
end
|
25
|
-
|
26
24
|
end
|
27
25
|
end
|
28
26
|
end
|
@@ -3,7 +3,6 @@ module Garner
|
|
3
3
|
module Context
|
4
4
|
module Key
|
5
5
|
class RequestPath < Base
|
6
|
-
|
7
6
|
def self.field
|
8
7
|
:request_path
|
9
8
|
end
|
@@ -14,13 +13,12 @@ module Garner
|
|
14
13
|
# @param ruby_context [Object] An optional Ruby context.
|
15
14
|
# @return [Garner::Cache::Identity] The modified identity.
|
16
15
|
def self.apply(identity, ruby_context = nil)
|
17
|
-
return
|
16
|
+
return super unless ruby_context.respond_to?(:request)
|
18
17
|
|
19
18
|
request = ruby_context.request
|
20
19
|
identity.key(field => request.path) if request.respond_to?(:path)
|
21
20
|
identity
|
22
21
|
end
|
23
|
-
|
24
22
|
end
|
25
23
|
end
|
26
24
|
end
|
@@ -3,7 +3,6 @@ module Garner
|
|
3
3
|
module Context
|
4
4
|
module Key
|
5
5
|
class RequestPost < Base
|
6
|
-
|
7
6
|
def self.field
|
8
7
|
:request_params
|
9
8
|
end
|
@@ -14,15 +13,14 @@ module Garner
|
|
14
13
|
# @param ruby_context [Object] An optional Ruby context.
|
15
14
|
# @return [Garner::Cache::Identity] The modified identity.
|
16
15
|
def self.apply(identity, ruby_context = nil)
|
17
|
-
return
|
16
|
+
return super unless ruby_context.respond_to?(:request)
|
18
17
|
|
19
18
|
request = ruby_context.request
|
20
|
-
if request.request_method ==
|
19
|
+
if request.request_method == 'POST'
|
21
20
|
identity = identity.key(field => request.POST.dup)
|
22
21
|
end
|
23
22
|
identity
|
24
23
|
end
|
25
|
-
|
26
24
|
end
|
27
25
|
end
|
28
26
|
end
|
data/lib/garner/version.rb
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Garner::Cache::Context do
|
4
|
+
|
5
|
+
describe 'garner' do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
class TestContext
|
9
|
+
include Garner::Cache::Context
|
10
|
+
end
|
11
|
+
@test_context = TestContext.new
|
12
|
+
end
|
13
|
+
|
14
|
+
subject do
|
15
|
+
-> { @test_context.garner }
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'returns a Garner::Cache::Identity' do
|
19
|
+
subject.call.should be_a(Garner::Cache::Identity)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "sets the identity's ruby_binding to self" do
|
23
|
+
subject.call.ruby_context.should eq @test_context
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'applies each of Garner.config.context_key_strategies' do
|
27
|
+
# Default :context_key_strategies
|
28
|
+
subject.call.key_hash[:caller].should_not be_nil
|
29
|
+
|
30
|
+
# Custom :context_key_strategies
|
31
|
+
Garner.configure do |config|
|
32
|
+
config.context_key_strategies = []
|
33
|
+
end
|
34
|
+
subject.call.key_hash[:caller].should be_nil
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Garner::Cache::Identity do
|
4
|
+
|
5
|
+
it 'includes Garner.config.global_cache_options' do
|
6
|
+
Garner.configure { |config| config.global_cache_options = { foo: 'bar' } }
|
7
|
+
subject.options_hash[:foo].should eq 'bar'
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'includes Garner.config.expires_in' do
|
11
|
+
Garner.configure { |config| config.expires_in = 5.minutes }
|
12
|
+
subject.options_hash[:expires_in].should eq 5.minutes
|
13
|
+
end
|
14
|
+
|
15
|
+
describe 'nocache' do
|
16
|
+
it 'forces a cache bypass' do
|
17
|
+
Garner::Cache.should_not_receive :fetch
|
18
|
+
subject.nocache.fetch { 'foo' }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'bind' do
|
23
|
+
it "adds to the object identity's bindings" do
|
24
|
+
subject.bind('foo')
|
25
|
+
subject.bind('bar')
|
26
|
+
subject.bindings.should eq %w(foo bar)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'raises an error for <> 1 arguments' do
|
30
|
+
expect { subject.bind }.to raise_error
|
31
|
+
expect { subject.bind('foo', 'bar') }.to raise_error
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe 'key' do
|
36
|
+
it "adds to the object identity's key_hash" do
|
37
|
+
subject.key(foo: 1)
|
38
|
+
subject.key(bar: 2)
|
39
|
+
subject.key_hash.should eq(foo: 1, bar: 2)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'raises an error for <> 1 arguments' do
|
43
|
+
expect { subject.key }.to raise_error
|
44
|
+
expect { subject.key({}, {}) }.to raise_error
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'raises an error for non-hash arguments' do
|
48
|
+
expect { subject.key('foo') }.to raise_error
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe 'options' do
|
53
|
+
it "adds to the object identity's options_hash" do
|
54
|
+
subject.options(foo: 1)
|
55
|
+
subject.options(bar: 2)
|
56
|
+
subject.options_hash.should eq(expires_in: nil, foo: 1, bar: 2)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'raises an error for <> 1 arguments' do
|
60
|
+
expect { subject.options }.to raise_error
|
61
|
+
expect { subject.options({}, {}) }.to raise_error
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'raises an error for non-hash arguments' do
|
65
|
+
expect { subject.options('foo') }.to raise_error
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Garner::Cache do
|
4
|
+
subject do
|
5
|
+
Garner::Cache
|
6
|
+
end
|
7
|
+
|
8
|
+
describe 'fetch' do
|
9
|
+
it 'requires bindings, a key hash, and an options hash' do
|
10
|
+
expect { subject.fetch { 'foo' } }.to raise_error
|
11
|
+
expect { subject.fetch([]) { 'foo' } }.to raise_error
|
12
|
+
expect { subject.fetch([], {}) { 'foo' } }.to raise_error
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'requires a block' do
|
16
|
+
expect { subject.fetch([], {}, {}) }.to raise_error
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'does not cache nil results' do
|
20
|
+
result1 = subject.fetch([], {}, {}) { nil }
|
21
|
+
result2 = subject.fetch([], {}, {}) { 'foo' }
|
22
|
+
result3 = subject.fetch([], {}, {}) { 'bar' }
|
23
|
+
|
24
|
+
result1.should.nil?
|
25
|
+
result2.should eq 'foo'
|
26
|
+
result3.should eq 'foo'
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'does not cache results with un-bindable bindings' do
|
30
|
+
unbindable = double('object')
|
31
|
+
unbindable.stub(:garner_cache_key) { nil }
|
32
|
+
result1 = subject.fetch([unbindable], {}, {}) { 'foo' }
|
33
|
+
result2 = subject.fetch([unbindable], {}, {}) { 'bar' }
|
34
|
+
result1.should eq 'foo'
|
35
|
+
result2.should eq 'bar'
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'raises an exception by default for nil bindings' do
|
39
|
+
expect do
|
40
|
+
subject.fetch([nil], {}, {}) { 'foo' }
|
41
|
+
end.to raise_error(Garner::Cache::NilBinding)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'raises no exception for nil bindings if config.whiny_nils is false' do
|
45
|
+
Garner.configure { |config| config.whiny_nils = false }
|
46
|
+
expect { subject.fetch([nil], {}, {}) { 'foo' } }.not_to raise_error
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Garner::Config do
|
4
|
+
before :each do
|
5
|
+
@cache = Garner::Config.cache
|
6
|
+
end
|
7
|
+
after :each do
|
8
|
+
Garner::Config.cache = @cache
|
9
|
+
end
|
10
|
+
it 'configures a cache store' do
|
11
|
+
cache = Class.new
|
12
|
+
Garner.configure do |config|
|
13
|
+
config.cache = cache
|
14
|
+
end
|
15
|
+
Garner.config.cache.should eq cache
|
16
|
+
end
|
17
|
+
end
|