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.
@@ -0,0 +1,331 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ # 5.minutes below. The library does not require this itself.
6
+ require 'active_support/core_ext/numeric/time'
7
+
8
+ class ConfigurationClass
9
+ include ::ActiveRemote::Cached
10
+
11
+ def self.find(*)
12
+ :find_result
13
+ end
14
+
15
+ def self.search(*)
16
+ [:search_result]
17
+ end
18
+
19
+ cached_finders_for :guid
20
+ cached_finders_for %i[alpha beta]
21
+
22
+ # Declared twice on purpose, to prove cached_methods does not repeat itself.
23
+ cached_finders_for :guid
24
+ end
25
+
26
+ class AliasFinderClass
27
+ include ::ActiveRemote::Cached
28
+
29
+ def self.find(*)
30
+ :find_result
31
+ end
32
+
33
+ def self.search(*)
34
+ [:search_result]
35
+ end
36
+
37
+ cached_finders :guid
38
+ end
39
+
40
+ class ChildFinderClass < ConfigurationClass; end
41
+
42
+ class DurationOptionClass
43
+ include ::ActiveRemote::Cached
44
+
45
+ def self.find(*)
46
+ :find_result
47
+ end
48
+
49
+ def self.search(*)
50
+ [:search_result]
51
+ end
52
+
53
+ # 5.minutes has no literal form. The options must not be interpolated into
54
+ # the generated source.
55
+ cached_finders_for :guid, :expires_in => 5.minutes
56
+ end
57
+
58
+ class DurationChildClass < DurationOptionClass; end
59
+
60
+ describe ::ActiveRemote::Cached do
61
+ let(:versioned_prefix) { ::ActiveRemote::Cached::RUBY_AND_ACTIVE_SUPPORT_VERSION }
62
+
63
+ before do
64
+ ::ActiveRemote::Cached.cache(HashCache.new)
65
+ end
66
+
67
+ after do
68
+ ::ActiveRemote::Cached.default_options({})
69
+ end
70
+
71
+ describe '.cache' do
72
+ it 'wraps the cache provider in a cache' do
73
+ expect(::ActiveRemote::Cached.cache).to be_a(::ActiveRemote::Cached::Cache)
74
+ end
75
+
76
+ it 'returns the current cache when no cache provider is given' do
77
+ cache = ::ActiveRemote::Cached.cache
78
+
79
+ expect(::ActiveRemote::Cached.cache).to be(cache)
80
+ end
81
+ end
82
+
83
+ describe '.default_options' do
84
+ it 'returns an empty hash when no options are set' do
85
+ expect(::ActiveRemote::Cached.default_options).to eq({})
86
+ end
87
+
88
+ it 'returns the options that were set' do
89
+ ::ActiveRemote::Cached.default_options(:expires_in => 100)
90
+
91
+ expect(::ActiveRemote::Cached.default_options).to eq(:expires_in => 100)
92
+ end
93
+
94
+ # Passing nil reads the options. Callers must pass an empty hash to clear.
95
+ it 'keeps the current options when nil is given' do
96
+ ::ActiveRemote::Cached.default_options(:expires_in => 100)
97
+
98
+ expect(::ActiveRemote::Cached.default_options(nil)).to eq(:expires_in => 100)
99
+ end
100
+
101
+ it 'clears the current options when an empty hash is given' do
102
+ ::ActiveRemote::Cached.default_options(:expires_in => 100)
103
+
104
+ expect(::ActiveRemote::Cached.default_options({})).to eq({})
105
+ end
106
+ end
107
+
108
+ describe 'RUBY_AND_ACTIVE_SUPPORT_VERSION' do
109
+ it 'prefixes every cache key' do
110
+ expect(::ActiveRemote::Cached.cache).to receive(:fetch).with(
111
+ [versioned_prefix, ConfigurationClass.name, '#find', 'guid.guid'], {}
112
+ ).and_return(:find_result)
113
+
114
+ ConfigurationClass.cached_find_by_guid(:guid)
115
+ end
116
+ end
117
+
118
+ describe '.cached_find' do
119
+ it 'calls find with the given arguments' do
120
+ expect(ConfigurationClass).to receive(:find).and_return(:hello)
121
+
122
+ expect(ConfigurationClass.cached_find(:guid => :guid)).to eq(:hello)
123
+ end
124
+
125
+ it 'sorts the argument keys before it builds the cache key' do
126
+ expect(ConfigurationClass).to receive(:cached_find_by_alpha_and_beta).with('A', 'B', {})
127
+
128
+ ConfigurationClass.cached_find(:beta => 'B', :alpha => 'A')
129
+ end
130
+
131
+ it 'yields to the block instead of calling find' do
132
+ expect(ConfigurationClass).not_to receive(:find)
133
+
134
+ expect(ConfigurationClass.cached_find(:guid => :guid) { :block_result }).to eq(:block_result)
135
+ end
136
+
137
+ it 'passes the options through to the fetch call' do
138
+ expect(::ActiveRemote::Cached.cache).to receive(:fetch).with(
139
+ [versioned_prefix, ConfigurationClass.name, '#find', 'guid.guid'], { :expires_in => 200 }
140
+ ).and_return(:hello)
141
+
142
+ expect(ConfigurationClass.cached_find({ :guid => :guid }, :expires_in => 200)).to eq(:hello)
143
+ end
144
+ end
145
+
146
+ describe '.cached_search' do
147
+ it 'calls search with the given arguments' do
148
+ expect(ConfigurationClass).to receive(:search).and_return([:hello])
149
+
150
+ expect(ConfigurationClass.cached_search(:guid => :guid)).to eq([:hello])
151
+ end
152
+
153
+ it 'sorts the argument keys before it builds the cache key' do
154
+ expect(ConfigurationClass).to receive(:cached_search_by_alpha_and_beta).with('A', 'B', {})
155
+
156
+ ConfigurationClass.cached_search(:beta => 'B', :alpha => 'A')
157
+ end
158
+ end
159
+
160
+ describe '.cached_methods' do
161
+ it 'does not repeat a method when the same finder is declared twice' do
162
+ expect(ConfigurationClass.cached_methods).to eq(ConfigurationClass.cached_methods.uniq)
163
+ end
164
+ end
165
+
166
+ describe '.cached_finders' do
167
+ it 'creates the same methods as cached_finders_for' do
168
+ expect(AliasFinderClass).to respond_to(:cached_find_by_guid)
169
+ expect(AliasFinderClass).to respond_to(:cached_search_by_guid)
170
+ end
171
+ end
172
+
173
+ describe '.respond_to_missing?' do
174
+ it 'is true for a finder that was defined' do
175
+ expect(ConfigurationClass.respond_to?(:cached_find_by_alpha_and_beta)).to eq(true)
176
+ end
177
+
178
+ it 'is true for a finder whose arguments are in a different order' do
179
+ expect(ConfigurationClass.respond_to?(:cached_find_by_beta_and_alpha)).to eq(true)
180
+ end
181
+
182
+ it 'is false for a finder that was not defined' do
183
+ expect(ConfigurationClass.respond_to?(:cached_find_by_missing)).to eq(false)
184
+ end
185
+
186
+ it 'returns a method object for a finder in a different argument order' do
187
+ expect(ConfigurationClass.method(:cached_find_by_beta_and_alpha)).to be_a(::Method)
188
+ end
189
+ end
190
+
191
+ describe '.method_missing' do
192
+ it 'raises NoMethodError for a method that is not a finder' do
193
+ expect { ConfigurationClass.not_a_finder }.to raise_error(::NoMethodError)
194
+ end
195
+
196
+ it 'raises NoMethodError for a finder that was not defined' do
197
+ expect { ConfigurationClass.cached_find_by_missing(:value) }.to raise_error(::NoMethodError)
198
+ end
199
+ end
200
+
201
+ describe '._args_in_sorted_order' do
202
+ it 'reorders the arguments to match the defined method' do
203
+ expect(ConfigurationClass).to receive(:cached_find_by_alpha_and_beta).with('A', 'B')
204
+
205
+ ConfigurationClass.cached_find_by_beta_and_alpha('B', 'A')
206
+ end
207
+
208
+ it 'passes the options hash through as the last argument' do
209
+ expect(ConfigurationClass).to receive(:cached_find_by_alpha_and_beta).with('A', 'B', { :expires_in => 10 })
210
+
211
+ ConfigurationClass.cached_find_by_beta_and_alpha('B', 'A', { :expires_in => 10 })
212
+ end
213
+
214
+ # A missing argument used to become nil, and the finder then cached a
215
+ # record under a key built from that nil.
216
+ it 'raises ArgumentError when an argument is missing' do
217
+ expect do
218
+ ConfigurationClass.cached_find_by_beta_and_alpha('B')
219
+ end.to raise_error(::ArgumentError, /given 1, expected 2/)
220
+ end
221
+ end
222
+
223
+ describe '._method_missing_name' do
224
+ it 'returns nil when the method name is not a finder' do
225
+ expect(ConfigurationClass._method_missing_name(:not_a_finder)).to be_nil
226
+ end
227
+
228
+ it 'sorts the argument names in the method name' do
229
+ expect(ConfigurationClass._method_missing_name(:cached_find_by_beta_and_alpha)).to eq(
230
+ :cached_find_by_alpha_and_beta
231
+ )
232
+ end
233
+
234
+ # The regex has no anchors, so it matches a finder name inside a longer
235
+ # name. This records the current behavior. See the note in the README.
236
+ it 'matches a finder name that is a suffix of a longer method name' do
237
+ expect(ConfigurationClass._method_missing_name(:not_cached_find_by_guid)).to eq(
238
+ :cached_find_by_guid
239
+ )
240
+ end
241
+ end
242
+
243
+ describe 'a finder whose definition fails' do
244
+ let(:broken_class) do
245
+ Class.new do
246
+ include ::ActiveRemote::Cached
247
+
248
+ def self.find(*)
249
+ :find_result
250
+ end
251
+
252
+ def self.search(*)
253
+ [:search_result]
254
+ end
255
+ end
256
+ end
257
+
258
+ # cached_methods used to be written before class_eval ran, so a failure
259
+ # registered a name with no method behind it. method_missing then
260
+ # dispatched to that same missing name and recursed until the stack ran out.
261
+ it 'registers no method name' do
262
+ expect { broken_class.cached_finders_for :'bad-name' }.to raise_error(::SyntaxError)
263
+
264
+ expect(broken_class.cached_methods).to eq([])
265
+ end
266
+
267
+ it 'raises NoMethodError rather than recursing' do
268
+ begin
269
+ broken_class.cached_finders_for :'bad-name'
270
+ rescue ::SyntaxError # rubocop:disable Lint/SuppressedException
271
+ end
272
+
273
+ expect { broken_class.cached_delete_by_guid('x') }.to raise_error(::NoMethodError)
274
+ end
275
+ end
276
+
277
+ describe 'a finder declared with an option that has no literal form' do
278
+ it 'defines the finder methods' do
279
+ expect(DurationOptionClass).to respond_to(:cached_find_by_guid)
280
+ expect(DurationOptionClass).to respond_to(:cached_search_by_guid)
281
+ end
282
+
283
+ it 'passes the option through to the fetch call' do
284
+ expect(::ActiveRemote::Cached.cache).to receive(:fetch).with(
285
+ [versioned_prefix, DurationOptionClass.name, '#find', 'guid.guid'], { :expires_in => 5.minutes }
286
+ ).and_return(:hello)
287
+
288
+ expect(DurationOptionClass.cached_find_by_guid(:guid)).to eq(:hello)
289
+ end
290
+
291
+ it 'lets a local option override the declared option' do
292
+ expect(::ActiveRemote::Cached.cache).to receive(:fetch).with(
293
+ [versioned_prefix, DurationOptionClass.name, '#find', 'guid.guid'], { :expires_in => 200 }
294
+ ).and_return(:hello)
295
+
296
+ expect(DurationOptionClass.cached_find_by_guid(:guid, :expires_in => 200)).to eq(:hello)
297
+ end
298
+
299
+ it 'applies the declared option in a subclass' do
300
+ expect(::ActiveRemote::Cached.cache).to receive(:fetch).with(
301
+ [versioned_prefix, DurationChildClass.name, '#find', 'guid.guid'], { :expires_in => 5.minutes }
302
+ ).and_return(:hello)
303
+
304
+ expect(DurationChildClass.cached_find_by_guid(:guid)).to eq(:hello)
305
+ end
306
+ end
307
+
308
+ describe 'a subclass of a class with cached finders' do
309
+ it 'responds to the finders the parent defined' do
310
+ expect(ChildFinderClass).to respond_to(:cached_find_by_alpha_and_beta)
311
+ end
312
+
313
+ it 'calls the finders the parent defined' do
314
+ expect(ChildFinderClass).to receive(:find).and_return(:hello)
315
+
316
+ expect(ChildFinderClass.cached_find_by_alpha_and_beta('A', 'B')).to eq(:hello)
317
+ end
318
+
319
+ it 'has an empty cached_methods list of its own' do
320
+ expect(ChildFinderClass.cached_methods).to eq([])
321
+ end
322
+
323
+ # method_missing reads cached_methods, which is empty on the subclass, so
324
+ # the subclass cannot reorder the arguments the way the parent can.
325
+ it 'does not accept the finder arguments in a different order' do
326
+ expect(ConfigurationClass.cached_find_by_beta_and_alpha('B', 'A')).to eq(:find_result)
327
+
328
+ expect { ChildFinderClass.cached_find_by_beta_and_alpha('B', 'A') }.to raise_error(::NoMethodError)
329
+ end
330
+ end
331
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,21 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ostruct'
1
4
  require 'rubygems'
2
5
  require 'bundler'
3
6
  Bundler.require(:default, :development, :test)
4
7
 
5
- require 'minitest/mock'
6
- require 'minitest/spec'
7
- require 'minitest/autorun'
8
- require 'minitest/pride'
9
-
10
8
  class HashCache < Hash
11
9
  def exist?(key)
12
- self.has_key?(key)
10
+ key?(key)
13
11
  end
14
12
 
15
- def fetch(key, options = {}, &blk)
16
- if self.has_key?(key)
17
- return self[key]
18
- end
13
+ def fetch(key, _options = {})
14
+ return self[key] if key?(key)
19
15
 
20
16
  self[key] = yield
21
17
  end
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_remote-cached
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brandon Dewitt
8
8
  - MXDevExperience
9
- autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2024-04-26 00:00:00.000000000 Z
11
+ date: 1980-01-02 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: active_remote
@@ -17,23 +16,37 @@ dependencies:
17
16
  requirements:
18
17
  - - ">="
19
18
  - !ruby/object:Gem::Version
20
- version: '0'
19
+ version: '6.1'
21
20
  type: :runtime
22
21
  prerelease: false
23
22
  version_requirements: !ruby/object:Gem::Requirement
24
23
  requirements:
25
24
  - - ">="
26
25
  - !ruby/object:Gem::Version
27
- version: '0'
26
+ version: '6.1'
28
27
  - !ruby/object:Gem::Dependency
29
28
  name: activesupport
30
29
  requirement: !ruby/object:Gem::Requirement
31
30
  requirements:
32
31
  - - ">="
33
32
  - !ruby/object:Gem::Version
34
- version: '0'
33
+ version: '6.1'
35
34
  type: :runtime
36
35
  prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '6.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: appraisal
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
37
50
  version_requirements: !ruby/object:Gem::Requirement
38
51
  requirements:
39
52
  - - ">="
@@ -67,6 +80,20 @@ dependencies:
67
80
  - - ">="
68
81
  - !ruby/object:Gem::Version
69
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: ostruct
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
70
97
  - !ruby/object:Gem::Dependency
71
98
  name: pry
72
99
  requirement: !ruby/object:Gem::Requirement
@@ -95,6 +122,34 @@ dependencies:
95
122
  - - ">="
96
123
  - !ruby/object:Gem::Version
97
124
  version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '3.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '3.0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rubocop
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '1.80'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '1.80'
98
153
  description: ' Provides "cached" finders and a DSL to enumerate which finders should
99
154
  have cached versions '
100
155
  email:
@@ -104,7 +159,10 @@ executables: []
104
159
  extensions: []
105
160
  extra_rdoc_files: []
106
161
  files:
162
+ - ".github/workflows/ci.yml"
107
163
  - ".gitignore"
164
+ - ".rubocop.yml"
165
+ - Appraisals
108
166
  - Gemfile
109
167
  - LICENSE.txt
110
168
  - README.md
@@ -122,11 +180,15 @@ files:
122
180
  - spec/active_remote/cached_exist_methods_spec.rb
123
181
  - spec/active_remote/cached_find_methods_spec.rb
124
182
  - spec/active_remote/cached_search_methods_spec.rb
183
+ - spec/active_remote/cached_spec.rb
125
184
  - spec/spec_helper.rb
126
- homepage: ''
127
- licenses: []
128
- metadata: {}
129
- post_install_message:
185
+ homepage: https://github.com/mxenabled/active_remote-cached
186
+ licenses:
187
+ - MIT
188
+ metadata:
189
+ homepage_uri: https://github.com/mxenabled/active_remote-cached
190
+ source_code_uri: https://github.com/mxenabled/active_remote-cached
191
+ rubygems_mfa_required: 'true'
130
192
  rdoc_options: []
131
193
  require_paths:
132
194
  - lib
@@ -134,23 +196,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
134
196
  requirements:
135
197
  - - ">="
136
198
  - !ruby/object:Gem::Version
137
- version: '0'
199
+ version: '3.1'
138
200
  required_rubygems_version: !ruby/object:Gem::Requirement
139
201
  requirements:
140
202
  - - ">="
141
203
  - !ruby/object:Gem::Version
142
204
  version: '0'
143
205
  requirements: []
144
- rubygems_version: 3.3.7
145
- signing_key:
206
+ rubygems_version: 3.6.9
146
207
  specification_version: 4
147
208
  summary: Provides a configuration for caching mechanisms and finders on ActiveRemote
148
- models that are cached/cacheable
149
- test_files:
150
- - spec/active_remote/cached/argument_keys_spec.rb
151
- - spec/active_remote/cached/cache_spec.rb
152
- - spec/active_remote/cached_delete_methods_spec.rb
153
- - spec/active_remote/cached_exist_methods_spec.rb
154
- - spec/active_remote/cached_find_methods_spec.rb
155
- - spec/active_remote/cached_search_methods_spec.rb
156
- - spec/spec_helper.rb
209
+ models
210
+ test_files: []