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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +59 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/DEVELOPMENT.md +25 -0
- data/LICENSE +22 -0
- data/README.md +950 -0
- data/lib/cuprum/collections/base.rb +11 -0
- data/lib/cuprum/collections/basic/collection.rb +135 -0
- data/lib/cuprum/collections/basic/command.rb +112 -0
- data/lib/cuprum/collections/basic/commands/assign_one.rb +54 -0
- data/lib/cuprum/collections/basic/commands/build_one.rb +45 -0
- data/lib/cuprum/collections/basic/commands/destroy_one.rb +48 -0
- data/lib/cuprum/collections/basic/commands/find_many.rb +65 -0
- data/lib/cuprum/collections/basic/commands/find_matching.rb +126 -0
- data/lib/cuprum/collections/basic/commands/find_one.rb +49 -0
- data/lib/cuprum/collections/basic/commands/insert_one.rb +50 -0
- data/lib/cuprum/collections/basic/commands/update_one.rb +52 -0
- data/lib/cuprum/collections/basic/commands/validate_one.rb +69 -0
- data/lib/cuprum/collections/basic/commands.rb +18 -0
- data/lib/cuprum/collections/basic/query.rb +160 -0
- data/lib/cuprum/collections/basic/query_builder.rb +69 -0
- data/lib/cuprum/collections/basic/rspec/command_contract.rb +392 -0
- data/lib/cuprum/collections/basic/rspec.rb +8 -0
- data/lib/cuprum/collections/basic.rb +22 -0
- data/lib/cuprum/collections/command.rb +26 -0
- data/lib/cuprum/collections/commands/abstract_find_many.rb +77 -0
- data/lib/cuprum/collections/commands/abstract_find_matching.rb +64 -0
- data/lib/cuprum/collections/commands/abstract_find_one.rb +44 -0
- data/lib/cuprum/collections/commands.rb +8 -0
- data/lib/cuprum/collections/constraints/attribute_name.rb +22 -0
- data/lib/cuprum/collections/constraints/order/attributes_array.rb +26 -0
- data/lib/cuprum/collections/constraints/order/attributes_hash.rb +27 -0
- data/lib/cuprum/collections/constraints/order/complex_ordering.rb +46 -0
- data/lib/cuprum/collections/constraints/order/sort_direction.rb +32 -0
- data/lib/cuprum/collections/constraints/order.rb +8 -0
- data/lib/cuprum/collections/constraints/ordering.rb +114 -0
- data/lib/cuprum/collections/constraints/query_hash.rb +25 -0
- data/lib/cuprum/collections/constraints.rb +8 -0
- data/lib/cuprum/collections/errors/already_exists.rb +86 -0
- data/lib/cuprum/collections/errors/extra_attributes.rb +66 -0
- data/lib/cuprum/collections/errors/failed_validation.rb +66 -0
- data/lib/cuprum/collections/errors/invalid_parameters.rb +50 -0
- data/lib/cuprum/collections/errors/invalid_query.rb +55 -0
- data/lib/cuprum/collections/errors/missing_default_contract.rb +49 -0
- data/lib/cuprum/collections/errors/not_found.rb +81 -0
- data/lib/cuprum/collections/errors/unknown_operator.rb +71 -0
- data/lib/cuprum/collections/errors.rb +8 -0
- data/lib/cuprum/collections/queries/ordering.rb +74 -0
- data/lib/cuprum/collections/queries/parse.rb +22 -0
- data/lib/cuprum/collections/queries/parse_block.rb +206 -0
- data/lib/cuprum/collections/queries/parse_strategy.rb +91 -0
- data/lib/cuprum/collections/queries.rb +25 -0
- data/lib/cuprum/collections/query.rb +247 -0
- data/lib/cuprum/collections/query_builder.rb +61 -0
- data/lib/cuprum/collections/rspec/assign_one_command_contract.rb +168 -0
- data/lib/cuprum/collections/rspec/build_one_command_contract.rb +93 -0
- data/lib/cuprum/collections/rspec/collection_contract.rb +153 -0
- data/lib/cuprum/collections/rspec/destroy_one_command_contract.rb +106 -0
- data/lib/cuprum/collections/rspec/find_many_command_contract.rb +327 -0
- data/lib/cuprum/collections/rspec/find_matching_command_contract.rb +194 -0
- data/lib/cuprum/collections/rspec/find_one_command_contract.rb +154 -0
- data/lib/cuprum/collections/rspec/fixtures.rb +89 -0
- data/lib/cuprum/collections/rspec/insert_one_command_contract.rb +83 -0
- data/lib/cuprum/collections/rspec/query_builder_contract.rb +92 -0
- data/lib/cuprum/collections/rspec/query_contract.rb +650 -0
- data/lib/cuprum/collections/rspec/querying_contract.rb +298 -0
- data/lib/cuprum/collections/rspec/update_one_command_contract.rb +79 -0
- data/lib/cuprum/collections/rspec/validate_one_command_contract.rb +96 -0
- data/lib/cuprum/collections/rspec.rb +8 -0
- data/lib/cuprum/collections/version.rb +59 -0
- data/lib/cuprum/collections.rb +26 -0
- 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,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,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
|