cuprum-collections 0.3.0 → 0.4.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +48 -0
  3. data/DEVELOPMENT.md +2 -2
  4. data/README.md +13 -11
  5. data/lib/cuprum/collections/association.rb +256 -0
  6. data/lib/cuprum/collections/associations/belongs_to.rb +32 -0
  7. data/lib/cuprum/collections/associations/has_many.rb +23 -0
  8. data/lib/cuprum/collections/associations/has_one.rb +23 -0
  9. data/lib/cuprum/collections/associations.rb +10 -0
  10. data/lib/cuprum/collections/basic/collection.rb +39 -74
  11. data/lib/cuprum/collections/basic/commands/find_many.rb +1 -1
  12. data/lib/cuprum/collections/basic/commands/find_matching.rb +1 -1
  13. data/lib/cuprum/collections/basic/repository.rb +9 -33
  14. data/lib/cuprum/collections/basic.rb +1 -0
  15. data/lib/cuprum/collections/collection.rb +154 -0
  16. data/lib/cuprum/collections/commands/associations/find_many.rb +161 -0
  17. data/lib/cuprum/collections/commands/associations/require_many.rb +48 -0
  18. data/lib/cuprum/collections/commands/associations.rb +13 -0
  19. data/lib/cuprum/collections/commands/find_one_matching.rb +1 -1
  20. data/lib/cuprum/collections/commands.rb +1 -0
  21. data/lib/cuprum/collections/errors/abstract_find_error.rb +1 -1
  22. data/lib/cuprum/collections/relation.rb +401 -0
  23. data/lib/cuprum/collections/repository.rb +71 -4
  24. data/lib/cuprum/collections/resource.rb +65 -0
  25. data/lib/cuprum/collections/rspec/contracts/association_contracts.rb +2137 -0
  26. data/lib/cuprum/collections/rspec/contracts/basic/command_contracts.rb +484 -0
  27. data/lib/cuprum/collections/rspec/contracts/basic.rb +11 -0
  28. data/lib/cuprum/collections/rspec/contracts/collection_contracts.rb +429 -0
  29. data/lib/cuprum/collections/rspec/contracts/command_contracts.rb +1462 -0
  30. data/lib/cuprum/collections/rspec/contracts/query_contracts.rb +1093 -0
  31. data/lib/cuprum/collections/rspec/contracts/relation_contracts.rb +1381 -0
  32. data/lib/cuprum/collections/rspec/contracts/repository_contracts.rb +605 -0
  33. data/lib/cuprum/collections/rspec/contracts.rb +23 -0
  34. data/lib/cuprum/collections/rspec/fixtures.rb +85 -82
  35. data/lib/cuprum/collections/rspec.rb +4 -1
  36. data/lib/cuprum/collections/version.rb +1 -1
  37. data/lib/cuprum/collections.rb +9 -4
  38. metadata +23 -19
  39. data/lib/cuprum/collections/base.rb +0 -11
  40. data/lib/cuprum/collections/basic/rspec/command_contract.rb +0 -392
  41. data/lib/cuprum/collections/rspec/assign_one_command_contract.rb +0 -168
  42. data/lib/cuprum/collections/rspec/build_one_command_contract.rb +0 -93
  43. data/lib/cuprum/collections/rspec/collection_contract.rb +0 -190
  44. data/lib/cuprum/collections/rspec/destroy_one_command_contract.rb +0 -108
  45. data/lib/cuprum/collections/rspec/find_many_command_contract.rb +0 -407
  46. data/lib/cuprum/collections/rspec/find_matching_command_contract.rb +0 -194
  47. data/lib/cuprum/collections/rspec/find_one_command_contract.rb +0 -157
  48. data/lib/cuprum/collections/rspec/insert_one_command_contract.rb +0 -84
  49. data/lib/cuprum/collections/rspec/query_builder_contract.rb +0 -92
  50. data/lib/cuprum/collections/rspec/query_contract.rb +0 -650
  51. data/lib/cuprum/collections/rspec/querying_contract.rb +0 -298
  52. data/lib/cuprum/collections/rspec/repository_contract.rb +0 -235
  53. data/lib/cuprum/collections/rspec/update_one_command_contract.rb +0 -80
  54. data/lib/cuprum/collections/rspec/validate_one_command_contract.rb +0 -96
@@ -0,0 +1,484 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections/rspec/contracts/basic'
4
+
5
+ module Cuprum::Collections::RSpec::Contracts::Basic
6
+ # Contracts for asserting on Basic::Command objects.
7
+ module CommandContracts
8
+ # Contract validating the behavior of a basic command implementation.
9
+ module ShouldBeABasicCommandContract
10
+ extend RSpec::SleepingKingStudios::Contract
11
+
12
+ # @!method apply(example_group)
13
+ # Adds the contract to the example group.
14
+ #
15
+ # @param example_group [RSpec::Core::ExampleGroup] the example group to
16
+ # which the contract is applied.
17
+ contract do
18
+ describe '.subclass' do
19
+ let(:subclass) { described_class.subclass }
20
+ let(:constructor_options) do
21
+ {
22
+ collection_name: 'books',
23
+ data: data,
24
+ optional_key: 'optional value'
25
+ }
26
+ end
27
+
28
+ it 'should define the class method' do
29
+ expect(described_class)
30
+ .to respond_to(:subclass)
31
+ .with(0).arguments
32
+ .and_any_keywords
33
+ end
34
+
35
+ it { expect(subclass).to be_a Class }
36
+
37
+ it { expect(subclass).to be < described_class }
38
+
39
+ it 'should define the constructor' do
40
+ expect(subclass)
41
+ .to respond_to(:new)
42
+ .with(0).arguments
43
+ .and_any_keywords
44
+ end
45
+
46
+ it 'should return the collection name' do
47
+ expect(subclass.new(**constructor_options).collection_name)
48
+ .to be collection_name
49
+ end
50
+
51
+ it 'should return the data' do
52
+ expect(subclass.new(**constructor_options).data)
53
+ .to be data
54
+ end
55
+
56
+ it 'should return the options' do
57
+ expect(subclass.new(**constructor_options).options)
58
+ .to be == { optional_key: 'optional value' }
59
+ end
60
+
61
+ describe 'with options' do
62
+ let(:default_options) do
63
+ {
64
+ collection_name: 'books',
65
+ custom_key: 'custom value'
66
+ }
67
+ end
68
+ let(:constructor_options) do
69
+ {
70
+ data: data,
71
+ optional_key: 'optional value'
72
+ }
73
+ end
74
+ let(:subclass) { described_class.subclass(**default_options) }
75
+
76
+ it { expect(subclass).to be_a Class }
77
+
78
+ it { expect(subclass).to be < described_class }
79
+
80
+ it 'should define the constructor' do
81
+ expect(subclass)
82
+ .to respond_to(:new)
83
+ .with(0).arguments
84
+ .and_any_keywords
85
+ end
86
+
87
+ it 'should return the collection name' do
88
+ expect(subclass.new(**constructor_options).collection_name)
89
+ .to be collection_name
90
+ end
91
+
92
+ it 'should return the data' do
93
+ expect(subclass.new(**constructor_options).data)
94
+ .to be data
95
+ end
96
+
97
+ it 'should return the options' do
98
+ expect(subclass.new(**constructor_options).options)
99
+ .to be == {
100
+ custom_key: 'custom value',
101
+ optional_key: 'optional value'
102
+ }
103
+ end
104
+ end
105
+ end
106
+
107
+ describe '#collection_name' do
108
+ include_examples 'should have reader',
109
+ :collection_name,
110
+ -> { collection_name }
111
+
112
+ context 'when initialized with collection_name: symbol' do
113
+ let(:collection_name) { :books }
114
+
115
+ it { expect(command.collection_name).to be == collection_name.to_s }
116
+ end
117
+ end
118
+
119
+ describe '#data' do
120
+ include_examples 'should define reader', :data, -> { data }
121
+ end
122
+
123
+ describe '#default_contract' do
124
+ include_examples 'should define reader', :default_contract, nil
125
+
126
+ context 'when initialized with a default contract' do
127
+ let(:default_contract) { Stannum::Contract.new }
128
+ let(:constructor_options) do
129
+ super().merge(default_contract: default_contract)
130
+ end
131
+
132
+ it { expect(command.default_contract).to be default_contract }
133
+ end
134
+ end
135
+
136
+ describe '#member_name' do
137
+ def tools
138
+ SleepingKingStudios::Tools::Toolbelt.instance
139
+ end
140
+
141
+ include_examples 'should have reader',
142
+ :member_name,
143
+ -> { tools.str.singularize(collection_name) }
144
+
145
+ context 'when initialized with collection_name: value' do
146
+ let(:collection_name) { :books }
147
+
148
+ it 'should return the singular collection name' do
149
+ expect(command.member_name)
150
+ .to be == tools.str.singularize(collection_name.to_s)
151
+ end
152
+ end
153
+
154
+ context 'when initialized with member_name: string' do
155
+ let(:member_name) { 'tome' }
156
+ let(:constructor_options) do
157
+ super().merge(member_name: member_name)
158
+ end
159
+
160
+ it 'should return the singular collection name' do
161
+ expect(command.member_name).to be member_name
162
+ end
163
+ end
164
+
165
+ context 'when initialized with member_name: symbol' do
166
+ let(:member_name) { :tome }
167
+ let(:constructor_options) do
168
+ super().merge(member_name: member_name)
169
+ end
170
+
171
+ it 'should return the singular collection name' do
172
+ expect(command.member_name).to be == member_name.to_s
173
+ end
174
+ end
175
+ end
176
+
177
+ describe '#options' do
178
+ let(:expected_options) do
179
+ defined?(super()) ? super() : constructor_options
180
+ end
181
+
182
+ include_examples 'should define reader',
183
+ :options,
184
+ -> { be == expected_options }
185
+
186
+ context 'when initialized with options' do
187
+ let(:constructor_options) { super().merge({ key: 'value' }) }
188
+ let(:expected_options) { super().merge({ key: 'value' }) }
189
+
190
+ it { expect(command.options).to be == expected_options }
191
+ end
192
+ end
193
+
194
+ describe '#primary_key_name' do
195
+ include_examples 'should define reader', :primary_key_name, :id
196
+
197
+ context 'when initialized with a primary key name' do
198
+ let(:primary_key_name) { :uuid }
199
+ let(:constructor_options) do
200
+ super().merge({ primary_key_name: primary_key_name })
201
+ end
202
+
203
+ it { expect(command.primary_key_name).to be == primary_key_name }
204
+ end
205
+ end
206
+
207
+ describe '#primary_key_type' do
208
+ include_examples 'should define reader', :primary_key_type, Integer
209
+
210
+ context 'when initialized with a primary key type' do
211
+ let(:primary_key_type) { String }
212
+ let(:constructor_options) do
213
+ super().merge({ primary_key_type: primary_key_type })
214
+ end
215
+
216
+ it { expect(command.primary_key_type).to be == primary_key_type }
217
+ end
218
+ end
219
+
220
+ describe '#validate_primary_key' do
221
+ let(:primary_key_type) { Integer }
222
+ let(:expected_error) do
223
+ type = primary_key_type
224
+ contract = Stannum::Contracts::ParametersContract.new do
225
+ keyword :primary_key, type
226
+ end
227
+ errors = contract.errors_for(
228
+ {
229
+ arguments: [],
230
+ block: nil,
231
+ keywords: { primary_key: nil }
232
+ }
233
+ )
234
+
235
+ Cuprum::Collections::Errors::InvalidParameters.new(
236
+ command: command,
237
+ errors: errors
238
+ )
239
+ end
240
+
241
+ it 'should define the private method' do
242
+ expect(command)
243
+ .to respond_to(:validate_primary_key, true)
244
+ .with(1).argument
245
+ end
246
+
247
+ describe 'with nil' do
248
+ it 'should return a failing result' do
249
+ expect(command.send(:validate_primary_key, nil))
250
+ .to be_a_failing_result
251
+ .with_error(expected_error)
252
+ end
253
+ end
254
+
255
+ describe 'with an Object' do
256
+ it 'should return a failing result' do
257
+ expect(command.send(:validate_primary_key, Object.new.freeze))
258
+ .to be_a_failing_result
259
+ .with_error(expected_error)
260
+ end
261
+ end
262
+
263
+ describe 'with a String' do
264
+ it 'should return a failing result' do
265
+ expect(command.send(:validate_primary_key, '12345'))
266
+ .to be_a_failing_result
267
+ .with_error(expected_error)
268
+ end
269
+ end
270
+
271
+ describe 'with an Integer' do
272
+ it 'should not return a result' do
273
+ expect(command.send(:validate_primary_key, 12_345))
274
+ .not_to be_a_result
275
+ end
276
+ end
277
+
278
+ context 'when initialized with a primary key type' do
279
+ let(:primary_key_type) { String }
280
+ let(:constructor_options) do
281
+ super().merge({ primary_key_type: primary_key_type })
282
+ end
283
+
284
+ describe 'with an Integer' do
285
+ it 'should return a failing result' do
286
+ expect(command.send(:validate_primary_key, 12_345))
287
+ .to be_a_failing_result
288
+ .with_error(expected_error)
289
+ end
290
+ end
291
+
292
+ describe 'with a String' do
293
+ it 'should not return a result' do
294
+ expect(command.send(:validate_primary_key, '12345'))
295
+ .not_to be_a_result
296
+ end
297
+ end
298
+ end
299
+ end
300
+
301
+ describe '#validate_primary_keys' do
302
+ let(:primary_keys) { nil }
303
+ let(:primary_key_type) { Integer }
304
+ let(:expected_error) do
305
+ type = primary_key_type
306
+ contract = Stannum::Contracts::ParametersContract.new do
307
+ keyword :primary_keys,
308
+ Stannum::Constraints::Types::ArrayType.new(item_type: type)
309
+ end
310
+ errors = contract.errors_for(
311
+ {
312
+ arguments: [],
313
+ block: nil,
314
+ keywords: { primary_keys: primary_keys }
315
+ }
316
+ )
317
+
318
+ Cuprum::Collections::Errors::InvalidParameters.new(
319
+ command: command,
320
+ errors: errors
321
+ )
322
+ end
323
+
324
+ it 'should define the private method' do
325
+ expect(command)
326
+ .to respond_to(:validate_primary_keys, true)
327
+ .with(1).argument
328
+ end
329
+
330
+ describe 'with nil' do
331
+ it 'should return a failing result' do
332
+ expect(command.send(:validate_primary_keys, nil))
333
+ .to be_a_failing_result
334
+ .with_error(expected_error)
335
+ end
336
+ end
337
+
338
+ describe 'with an Object' do
339
+ it 'should return a failing result' do
340
+ expect(command.send(:validate_primary_keys, Object.new.freeze))
341
+ .to be_a_failing_result
342
+ .with_error(expected_error)
343
+ end
344
+ end
345
+
346
+ describe 'with a String' do
347
+ it 'should return a failing result' do
348
+ expect(command.send(:validate_primary_keys, '12345'))
349
+ .to be_a_failing_result
350
+ .with_error(expected_error)
351
+ end
352
+ end
353
+
354
+ describe 'with an Integer' do
355
+ it 'should return a failing result' do
356
+ expect(command.send(:validate_primary_keys, 12_345))
357
+ .to be_a_failing_result
358
+ .with_error(expected_error)
359
+ end
360
+ end
361
+
362
+ describe 'with an empty Array' do
363
+ it 'should not return a result' do
364
+ expect(command.send(:validate_primary_keys, []))
365
+ .not_to be_a_result
366
+ end
367
+ end
368
+
369
+ describe 'with an Array with nil values' do
370
+ let(:primary_keys) { Array.new(3, nil) }
371
+
372
+ it 'should return a failing result' do
373
+ expect(command.send(:validate_primary_keys, primary_keys))
374
+ .to be_a_failing_result
375
+ .with_error(expected_error)
376
+ end
377
+ end
378
+
379
+ describe 'with an Array with Object values' do
380
+ let(:primary_keys) { Array.new(3) { Object.new.freeze } }
381
+
382
+ it 'should return a failing result' do
383
+ expect(command.send(:validate_primary_keys, primary_keys))
384
+ .to be_a_failing_result
385
+ .with_error(expected_error)
386
+ end
387
+ end
388
+
389
+ describe 'with an Array with String values' do
390
+ let(:primary_keys) { %w[ichi ni san] }
391
+
392
+ it 'should return a failing result' do
393
+ expect(command.send(:validate_primary_keys, primary_keys))
394
+ .to be_a_failing_result
395
+ .with_error(expected_error)
396
+ end
397
+ end
398
+
399
+ describe 'with an Array with Integer values' do
400
+ it 'should not return a result' do
401
+ expect(command.send(:validate_primary_keys, [0, 1, 2]))
402
+ .not_to be_a_result
403
+ end
404
+ end
405
+ end
406
+ end
407
+ end
408
+
409
+ # Contract defining contexts for validating basic commands.
410
+ module WithBasicCommandContextsContract
411
+ extend RSpec::SleepingKingStudios::Contract
412
+
413
+ # @!method apply(example_group)
414
+ # Adds the contract to the example group.
415
+ #
416
+ # @param example_group [RSpec::Core::ExampleGroup] the example group to
417
+ # which the contract is applied.
418
+ contract do
419
+ shared_context 'with parameters for a basic contract' do
420
+ let(:collection_name) { 'books' }
421
+ let(:data) { [] }
422
+ let(:mapped_data) { data }
423
+ let(:constructor_options) { {} }
424
+ let(:expected_options) { {} }
425
+ let(:primary_key_name) { :id }
426
+ let(:primary_key_type) { Integer }
427
+ let(:entity_type) do
428
+ Stannum::Constraints::Types::HashWithStringKeys.new
429
+ end
430
+ let(:fixtures_data) do
431
+ Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES.dup
432
+ end
433
+ let(:query) do
434
+ Cuprum::Collections::Basic::Query.new(mapped_data)
435
+ end
436
+ let(:scope) do
437
+ Cuprum::Collections::Basic::Query
438
+ .new(mapped_data).where(scope_filter)
439
+ end
440
+ end
441
+
442
+ shared_context 'with a custom primary key' do
443
+ let(:primary_key_name) { :uuid }
444
+ let(:primary_key_type) { String }
445
+ let(:constructor_options) do
446
+ super().merge(
447
+ primary_key_name: primary_key_name,
448
+ primary_key_type: primary_key_type
449
+ )
450
+ end
451
+ let(:mapped_data) do
452
+ data.map do |item|
453
+ item.dup.tap do |hsh|
454
+ value = hsh.delete('id').to_s.rjust(12, '0')
455
+
456
+ hsh['uuid'] = "00000000-0000-0000-0000-#{value}"
457
+ end
458
+ end
459
+ end
460
+ let(:invalid_primary_key_value) do
461
+ '00000000-0000-0000-0000-000000000100'
462
+ end
463
+ let(:valid_primary_key_value) do
464
+ '00000000-0000-0000-0000-000000000000'
465
+ end
466
+ let(:invalid_primary_key_values) do
467
+ %w[
468
+ 00000000-0000-0000-0000-000000000100
469
+ 00000000-0000-0000-0000-000000000101
470
+ 00000000-0000-0000-0000-000000000102
471
+ ]
472
+ end
473
+ let(:valid_primary_key_values) do
474
+ %w[
475
+ 00000000-0000-0000-0000-000000000000
476
+ 00000000-0000-0000-0000-000000000001
477
+ 00000000-0000-0000-0000-000000000002
478
+ ]
479
+ end
480
+ end
481
+ end
482
+ end
483
+ end
484
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections/rspec/contracts'
4
+
5
+ module Cuprum::Collections::RSpec::Contracts
6
+ # Namespace for RSpec contract objects for Basic collections.
7
+ module Basic
8
+ autoload :CommandContracts,
9
+ 'cuprum/collections/rspec/contracts/basic/command_contracts'
10
+ end
11
+ end