garner 0.4.5 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +35 -0
  5. data/.travis.yml +13 -0
  6. data/CHANGELOG.md +130 -0
  7. data/CONTRIBUTING.md +118 -0
  8. data/Gemfile +3 -0
  9. data/README.md +1 -0
  10. data/Rakefile +39 -0
  11. data/UPGRADING.md +118 -0
  12. data/garner.gemspec +44 -0
  13. data/lib/garner.rb +21 -21
  14. data/lib/garner/cache.rb +13 -6
  15. data/lib/garner/cache/binding.rb +6 -14
  16. data/lib/garner/cache/context.rb +11 -12
  17. data/lib/garner/cache/identity.rb +1 -1
  18. data/lib/garner/config.rb +12 -7
  19. data/lib/garner/mixins/active_record.rb +3 -3
  20. data/lib/garner/mixins/active_record/base.rb +2 -2
  21. data/lib/garner/mixins/mongoid.rb +4 -4
  22. data/lib/garner/mixins/mongoid/document.rb +8 -12
  23. data/lib/garner/mixins/mongoid/identity.rb +5 -6
  24. data/lib/garner/mixins/rack.rb +1 -2
  25. data/lib/garner/strategies/binding/invalidation/base.rb +2 -4
  26. data/lib/garner/strategies/binding/invalidation/binding_index.rb +1 -3
  27. data/lib/garner/strategies/binding/invalidation/touch.rb +0 -2
  28. data/lib/garner/strategies/binding/key/base.rb +1 -3
  29. data/lib/garner/strategies/binding/key/binding_index.rb +3 -4
  30. data/lib/garner/strategies/binding/key/cache_key.rb +0 -2
  31. data/lib/garner/strategies/binding/key/safe_cache_key.rb +2 -3
  32. data/lib/garner/strategies/context/key/base.rb +1 -3
  33. data/lib/garner/strategies/context/key/caller.rb +9 -12
  34. data/lib/garner/strategies/context/key/jsonp.rb +3 -6
  35. data/lib/garner/strategies/context/key/request_get.rb +2 -4
  36. data/lib/garner/strategies/context/key/request_path.rb +1 -3
  37. data/lib/garner/strategies/context/key/request_post.rb +2 -4
  38. data/lib/garner/version.rb +1 -1
  39. data/spec/garner/cache/context_spec.rb +38 -0
  40. data/spec/garner/cache/identity_spec.rb +68 -0
  41. data/spec/garner/cache_spec.rb +49 -0
  42. data/spec/garner/config_spec.rb +17 -0
  43. data/spec/garner/mixins/mongoid/document_spec.rb +80 -0
  44. data/spec/garner/mixins/mongoid/identity_spec.rb +140 -0
  45. data/spec/garner/mixins/rack_spec.rb +48 -0
  46. data/spec/garner/strategies/binding/invalidation/binding_index_spec.rb +14 -0
  47. data/spec/garner/strategies/binding/invalidation/touch_spec.rb +23 -0
  48. data/spec/garner/strategies/binding/key/binding_index_spec.rb +245 -0
  49. data/spec/garner/strategies/binding/key/cache_key_spec.rb +29 -0
  50. data/spec/garner/strategies/binding/key/safe_cache_key_spec.rb +61 -0
  51. data/spec/garner/strategies/context/key/caller_spec.rb +106 -0
  52. data/spec/garner/strategies/context/key/jsonp_spec.rb +22 -0
  53. data/spec/garner/strategies/context/key/request_get_spec.rb +33 -0
  54. data/spec/garner/strategies/context/key/request_path_spec.rb +28 -0
  55. data/spec/garner/strategies/context/key/request_post_spec.rb +34 -0
  56. data/spec/garner/version_spec.rb +11 -0
  57. data/spec/integration/active_record_spec.rb +43 -0
  58. data/spec/integration/grape_spec.rb +33 -0
  59. data/spec/integration/mongoid_spec.rb +355 -0
  60. data/spec/integration/rack_spec.rb +77 -0
  61. data/spec/integration/sinatra_spec.rb +29 -0
  62. data/spec/performance/strategy_benchmark.rb +59 -0
  63. data/spec/performance/support/benchmark_context.rb +31 -0
  64. data/spec/performance/support/benchmark_context_wrapper.rb +67 -0
  65. data/spec/shared/binding_invalidation_strategy.rb +17 -0
  66. data/spec/shared/binding_key_strategy.rb +35 -0
  67. data/spec/shared/conditional_get.rb +48 -0
  68. data/spec/shared/context_key_strategy.rb +24 -0
  69. data/spec/spec_helper.rb +24 -0
  70. data/spec/spec_support.rb +5 -0
  71. data/spec/support/active_record.rb +36 -0
  72. data/spec/support/cache.rb +15 -0
  73. data/spec/support/garner.rb +5 -0
  74. data/spec/support/mongoid.rb +71 -0
  75. 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(binding)
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
- :strategy => self,
100
- :proxied_binding => binding_key
98
+ strategy: self,
99
+ proxied_binding: binding_key
101
100
  }
102
101
  end
103
102
 
104
- def self.new_cache_key_for(binding)
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 = /^(?<model>[^\/]+)\/(?<id>.+)-(?<timestamp>[0-9]{14})$/
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 = sprintf("%.10f", decimal_portion).gsub(/^0/, "")
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, ruby_context = nil)
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).detect do |line|
25
- !line.include?(File.join("lib", "garner"))
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(":")[0]
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("lib", "garner"))
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 || "", "")}:#{parts[:lineno]}"
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) : identity
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, "..")).realpath.to_s
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, "Gemfile"))
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 identity unless key_hash[field]
19
-
17
+ return super unless key_hash[field]
20
18
 
21
- key_hash[field].delete("callback")
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 identity unless (ruby_context.respond_to?(:request))
16
+ return super unless ruby_context.respond_to?(:request)
18
17
 
19
18
  request = ruby_context.request
20
- if request && ["GET", "HEAD"].include?(request.request_method)
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 identity unless (ruby_context.respond_to?(:request))
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 identity unless (ruby_context.respond_to?(:request))
16
+ return super unless ruby_context.respond_to?(:request)
18
17
 
19
18
  request = ruby_context.request
20
- if request.request_method == "POST"
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
@@ -1,3 +1,3 @@
1
1
  module Garner
2
- VERSION = "0.4.5"
2
+ VERSION = '0.5.0'
3
3
  end
@@ -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