cuprum-collections 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +59 -0
  3. data/CODE_OF_CONDUCT.md +132 -0
  4. data/DEVELOPMENT.md +25 -0
  5. data/LICENSE +22 -0
  6. data/README.md +950 -0
  7. data/lib/cuprum/collections/base.rb +11 -0
  8. data/lib/cuprum/collections/basic/collection.rb +135 -0
  9. data/lib/cuprum/collections/basic/command.rb +112 -0
  10. data/lib/cuprum/collections/basic/commands/assign_one.rb +54 -0
  11. data/lib/cuprum/collections/basic/commands/build_one.rb +45 -0
  12. data/lib/cuprum/collections/basic/commands/destroy_one.rb +48 -0
  13. data/lib/cuprum/collections/basic/commands/find_many.rb +65 -0
  14. data/lib/cuprum/collections/basic/commands/find_matching.rb +126 -0
  15. data/lib/cuprum/collections/basic/commands/find_one.rb +49 -0
  16. data/lib/cuprum/collections/basic/commands/insert_one.rb +50 -0
  17. data/lib/cuprum/collections/basic/commands/update_one.rb +52 -0
  18. data/lib/cuprum/collections/basic/commands/validate_one.rb +69 -0
  19. data/lib/cuprum/collections/basic/commands.rb +18 -0
  20. data/lib/cuprum/collections/basic/query.rb +160 -0
  21. data/lib/cuprum/collections/basic/query_builder.rb +69 -0
  22. data/lib/cuprum/collections/basic/rspec/command_contract.rb +392 -0
  23. data/lib/cuprum/collections/basic/rspec.rb +8 -0
  24. data/lib/cuprum/collections/basic.rb +22 -0
  25. data/lib/cuprum/collections/command.rb +26 -0
  26. data/lib/cuprum/collections/commands/abstract_find_many.rb +77 -0
  27. data/lib/cuprum/collections/commands/abstract_find_matching.rb +64 -0
  28. data/lib/cuprum/collections/commands/abstract_find_one.rb +44 -0
  29. data/lib/cuprum/collections/commands.rb +8 -0
  30. data/lib/cuprum/collections/constraints/attribute_name.rb +22 -0
  31. data/lib/cuprum/collections/constraints/order/attributes_array.rb +26 -0
  32. data/lib/cuprum/collections/constraints/order/attributes_hash.rb +27 -0
  33. data/lib/cuprum/collections/constraints/order/complex_ordering.rb +46 -0
  34. data/lib/cuprum/collections/constraints/order/sort_direction.rb +32 -0
  35. data/lib/cuprum/collections/constraints/order.rb +8 -0
  36. data/lib/cuprum/collections/constraints/ordering.rb +114 -0
  37. data/lib/cuprum/collections/constraints/query_hash.rb +25 -0
  38. data/lib/cuprum/collections/constraints.rb +8 -0
  39. data/lib/cuprum/collections/errors/already_exists.rb +86 -0
  40. data/lib/cuprum/collections/errors/extra_attributes.rb +66 -0
  41. data/lib/cuprum/collections/errors/failed_validation.rb +66 -0
  42. data/lib/cuprum/collections/errors/invalid_parameters.rb +50 -0
  43. data/lib/cuprum/collections/errors/invalid_query.rb +55 -0
  44. data/lib/cuprum/collections/errors/missing_default_contract.rb +49 -0
  45. data/lib/cuprum/collections/errors/not_found.rb +81 -0
  46. data/lib/cuprum/collections/errors/unknown_operator.rb +71 -0
  47. data/lib/cuprum/collections/errors.rb +8 -0
  48. data/lib/cuprum/collections/queries/ordering.rb +74 -0
  49. data/lib/cuprum/collections/queries/parse.rb +22 -0
  50. data/lib/cuprum/collections/queries/parse_block.rb +206 -0
  51. data/lib/cuprum/collections/queries/parse_strategy.rb +91 -0
  52. data/lib/cuprum/collections/queries.rb +25 -0
  53. data/lib/cuprum/collections/query.rb +247 -0
  54. data/lib/cuprum/collections/query_builder.rb +61 -0
  55. data/lib/cuprum/collections/rspec/assign_one_command_contract.rb +168 -0
  56. data/lib/cuprum/collections/rspec/build_one_command_contract.rb +93 -0
  57. data/lib/cuprum/collections/rspec/collection_contract.rb +153 -0
  58. data/lib/cuprum/collections/rspec/destroy_one_command_contract.rb +106 -0
  59. data/lib/cuprum/collections/rspec/find_many_command_contract.rb +327 -0
  60. data/lib/cuprum/collections/rspec/find_matching_command_contract.rb +194 -0
  61. data/lib/cuprum/collections/rspec/find_one_command_contract.rb +154 -0
  62. data/lib/cuprum/collections/rspec/fixtures.rb +89 -0
  63. data/lib/cuprum/collections/rspec/insert_one_command_contract.rb +83 -0
  64. data/lib/cuprum/collections/rspec/query_builder_contract.rb +92 -0
  65. data/lib/cuprum/collections/rspec/query_contract.rb +650 -0
  66. data/lib/cuprum/collections/rspec/querying_contract.rb +298 -0
  67. data/lib/cuprum/collections/rspec/update_one_command_contract.rb +79 -0
  68. data/lib/cuprum/collections/rspec/validate_one_command_contract.rb +96 -0
  69. data/lib/cuprum/collections/rspec.rb +8 -0
  70. data/lib/cuprum/collections/version.rb +59 -0
  71. data/lib/cuprum/collections.rb +26 -0
  72. metadata +219 -0
@@ -0,0 +1,392 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections/basic/rspec'
4
+
5
+ module Cuprum::Collections::Basic::RSpec
6
+ # Contract validating the behavior of a basic command implementation.
7
+ COMMAND_CONTRACT = lambda do
8
+ describe '.subclass' do
9
+ let(:subclass) { described_class.subclass }
10
+ let(:constructor_options) do
11
+ {
12
+ collection_name: 'books',
13
+ data: data,
14
+ optional_key: 'optional value'
15
+ }
16
+ end
17
+
18
+ it 'should define the class method' do
19
+ expect(described_class)
20
+ .to respond_to(:subclass)
21
+ .with(0).arguments
22
+ .and_any_keywords
23
+ end
24
+
25
+ it { expect(subclass).to be_a Class }
26
+
27
+ it { expect(subclass).to be < described_class }
28
+
29
+ it 'should define the constructor' do
30
+ expect(subclass)
31
+ .to respond_to(:new)
32
+ .with(0).arguments
33
+ .and_any_keywords
34
+ end
35
+
36
+ it 'should return the collection name' do
37
+ expect(subclass.new(**constructor_options).collection_name)
38
+ .to be collection_name
39
+ end
40
+
41
+ it 'should return the data' do
42
+ expect(subclass.new(**constructor_options).data)
43
+ .to be data
44
+ end
45
+
46
+ it 'should return the options' do
47
+ expect(subclass.new(**constructor_options).options)
48
+ .to be == { optional_key: 'optional value' }
49
+ end
50
+
51
+ describe 'with options' do
52
+ let(:default_options) do
53
+ {
54
+ collection_name: 'books',
55
+ custom_key: 'custom value'
56
+ }
57
+ end
58
+ let(:constructor_options) do
59
+ {
60
+ data: data,
61
+ optional_key: 'optional value'
62
+ }
63
+ end
64
+ let(:subclass) { described_class.subclass(**default_options) }
65
+
66
+ it { expect(subclass).to be_a Class }
67
+
68
+ it { expect(subclass).to be < described_class }
69
+
70
+ it 'should define the constructor' do
71
+ expect(subclass)
72
+ .to respond_to(:new)
73
+ .with(0).arguments
74
+ .and_any_keywords
75
+ end
76
+
77
+ it 'should return the collection name' do
78
+ expect(subclass.new(**constructor_options).collection_name)
79
+ .to be collection_name
80
+ end
81
+
82
+ it 'should return the data' do
83
+ expect(subclass.new(**constructor_options).data)
84
+ .to be data
85
+ end
86
+
87
+ it 'should return the options' do
88
+ expect(subclass.new(**constructor_options).options)
89
+ .to be == {
90
+ custom_key: 'custom value',
91
+ optional_key: 'optional value'
92
+ }
93
+ end
94
+ end
95
+ end
96
+
97
+ describe '#collection_name' do
98
+ include_examples 'should have reader',
99
+ :collection_name,
100
+ -> { collection_name }
101
+
102
+ context 'when initialized with collection_name: symbol' do
103
+ let(:collection_name) { :books }
104
+
105
+ it { expect(command.collection_name).to be == collection_name.to_s }
106
+ end
107
+ end
108
+
109
+ describe '#data' do
110
+ include_examples 'should define reader', :data, -> { data }
111
+ end
112
+
113
+ describe '#default_contract' do
114
+ include_examples 'should define reader', :default_contract, nil
115
+
116
+ context 'when initialized with a default contract' do
117
+ let(:default_contract) { Stannum::Contract.new }
118
+ let(:constructor_options) do
119
+ super().merge(default_contract: default_contract)
120
+ end
121
+
122
+ it { expect(command.default_contract).to be default_contract }
123
+ end
124
+ end
125
+
126
+ describe '#member_name' do
127
+ def tools
128
+ SleepingKingStudios::Tools::Toolbelt.instance
129
+ end
130
+
131
+ include_examples 'should have reader',
132
+ :member_name,
133
+ -> { tools.str.singularize(collection_name) }
134
+
135
+ context 'when initialized with collection_name: value' do
136
+ let(:collection_name) { :books }
137
+
138
+ it 'should return the singular collection name' do
139
+ expect(command.member_name)
140
+ .to be == tools.str.singularize(collection_name.to_s)
141
+ end
142
+ end
143
+
144
+ context 'when initialized with member_name: string' do
145
+ let(:member_name) { 'tome' }
146
+ let(:constructor_options) { super().merge(member_name: member_name) }
147
+
148
+ it 'should return the singular collection name' do
149
+ expect(command.member_name).to be member_name
150
+ end
151
+ end
152
+
153
+ context 'when initialized with member_name: symbol' do
154
+ let(:member_name) { :tome }
155
+ let(:constructor_options) { super().merge(member_name: member_name) }
156
+
157
+ it 'should return the singular collection name' do
158
+ expect(command.member_name).to be == member_name.to_s
159
+ end
160
+ end
161
+ end
162
+
163
+ describe '#options' do
164
+ let(:expected_options) do
165
+ defined?(super()) ? super() : constructor_options
166
+ end
167
+
168
+ include_examples 'should define reader',
169
+ :options,
170
+ -> { be == expected_options }
171
+
172
+ context 'when initialized with options' do
173
+ let(:constructor_options) { super().merge({ key: 'value' }) }
174
+ let(:expected_options) { super().merge({ key: 'value' }) }
175
+
176
+ it { expect(command.options).to be == expected_options }
177
+ end
178
+ end
179
+
180
+ describe '#primary_key_name' do
181
+ include_examples 'should define reader', :primary_key_name, :id
182
+
183
+ context 'when initialized with a primary key name' do
184
+ let(:primary_key_name) { :uuid }
185
+ let(:constructor_options) do
186
+ super().merge({ primary_key_name: primary_key_name })
187
+ end
188
+
189
+ it { expect(command.primary_key_name).to be == primary_key_name }
190
+ end
191
+ end
192
+
193
+ describe '#primary_key_type' do
194
+ include_examples 'should define reader', :primary_key_type, Integer
195
+
196
+ context 'when initialized with a primary key type' do
197
+ let(:primary_key_type) { String }
198
+ let(:constructor_options) do
199
+ super().merge({ primary_key_type: primary_key_type })
200
+ end
201
+
202
+ it { expect(command.primary_key_type).to be == primary_key_type }
203
+ end
204
+ end
205
+
206
+ describe '#validate_primary_key' do
207
+ let(:primary_key_type) { Integer }
208
+ let(:expected_error) do
209
+ type = primary_key_type
210
+ contract = Stannum::Contracts::ParametersContract.new do
211
+ keyword :primary_key, type
212
+ end
213
+ errors = contract.errors_for(
214
+ {
215
+ arguments: [],
216
+ block: nil,
217
+ keywords: { primary_key: nil }
218
+ }
219
+ )
220
+
221
+ Cuprum::Collections::Errors::InvalidParameters.new(
222
+ command: command,
223
+ errors: errors
224
+ )
225
+ end
226
+
227
+ it 'should define the private method' do
228
+ expect(command)
229
+ .to respond_to(:validate_primary_key, true)
230
+ .with(1).argument
231
+ end
232
+
233
+ describe 'with nil' do
234
+ it 'should return a failing result' do
235
+ expect(command.send(:validate_primary_key, nil))
236
+ .to be_a_failing_result
237
+ .with_error(expected_error)
238
+ end
239
+ end
240
+
241
+ describe 'with an Object' do
242
+ it 'should return a failing result' do
243
+ expect(command.send(:validate_primary_key, Object.new.freeze))
244
+ .to be_a_failing_result
245
+ .with_error(expected_error)
246
+ end
247
+ end
248
+
249
+ describe 'with a String' do
250
+ it 'should return a failing result' do
251
+ expect(command.send(:validate_primary_key, '12345'))
252
+ .to be_a_failing_result
253
+ .with_error(expected_error)
254
+ end
255
+ end
256
+
257
+ describe 'with an Integer' do
258
+ it 'should not return a result' do
259
+ expect(command.send(:validate_primary_key, 12_345)).not_to be_a_result
260
+ end
261
+ end
262
+
263
+ context 'when initialized with a primary key type' do
264
+ let(:primary_key_type) { String }
265
+ let(:constructor_options) do
266
+ super().merge({ primary_key_type: primary_key_type })
267
+ end
268
+
269
+ describe 'with an Integer' do
270
+ it 'should return a failing result' do
271
+ expect(command.send(:validate_primary_key, 12_345))
272
+ .to be_a_failing_result
273
+ .with_error(expected_error)
274
+ end
275
+ end
276
+
277
+ describe 'with a String' do
278
+ it 'should not return a result' do
279
+ expect(command.send(:validate_primary_key, '12345'))
280
+ .not_to be_a_result
281
+ end
282
+ end
283
+ end
284
+ end
285
+
286
+ describe '#validate_primary_keys' do
287
+ let(:primary_keys) { nil }
288
+ let(:primary_key_type) { Integer }
289
+ let(:expected_error) do
290
+ type = primary_key_type
291
+ contract = Stannum::Contracts::ParametersContract.new do
292
+ keyword :primary_keys,
293
+ Stannum::Constraints::Types::ArrayType.new(item_type: type)
294
+ end
295
+ errors = contract.errors_for(
296
+ {
297
+ arguments: [],
298
+ block: nil,
299
+ keywords: { primary_keys: primary_keys }
300
+ }
301
+ )
302
+
303
+ Cuprum::Collections::Errors::InvalidParameters.new(
304
+ command: command,
305
+ errors: errors
306
+ )
307
+ end
308
+
309
+ it 'should define the private method' do
310
+ expect(command)
311
+ .to respond_to(:validate_primary_keys, true)
312
+ .with(1).argument
313
+ end
314
+
315
+ describe 'with nil' do
316
+ it 'should return a failing result' do
317
+ expect(command.send(:validate_primary_keys, nil))
318
+ .to be_a_failing_result
319
+ .with_error(expected_error)
320
+ end
321
+ end
322
+
323
+ describe 'with an Object' do
324
+ it 'should return a failing result' do
325
+ expect(command.send(:validate_primary_keys, Object.new.freeze))
326
+ .to be_a_failing_result
327
+ .with_error(expected_error)
328
+ end
329
+ end
330
+
331
+ describe 'with a String' do
332
+ it 'should return a failing result' do
333
+ expect(command.send(:validate_primary_keys, '12345'))
334
+ .to be_a_failing_result
335
+ .with_error(expected_error)
336
+ end
337
+ end
338
+
339
+ describe 'with an Integer' do
340
+ it 'should return a failing result' do
341
+ expect(command.send(:validate_primary_keys, 12_345))
342
+ .to be_a_failing_result
343
+ .with_error(expected_error)
344
+ end
345
+ end
346
+
347
+ describe 'with an empty Array' do
348
+ it 'should not return a result' do
349
+ expect(command.send(:validate_primary_keys, []))
350
+ .not_to be_a_result
351
+ end
352
+ end
353
+
354
+ describe 'with an Array with nil values' do
355
+ let(:primary_keys) { Array.new(3, nil) }
356
+
357
+ it 'should return a failing result' do
358
+ expect(command.send(:validate_primary_keys, primary_keys))
359
+ .to be_a_failing_result
360
+ .with_error(expected_error)
361
+ end
362
+ end
363
+
364
+ describe 'with an Array with Object values' do
365
+ let(:primary_keys) { Array.new(3) { Object.new.freeze } }
366
+
367
+ it 'should return a failing result' do
368
+ expect(command.send(:validate_primary_keys, primary_keys))
369
+ .to be_a_failing_result
370
+ .with_error(expected_error)
371
+ end
372
+ end
373
+
374
+ describe 'with an Array with String values' do
375
+ let(:primary_keys) { %w[ichi ni san] }
376
+
377
+ it 'should return a failing result' do
378
+ expect(command.send(:validate_primary_keys, primary_keys))
379
+ .to be_a_failing_result
380
+ .with_error(expected_error)
381
+ end
382
+ end
383
+
384
+ describe 'with an Array with Integer values' do
385
+ it 'should not return a result' do
386
+ expect(command.send(:validate_primary_keys, [0, 1, 2]))
387
+ .not_to be_a_result
388
+ end
389
+ end
390
+ end
391
+ end
392
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections/basic'
4
+
5
+ module Cuprum::Collections::Basic
6
+ # Namespace for RSpec contracts, which validate basic collections.
7
+ module RSpec; end
8
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections'
4
+
5
+ module Cuprum::Collections
6
+ # The Basic collection is an example, in-memory collection implementation.
7
+ module Basic
8
+ # Returns a new instance of Basic::Collection.
9
+ #
10
+ # @param options [Hash] Constructor options for the collection.
11
+ #
12
+ # @see Cuprum::Collections::Basic::Collection#initialize.
13
+ def self.new(**options)
14
+ Cuprum::Collections::Basic::Collection.new(**options)
15
+ end
16
+
17
+ autoload :Collection, 'cuprum/collections/basic/collection'
18
+ autoload :Command, 'cuprum/collections/basic/command'
19
+ autoload :Commands, 'cuprum/collections/basic/commands'
20
+ autoload :Query, 'cuprum/collections/basic/query'
21
+ end
22
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stannum/parameter_validation'
4
+
5
+ require 'cuprum/collections'
6
+ require 'cuprum/collections/errors/invalid_parameters'
7
+
8
+ module Cuprum::Collections
9
+ # Abstract base class for Cuprum::Collection commands.
10
+ class Command < Cuprum::Command
11
+ extend Stannum::ParameterValidation
12
+ include Stannum::ParameterValidation
13
+
14
+ private
15
+
16
+ def handle_invalid_parameters(errors:, method_name:)
17
+ return super unless method_name == :call
18
+
19
+ error = Cuprum::Collections::Errors::InvalidParameters.new(
20
+ command: self,
21
+ errors: errors
22
+ )
23
+ failure(error)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections/commands'
4
+ require 'cuprum/collections/errors/not_found'
5
+
6
+ module Cuprum::Collections::Commands
7
+ # Abstract implementation of the FindMany command.
8
+ #
9
+ # Subclasses must define the #build_query method, which returns an empty
10
+ # Query instance for that collection.
11
+ module AbstractFindMany
12
+ private
13
+
14
+ def apply_query(primary_keys:, scope:)
15
+ key = primary_key_name
16
+
17
+ (scope || build_query).where { { key => one_of(primary_keys) } }
18
+ end
19
+
20
+ def handle_missing_items(allow_partial:, items:, primary_keys:)
21
+ found, missing = match_items(items: items, primary_keys: primary_keys)
22
+
23
+ return found if missing.empty?
24
+
25
+ return found if allow_partial && !found.empty?
26
+
27
+ error = Cuprum::Collections::Errors::NotFound.new(
28
+ collection_name: collection_name,
29
+ primary_key_name: primary_key_name,
30
+ primary_key_values: missing
31
+ )
32
+ Cuprum::Result.new(error: error)
33
+ end
34
+
35
+ def items_with_primary_keys(items:)
36
+ # :nocov:
37
+ items.map { |item| [item.send(primary_key_name), item] }.to_h
38
+ # :nocov:
39
+ end
40
+
41
+ def match_items(items:, primary_keys:)
42
+ items = items_with_primary_keys(items: items)
43
+ found = []
44
+ missing = []
45
+
46
+ primary_keys.each do |key|
47
+ item = items[key]
48
+
49
+ item.nil? ? (missing << key) : (found << item)
50
+ end
51
+
52
+ [found, missing]
53
+ end
54
+
55
+ def process(
56
+ primary_keys:,
57
+ allow_partial: false,
58
+ envelope: false,
59
+ scope: nil
60
+ )
61
+ query = apply_query(primary_keys: primary_keys, scope: scope)
62
+ items = step do
63
+ handle_missing_items(
64
+ allow_partial: allow_partial,
65
+ items: query.to_a,
66
+ primary_keys: primary_keys
67
+ )
68
+ end
69
+
70
+ envelope ? wrap_items(items) : items
71
+ end
72
+
73
+ def wrap_items(items)
74
+ { collection_name => items }
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections/commands'
4
+ require 'cuprum/collections/queries/parse'
5
+
6
+ module Cuprum::Collections::Commands
7
+ # Abstract implementation of the FindMatching command.
8
+ #
9
+ # Subclasses must define the #build_query method, which returns an empty
10
+ # Query instance for that collection.
11
+ module AbstractFindMatching
12
+ private
13
+
14
+ def apply_query(criteria:, limit:, offset:, order:, scope:)
15
+ query = scope || build_query
16
+ query = query.limit(limit) if limit
17
+ query = query.offset(offset) if offset
18
+ query = query.order(order) if order
19
+ query = query.where(criteria, strategy: :unsafe) unless criteria.empty?
20
+
21
+ success(query)
22
+ end
23
+
24
+ def parse_criteria(strategy:, where:, &block)
25
+ return [] if strategy.nil? && where.nil? && !block_given?
26
+
27
+ Cuprum::Collections::Queries::Parse.new.call(
28
+ strategy: strategy,
29
+ where: where || block
30
+ )
31
+ end
32
+
33
+ def process( # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
34
+ envelope: false,
35
+ limit: nil,
36
+ offset: nil,
37
+ order: nil,
38
+ scope: nil,
39
+ strategy: nil,
40
+ where: nil,
41
+ &block
42
+ )
43
+ criteria = step do
44
+ parse_criteria(strategy: strategy, where: where, &block)
45
+ end
46
+
47
+ query = step do
48
+ apply_query(
49
+ criteria: criteria,
50
+ limit: limit,
51
+ offset: offset,
52
+ order: order,
53
+ scope: scope
54
+ )
55
+ end
56
+
57
+ envelope ? wrap_query(query) : query.each
58
+ end
59
+
60
+ def wrap_query(query)
61
+ { collection_name => query.to_a }
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections/commands'
4
+ require 'cuprum/collections/errors/not_found'
5
+
6
+ module Cuprum::Collections::Commands
7
+ # Abstract implementation of the FindOne command.
8
+ #
9
+ # Subclasses must define the #build_query method, which returns an empty
10
+ # Query instance for that collection.
11
+ module AbstractFindOne
12
+ private
13
+
14
+ def apply_query(primary_key:, scope:)
15
+ key = primary_key_name
16
+
17
+ (scope || build_query).where { { key => equals(primary_key) } }.limit(1)
18
+ end
19
+
20
+ def handle_missing_item(item:, primary_key:)
21
+ return if item
22
+
23
+ error = Cuprum::Collections::Errors::NotFound.new(
24
+ collection_name: collection_name,
25
+ primary_key_name: primary_key_name,
26
+ primary_key_values: [primary_key]
27
+ )
28
+ Cuprum::Result.new(error: error)
29
+ end
30
+
31
+ def process(envelope:, primary_key:, scope:)
32
+ query = apply_query(primary_key: primary_key, scope: scope)
33
+ item = query.to_a.first
34
+
35
+ step { handle_missing_item(item: item, primary_key: primary_key) }
36
+
37
+ envelope ? wrap_item(item) : item
38
+ end
39
+
40
+ def wrap_item(item)
41
+ { member_name => item }
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections'
4
+
5
+ module Cuprum::Collections
6
+ # Namespace for abstract commands implementing collection functionality.
7
+ module Commands; end
8
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stannum/constraints/hashes/indifferent_key'
4
+
5
+ require 'cuprum/collections/constraints'
6
+
7
+ module Cuprum::Collections::Constraints
8
+ # Asserts that the object is a non-empty String or Symbol.
9
+ class AttributeName < Stannum::Constraints::Hashes::IndifferentKey
10
+ # The :type of the error generated for a matching object.
11
+ NEGATED_TYPE = 'cuprum.collections.constraints.is_valid_attribute_name'
12
+
13
+ # The :type of the error generated for a non-matching object.
14
+ TYPE = 'cuprum.collections.constraints.is_not_valid_attribute_name'
15
+
16
+ # @return [Cuprum::Collections::Constraints::AttributeName] a cached
17
+ # instance of the constraint with default options.
18
+ def self.instance
19
+ @instance ||= new
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections/constraints/attribute_name'
4
+ require 'cuprum/collections/constraints/order'
5
+
6
+ require 'stannum/constraints/types/array_type'
7
+
8
+ module Cuprum::Collections::Constraints::Order
9
+ # Asserts that the object is an Array of attribute names.
10
+ class AttributesArray < Stannum::Constraints::Types::ArrayType
11
+ # @return [Cuprum::Collections::Constraints::Order::AttributesArray] a
12
+ # cached instance of the constraint with default options.
13
+ def self.instance
14
+ @instance ||= new
15
+ end
16
+
17
+ # @param options [Hash<Symbol, Object>] Configuration options for the
18
+ # constraint. Defaults to an empty Hash.
19
+ def initialize(**options)
20
+ super(
21
+ item_type: Cuprum::Collections::Constraints::AttributeName.instance,
22
+ **options
23
+ )
24
+ end
25
+ end
26
+ end