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