cuprum-collections 0.1.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 (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