active_remote-cached 1.0.0 → 1.2.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.
@@ -1,25 +1,151 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe ::ActiveRemote::Cached::ArgumentKeys do
4
- it "does not mutate a string by default" do
5
- ::ActiveRemote::Cached::ArgumentKeys.new("hello", {}).cache_key.must_equal("hello")
6
+ it 'does not mutate a string by default' do
7
+ expect(::ActiveRemote::Cached::ArgumentKeys.new('hello', {}).cache_key).to eq('hello')
6
8
  end
7
9
 
8
- it "returns a string of a symbol by default" do
9
- ::ActiveRemote::Cached::ArgumentKeys.new(:hello, {}).cache_key.must_equal("hello")
10
+ it 'returns a string of a symbol by default' do
11
+ expect(::ActiveRemote::Cached::ArgumentKeys.new(:hello, {}).cache_key).to eq('hello')
10
12
  end
11
13
 
12
- it "does not mutate a string with special characters by default" do
13
- ::ActiveRemote::Cached::ArgumentKeys.new("hello {}", {}).cache_key.must_equal("hello {}")
14
+ it 'does not mutate a string with special characters by default' do
15
+ expect(::ActiveRemote::Cached::ArgumentKeys.new('hello {}', {}).cache_key).to eq('hello {}')
14
16
  end
15
17
 
16
- it "removes special characters from string with special characters when :active_remote_cached_remove_characters" do
18
+ it 'removes special characters from string with special characters when :active_remote_cached_remove_characters' do
17
19
  options = { :active_remote_cached_remove_characters => true }
18
- ::ActiveRemote::Cached::ArgumentKeys.new("hello {}", options).cache_key.must_equal("hello")
20
+ expect(::ActiveRemote::Cached::ArgumentKeys.new('hello {}', options).cache_key).to eq('hello')
21
+ end
22
+
23
+ it 'replaces special characters from string with special characters when :active_remote_cached_replace_characters' do
24
+ options = { :active_remote_cached_replace_characters => true }
25
+ expect(::ActiveRemote::Cached::ArgumentKeys.new('hello {}', options).cache_key).to eq('helloSPLBRB')
26
+ end
27
+
28
+ it 'replaces a tab when :active_remote_cached_replace_characters' do
29
+ options = { :active_remote_cached_replace_characters => true }
30
+ expect(::ActiveRemote::Cached::ArgumentKeys.new("a\tb", options).cache_key).to eq('aTBb')
31
+ end
32
+
33
+ it 'replaces a newline when :active_remote_cached_replace_characters' do
34
+ options = { :active_remote_cached_replace_characters => true }
35
+ expect(::ActiveRemote::Cached::ArgumentKeys.new("a\nb", options).cache_key).to eq('aNLb')
36
+ end
37
+
38
+ # REMOVE_CHARACTERS uses [[:space:]], so both options must cover the same
39
+ # whitespace. A raw tab or newline in a key breaks the Memcached protocol.
40
+ it 'leaves no whitespace in the key under either option' do
41
+ argument = "a b\tc\nd\re\ff\vg"
42
+
43
+ removed = ::ActiveRemote::Cached::ArgumentKeys.new(
44
+ argument, :active_remote_cached_remove_characters => true
45
+ ).cache_key
46
+ replaced = ::ActiveRemote::Cached::ArgumentKeys.new(
47
+ argument, :active_remote_cached_replace_characters => true
48
+ ).cache_key
49
+
50
+ expect(removed).not_to match(/[[:space:]]/)
51
+ expect(replaced).not_to match(/[[:space:]]/)
52
+ end
53
+
54
+ it 'joins multiple arguments into one key' do
55
+ expect(::ActiveRemote::Cached::ArgumentKeys.new('hello', 'world', {}).cache_key).to eq('helloworld')
56
+ end
57
+
58
+ it 'removes nil arguments from the key' do
59
+ expect(::ActiveRemote::Cached::ArgumentKeys.new('hello', nil, 'world', {}).cache_key).to eq('helloworld')
60
+ end
61
+
62
+ it 'flattens nested array arguments into the key' do
63
+ expect(::ActiveRemote::Cached::ArgumentKeys.new(['hello', ['world']], {}).cache_key).to eq('helloworld')
64
+ end
65
+
66
+ it 'returns an empty key when no arguments are given' do
67
+ expect(::ActiveRemote::Cached::ArgumentKeys.new({}).cache_key).to eq('')
68
+ end
69
+
70
+ it 'returns the cache key from #to_s' do
71
+ expect(::ActiveRemote::Cached::ArgumentKeys.new('hello', {}).to_s).to eq('hello')
72
+ end
73
+
74
+ it 'removes characters when both the remove and the replace option are given' do
75
+ options = {
76
+ :active_remote_cached_remove_characters => true,
77
+ :active_remote_cached_replace_characters => true
78
+ }
79
+ expect(::ActiveRemote::Cached::ArgumentKeys.new('hello {}', options).cache_key).to eq('hello')
19
80
  end
20
81
 
21
- it "replaces special characters from string with special characters when :active_remote_cached_replace_characters" do
82
+ describe '.for_fields' do
83
+ it 'names each field in the key' do
84
+ argument_keys = ::ActiveRemote::Cached::ArgumentKeys.for_fields(%i[alpha beta], %w[x y], {})
85
+
86
+ expect(argument_keys.cache_key).to eq('alpha.x/beta.y')
87
+ end
88
+
89
+ it 'gives two finders with the same values two different keys' do
90
+ alpha_beta = ::ActiveRemote::Cached::ArgumentKeys.for_fields(%i[alpha beta], %w[x y], {})
91
+ gamma_delta = ::ActiveRemote::Cached::ArgumentKeys.for_fields(%i[gamma delta], %w[x y], {})
92
+
93
+ expect(alpha_beta.cache_key).not_to eq(gamma_delta.cache_key)
94
+ end
95
+
96
+ it 'gives a single field a different key than two fields with the same characters' do
97
+ one_field = ::ActiveRemote::Cached::ArgumentKeys.for_fields(%i[guid], %w[xy], {})
98
+ two_fields = ::ActiveRemote::Cached::ArgumentKeys.for_fields(%i[alpha beta], %w[x y], {})
99
+
100
+ expect(one_field.cache_key).not_to eq(two_fields.cache_key)
101
+ end
102
+
103
+ it 'joins an array value with a comma' do
104
+ argument_keys = ::ActiveRemote::Cached::ArgumentKeys.for_fields(%i[guid], [%w[a b]], {})
105
+
106
+ expect(argument_keys.cache_key).to eq('guid.a.b')
107
+ end
108
+
109
+ it 'removes a nil inside an array value' do
110
+ argument_keys = ::ActiveRemote::Cached::ArgumentKeys.for_fields(%i[guid], [['a', nil, 'b']], {})
111
+
112
+ expect(argument_keys.cache_key).to eq('guid.a.b')
113
+ end
114
+
115
+ it 'gives an empty value for a nil argument' do
116
+ argument_keys = ::ActiveRemote::Cached::ArgumentKeys.for_fields(%i[alpha beta], ['x', nil], {})
117
+
118
+ expect(argument_keys.cache_key).to eq('alpha.x/beta.')
119
+ end
120
+
121
+ it 'escapes a separator inside a value' do
122
+ argument_keys = ::ActiveRemote::Cached::ArgumentKeys.for_fields(%i[guid], ['a/b.c%d'], {})
123
+
124
+ expect(argument_keys.cache_key).to eq('guid.a%2Fb%2Ec%25d')
125
+ end
126
+
127
+ it 'keeps the separators when the remove option is given' do
128
+ options = { :active_remote_cached_remove_characters => true }
129
+ argument_keys = ::ActiveRemote::Cached::ArgumentKeys.for_fields(%i[alpha beta], ['hello {}', 'y'], options)
130
+
131
+ expect(argument_keys.cache_key).to eq('alpha.hello/beta.y')
132
+ end
133
+
134
+ it 'keeps the separators when the replace option is given' do
135
+ options = { :active_remote_cached_replace_characters => true }
136
+ argument_keys = ::ActiveRemote::Cached::ArgumentKeys.for_fields(%i[alpha beta], ['hello {}', 'y'], options)
137
+
138
+ expect(argument_keys.cache_key).to eq('alpha.helloSPLBRB/beta.y')
139
+ end
140
+ end
141
+
142
+ # #cache_key calls gsub! on an instance variable, so a second call must not
143
+ # replace the characters of the first result again.
144
+ it 'returns the same key when :active_remote_cached_replace_characters is called twice' do
22
145
  options = { :active_remote_cached_replace_characters => true }
23
- ::ActiveRemote::Cached::ArgumentKeys.new("hello {}", options).cache_key.must_equal("helloSPLBRB")
146
+ argument_keys = ::ActiveRemote::Cached::ArgumentKeys.new('hello {}', options)
147
+
148
+ expect(argument_keys.cache_key).to eq('helloSPLBRB')
149
+ expect(argument_keys.cache_key).to eq('helloSPLBRB')
24
150
  end
25
151
  end
@@ -1,35 +1,198 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe ::ActiveRemote::Cached::Cache do
4
- describe "API" do
5
- it "validates #delete present" do
6
+ let(:invalid_provider_error) { ::ActiveRemote::Cached::Cache::InvalidCacheProvider }
7
+ let(:cache_provider) { ::ActiveSupport::Cache::MemoryStore.new }
8
+ let(:cache) { ::ActiveRemote::Cached::Cache.new(cache_provider) }
9
+
10
+ describe 'API' do
11
+ it 'validates #delete present' do
6
12
  cache = OpenStruct.new(:write => nil, :fetch => nil, :read => nil, :exist? => nil)
7
- error = lambda { ::ActiveRemote::Cached.cache(cache) }.must_raise(RuntimeError)
8
- error.message.must_match(/respond_to.*delete/i)
13
+ expect { ::ActiveRemote::Cached.cache(cache) }.to raise_error(invalid_provider_error, /respond_to.*delete/i)
9
14
  end
10
15
 
11
- it "validates #exist? present" do
16
+ it 'validates #exist? present' do
12
17
  cache = OpenStruct.new(:write => nil, :delete => nil, :read => nil, :fetch => nil)
13
- error = lambda { ::ActiveRemote::Cached.cache(cache) }.must_raise(RuntimeError)
14
- error.message.must_match(/respond_to.*exist/i)
18
+ expect { ::ActiveRemote::Cached.cache(cache) }.to raise_error(invalid_provider_error, /respond_to.*exist/i)
15
19
  end
16
20
 
17
- it "validates #fetch present" do
21
+ it 'validates #fetch present' do
18
22
  cache = OpenStruct.new(:write => nil, :delete => nil, :read => nil, :exist? => nil)
19
- error = lambda { ::ActiveRemote::Cached.cache(cache) }.must_raise(RuntimeError)
20
- error.message.must_match(/respond_to.*fetch/i)
23
+ expect { ::ActiveRemote::Cached.cache(cache) }.to raise_error(invalid_provider_error, /respond_to.*fetch/i)
21
24
  end
22
25
 
23
- it "validates #read present" do
26
+ it 'validates #read present' do
24
27
  cache = OpenStruct.new(:write => nil, :delete => nil, :fetch => nil, :exist? => nil)
25
- error = lambda { ::ActiveRemote::Cached.cache(cache) }.must_raise(RuntimeError)
26
- error.message.must_match(/respond_to.*read/i)
28
+ expect { ::ActiveRemote::Cached.cache(cache) }.to raise_error(invalid_provider_error, /respond_to.*read/i)
27
29
  end
28
30
 
29
- it "validates #write present" do
31
+ it 'validates #write present' do
30
32
  cache = OpenStruct.new(:read => nil, :delete => nil, :fetch => nil, :exist? => nil)
31
- error = lambda { ::ActiveRemote::Cached.cache(cache)}.must_raise(RuntimeError)
32
- error.message.must_match(/respond_to.*write/i)
33
+ expect { ::ActiveRemote::Cached.cache(cache) }.to raise_error(invalid_provider_error, /respond_to.*write/i)
34
+ end
35
+ end
36
+
37
+ describe '#nested_caching?' do
38
+ it 'is false before nested caching is enabled' do
39
+ expect(cache.nested_caching?).to eq(false)
40
+ end
41
+ end
42
+
43
+ describe 'delegation' do
44
+ it 'exposes the cache provider it was given' do
45
+ expect(cache.cache_provider).to be(cache_provider)
46
+ end
47
+
48
+ it 'delegates unknown methods to the cache provider' do
49
+ expect(cache_provider).to receive(:clear).and_return(:cleared)
50
+ expect(cache.clear).to eq(:cleared)
51
+ end
52
+ end
53
+
54
+ describe '#fetch' do
55
+ it 'returns a nil value but does not persist it by default' do
56
+ expect(cache.fetch('key') { nil }).to be_nil
57
+ expect(cache_provider.exist?('key')).to eq(false)
58
+ end
59
+
60
+ it 'returns an empty value but does not persist it by default' do
61
+ expect(cache.fetch('key') { [] }).to eq([])
62
+ expect(cache_provider.exist?('key')).to eq(false)
63
+ end
64
+
65
+ it 'persists a nil value when :allow_nil is given' do
66
+ expect(cache.fetch('key', :allow_nil => true) { nil }).to be_nil
67
+ expect(cache_provider.exist?('key')).to eq(true)
68
+ end
69
+
70
+ it 'persists an empty value when :allow_empty is given' do
71
+ expect(cache.fetch('key', :allow_empty => true) { [] }).to eq([])
72
+ expect(cache_provider.exist?('key')).to eq(true)
73
+ end
74
+
75
+ it 'persists a value that is neither nil nor empty' do
76
+ expect(cache.fetch('key') { [:record] }).to eq([:record])
77
+ expect(cache_provider.exist?('key')).to eq(true)
78
+ end
79
+ end
80
+
81
+ describe '#fetch round trips' do
82
+ let(:counting_provider) do
83
+ Class.new(::ActiveSupport::Cache::MemoryStore) do
84
+ def initialize(*args)
85
+ @calls = []
86
+ super
87
+ end
88
+
89
+ attr_reader :calls
90
+
91
+ def fetch(*args, **options, &block)
92
+ @calls << :fetch
93
+ super
94
+ end
95
+
96
+ def write(*args, **options)
97
+ @calls << :write
98
+ super
99
+ end
100
+
101
+ def delete(*args, **options)
102
+ @calls << :delete
103
+ super
104
+ end
105
+ end.new
106
+ end
107
+
108
+ # The provider used to write the nil and then take a second round trip to
109
+ # delete it. :skip_nil makes the provider skip the write.
110
+ it 'takes one provider call for a nil value' do
111
+ cache = ::ActiveRemote::Cached::Cache.new(counting_provider)
112
+
113
+ cache.fetch('key') { nil }
114
+
115
+ expect(counting_provider.calls).to eq([:fetch])
116
+ end
117
+
118
+ it 'still writes a nil when :allow_nil is given' do
119
+ cache = ::ActiveRemote::Cached::Cache.new(counting_provider)
120
+
121
+ cache.fetch('key', :allow_nil => true) { nil }
122
+
123
+ expect(counting_provider.calls).to eq(%i[fetch write])
124
+ expect(counting_provider.exist?('key')).to eq(true)
125
+ end
126
+ end
127
+
128
+ describe '#enable_nested_caching!' do
129
+ it 'writes to the cache provider only until nested caching is enabled' do
130
+ cache.write('key', 'value')
131
+
132
+ expect(cache_provider.read('key')).to eq('value')
133
+ end
134
+
135
+ context 'when nested caching is enabled' do
136
+ before do
137
+ cache.enable_nested_caching!
138
+ end
139
+
140
+ it 'writes to both the nested cache and the cache provider' do
141
+ cache.write('key', 'value')
142
+
143
+ expect(cache.read('key')).to eq('value')
144
+ expect(cache_provider.read('key')).to eq('value')
145
+ end
146
+
147
+ it 'deletes from both the nested cache and the cache provider' do
148
+ cache.write('key', 'value')
149
+ cache.delete('key')
150
+
151
+ expect(cache.read('key')).to be_nil
152
+ expect(cache_provider.read('key')).to be_nil
153
+ end
154
+
155
+ it 'reads the nested value in preference to the cache provider value' do
156
+ cache.write('key', 'nested')
157
+ cache_provider.write('key', 'provider')
158
+
159
+ expect(cache.read('key')).to eq('nested')
160
+ end
161
+
162
+ it 'reports a key that only the cache provider holds' do
163
+ cache_provider.write('key', 'provider')
164
+
165
+ expect(cache.exist?('key')).to eq(true)
166
+ end
167
+
168
+ it 'reports that nested caching is on' do
169
+ expect(cache.nested_caching?).to eq(true)
170
+ end
171
+
172
+ # A new Cache starts with nested caching off, so swapping the provider
173
+ # used to turn the setting off without saying so.
174
+ it 'keeps nested caching on when the cache provider is replaced' do
175
+ original_cache = ::ActiveRemote::Cached.cache
176
+
177
+ ::ActiveRemote::Cached.cache(cache_provider)
178
+ ::ActiveRemote::Cached.cache.enable_nested_caching!
179
+ ::ActiveRemote::Cached.cache(::ActiveSupport::Cache::MemoryStore.new)
180
+
181
+ expect(::ActiveRemote::Cached.cache.nested_caching?).to eq(true)
182
+ ensure
183
+ # .cache now carries the nested setting forward, so reset the module
184
+ # rather than call it again.
185
+ ::ActiveRemote::Cached.instance_variable_set(:@cache_provider, original_cache)
186
+ end
187
+
188
+ # #read joins the two providers with ||, so a false value in the nested
189
+ # cache falls through to the cache provider. This records that behavior.
190
+ it 'falls through to the cache provider when the nested value is false' do
191
+ cache.write('key', false)
192
+ cache_provider.write('key', 'provider')
193
+
194
+ expect(cache.read('key')).to eq('provider')
195
+ end
33
196
  end
34
197
  end
35
198
  end
@@ -1,45 +1,99 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  class DeleteMethodClass
4
6
  include ::ActiveRemote::Cached
5
7
 
6
- def self.find; nil; end
7
- def self.search; nil; end
8
+ def self.find(*)
9
+ :record
10
+ end
11
+
12
+ def self.search(*)
13
+ [:record]
14
+ end
8
15
 
9
16
  cached_finders_for :guid
10
17
  cached_finders_for :guid, :user_guid
11
- cached_finders_for [:user_guid, :client_guid]
12
- cached_finders_for [:derp, :user_guid, :client_guid]
18
+ cached_finders_for %i[user_guid client_guid]
19
+ cached_finders_for %i[derp user_guid client_guid]
13
20
  end
14
21
 
15
22
  describe DeleteMethodClass do
16
- describe "API" do
23
+ describe 'API' do
17
24
  it "creates 'cached_delete_by_guid'" do
18
- DeleteMethodClass.must_respond_to("cached_delete_by_guid")
25
+ expect(DeleteMethodClass).to respond_to(:cached_delete_by_guid)
19
26
  end
20
27
 
21
28
  it "creates 'cached_delete_by_user_guid'" do
22
- DeleteMethodClass.must_respond_to("cached_delete_by_user_guid")
29
+ expect(DeleteMethodClass).to respond_to(:cached_delete_by_user_guid)
23
30
  end
24
31
 
25
32
  it "creates 'cached_delete_by_user_guid_and_client_guid'" do
26
- DeleteMethodClass.must_respond_to("cached_delete_by_user_guid_and_client_guid")
33
+ expect(DeleteMethodClass).to respond_to(:cached_delete_by_user_guid_and_client_guid)
27
34
  end
28
35
 
29
36
  it "creates 'cached_delete_by_client_guid_and_user_guid'" do
30
- DeleteMethodClass.must_respond_to("cached_delete_by_client_guid_and_user_guid")
37
+ expect(DeleteMethodClass).to respond_to(:cached_delete_by_client_guid_and_user_guid)
31
38
  end
32
39
 
33
40
  it "creates 'cached_delete_by_derp_and_user_guid_and_client_guid'" do
34
- DeleteMethodClass.must_respond_to("cached_delete_by_derp_and_user_guid_and_client_guid")
41
+ expect(DeleteMethodClass).to respond_to(:cached_delete_by_derp_and_user_guid_and_client_guid)
35
42
  end
36
43
 
37
44
  it "creates 'cached_delete_by_client_guid_and_derp_and_user_guid'" do
38
- DeleteMethodClass.must_respond_to("cached_delete_by_client_guid_and_derp_and_user_guid")
45
+ expect(DeleteMethodClass).to respond_to(:cached_delete_by_client_guid_and_derp_and_user_guid)
39
46
  end
40
47
 
41
48
  it "creates 'cached_delete_by_client_guid_and_user_guid_and_derp'" do
42
- DeleteMethodClass.must_respond_to("cached_delete_by_client_guid_and_user_guid_and_derp")
49
+ expect(DeleteMethodClass).to respond_to(:cached_delete_by_client_guid_and_user_guid_and_derp)
50
+ end
51
+ end
52
+
53
+ describe '#cached_delete_by_guid' do
54
+ before do
55
+ ::ActiveRemote::Cached.cache(HashCache.new)
56
+ end
57
+
58
+ after do
59
+ ::ActiveRemote::Cached.default_options({})
60
+ end
61
+
62
+ it 'deletes the find cache key' do
63
+ DeleteMethodClass.cached_find_by_guid(:guid)
64
+ expect(DeleteMethodClass.cached_exist_find_by_guid?(:guid)).to eq(true)
65
+
66
+ DeleteMethodClass.cached_delete_by_guid(:guid)
67
+
68
+ expect(DeleteMethodClass.cached_exist_find_by_guid?(:guid)).to eq(false)
69
+ end
70
+
71
+ it 'deletes the search cache key' do
72
+ DeleteMethodClass.cached_search_by_guid(:guid)
73
+ expect(DeleteMethodClass.cached_exist_search_by_guid?(:guid)).to eq(true)
74
+
75
+ DeleteMethodClass.cached_delete_by_guid(:guid)
76
+
77
+ expect(DeleteMethodClass.cached_exist_search_by_guid?(:guid)).to eq(false)
78
+ end
79
+
80
+ it 'does not raise when the cache keys are not present' do
81
+ expect { DeleteMethodClass.cached_delete_by_guid(:missing) }.not_to raise_error
82
+ end
83
+
84
+ describe 'namespaced cache' do
85
+ it 'deletes the namespaced find and search cache keys' do
86
+ expect(::ActiveRemote::Cached.cache).to receive(:delete).with(
87
+ [::ActiveRemote::Cached::RUBY_AND_ACTIVE_SUPPORT_VERSION, 'MyApp', DeleteMethodClass.name, '#find',
88
+ 'guid.guid']
89
+ )
90
+ expect(::ActiveRemote::Cached.cache).to receive(:delete).with(
91
+ [::ActiveRemote::Cached::RUBY_AND_ACTIVE_SUPPORT_VERSION, 'MyApp', DeleteMethodClass.name, '#search',
92
+ 'guid.guid']
93
+ )
94
+
95
+ DeleteMethodClass.cached_delete_by_guid(:guid, :namespace => 'MyApp')
96
+ end
43
97
  end
44
98
  end
45
99
  end