cuprum-collections 0.5.1 → 0.6.0.rc.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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +47 -0
  3. data/lib/cuprum/collections/adaptable/collection.rb +18 -0
  4. data/lib/cuprum/collections/adaptable/command.rb +22 -0
  5. data/lib/cuprum/collections/adaptable/commands/abstract_assign_one.rb +27 -0
  6. data/lib/cuprum/collections/adaptable/commands/abstract_build_one.rb +25 -0
  7. data/lib/cuprum/collections/adaptable/commands/abstract_validate_one.rb +35 -0
  8. data/lib/cuprum/collections/adaptable/commands.rb +15 -0
  9. data/lib/cuprum/collections/adaptable/query.rb +64 -0
  10. data/lib/cuprum/collections/adaptable.rb +13 -0
  11. data/lib/cuprum/collections/adapter.rb +300 -0
  12. data/lib/cuprum/collections/adapters/data_adapter.rb +82 -0
  13. data/lib/cuprum/collections/adapters/entity_adapter.rb +76 -0
  14. data/lib/cuprum/collections/adapters/hash_adapter.rb +48 -0
  15. data/lib/cuprum/collections/adapters.rb +14 -0
  16. data/lib/cuprum/collections/basic/collection.rb +2 -20
  17. data/lib/cuprum/collections/basic/commands/destroy_one.rb +1 -1
  18. data/lib/cuprum/collections/basic/commands/find_many.rb +0 -31
  19. data/lib/cuprum/collections/basic/commands/find_matching.rb +0 -94
  20. data/lib/cuprum/collections/basic/commands/find_one.rb +0 -18
  21. data/lib/cuprum/collections/basic/commands/insert_one.rb +1 -1
  22. data/lib/cuprum/collections/basic/commands/update_one.rb +1 -1
  23. data/lib/cuprum/collections/basic/scopes/criteria_scope.rb +36 -21
  24. data/lib/cuprum/collections/basic.rb +6 -5
  25. data/lib/cuprum/collections/collection.rb +6 -0
  26. data/lib/cuprum/collections/collection_command.rb +1 -1
  27. data/lib/cuprum/collections/commands/abstract_find_many.rb +40 -3
  28. data/lib/cuprum/collections/commands/abstract_find_matching.rb +102 -0
  29. data/lib/cuprum/collections/commands/abstract_find_one.rb +23 -1
  30. data/lib/cuprum/collections/commands/associations/find_many.rb +1 -3
  31. data/lib/cuprum/collections/commands/associations/require_many.rb +1 -1
  32. data/lib/cuprum/collections/commands/find_one_matching.rb +10 -10
  33. data/lib/cuprum/collections/commands/query_command.rb +6 -4
  34. data/lib/cuprum/collections/commands/upsert.rb +0 -2
  35. data/lib/cuprum/collections/constraints/order/attributes_array.rb +5 -4
  36. data/lib/cuprum/collections/constraints/order/attributes_hash.rb +5 -4
  37. data/lib/cuprum/collections/constraints/order/sort_direction.rb +2 -2
  38. data/lib/cuprum/collections/constraints/ordering.rb +11 -9
  39. data/lib/cuprum/collections/constraints/query_hash.rb +2 -2
  40. data/lib/cuprum/collections/errors/abstract_find_error.rb +101 -23
  41. data/lib/cuprum/collections/errors/extra_attributes.rb +3 -3
  42. data/lib/cuprum/collections/errors/failed_validation.rb +3 -3
  43. data/lib/cuprum/collections/errors/missing_default_contract.rb +12 -4
  44. data/lib/cuprum/collections/queries.rb +4 -0
  45. data/lib/cuprum/collections/relation.rb +0 -2
  46. data/lib/cuprum/collections/relations/parameters.rb +120 -68
  47. data/lib/cuprum/collections/repository.rb +71 -6
  48. data/lib/cuprum/collections/rspec/contracts/query_contracts.rb +23 -4
  49. data/lib/cuprum/collections/rspec/contracts/repository_contracts.rb +18 -0
  50. data/lib/cuprum/collections/rspec/contracts/scope_contracts.rb +51 -0
  51. data/lib/cuprum/collections/rspec/contracts/scopes/builder_contracts.rb +10 -0
  52. data/lib/cuprum/collections/rspec/contracts/scopes/composition_contracts.rb +8 -0
  53. data/lib/cuprum/collections/rspec/contracts/scopes/criteria_contracts.rb +18 -366
  54. data/lib/cuprum/collections/rspec/contracts/scopes/logical_contracts.rb +30 -0
  55. data/lib/cuprum/collections/rspec/contracts/scopes.rb +2 -0
  56. data/lib/cuprum/collections/rspec/contracts.rb +2 -10
  57. data/lib/cuprum/collections/rspec/deferred/adapter_examples.rb +1077 -0
  58. data/lib/cuprum/collections/rspec/deferred/collection_examples.rb +27 -7
  59. data/lib/cuprum/collections/rspec/deferred/commands/assign_one_examples.rb +4 -4
  60. data/lib/cuprum/collections/rspec/deferred/commands/build_one_examples.rb +2 -2
  61. data/lib/cuprum/collections/rspec/deferred/commands/destroy_one_examples.rb +2 -2
  62. data/lib/cuprum/collections/rspec/deferred/commands/find_many_examples.rb +5 -5
  63. data/lib/cuprum/collections/rspec/deferred/commands/find_matching_examples.rb +45 -12
  64. data/lib/cuprum/collections/rspec/deferred/commands/find_one_examples.rb +2 -2
  65. data/lib/cuprum/collections/rspec/deferred/commands/insert_one_examples.rb +1 -1
  66. data/lib/cuprum/collections/rspec/deferred/commands/update_one_examples.rb +1 -1
  67. data/lib/cuprum/collections/rspec/deferred/query_examples.rb +930 -0
  68. data/lib/cuprum/collections/rspec/deferred/relation_examples.rb +48 -17
  69. data/lib/cuprum/collections/rspec/deferred/repository_examples.rb +961 -0
  70. data/lib/cuprum/collections/rspec/deferred/scope_examples.rb +598 -0
  71. data/lib/cuprum/collections/rspec/deferred/scopes/all_examples.rb +391 -0
  72. data/lib/cuprum/collections/rspec/deferred/scopes/builder_examples.rb +857 -0
  73. data/lib/cuprum/collections/rspec/deferred/scopes/composition_examples.rb +93 -0
  74. data/lib/cuprum/collections/rspec/deferred/scopes/conjunction_examples.rb +438 -0
  75. data/lib/cuprum/collections/rspec/deferred/scopes/criteria_examples.rb +1941 -0
  76. data/lib/cuprum/collections/rspec/deferred/scopes/disjunction_examples.rb +415 -0
  77. data/lib/cuprum/collections/rspec/deferred/scopes/none_examples.rb +385 -0
  78. data/lib/cuprum/collections/rspec/deferred/scopes/parser_examples.rb +740 -0
  79. data/lib/cuprum/collections/rspec/deferred/scopes.rb +8 -0
  80. data/lib/cuprum/collections/scope.rb +2 -2
  81. data/lib/cuprum/collections/scopes/container.rb +5 -4
  82. data/lib/cuprum/collections/scopes/criteria/parser.rb +24 -48
  83. data/lib/cuprum/collections/scopes/criteria.rb +7 -6
  84. data/lib/cuprum/collections/version.rb +4 -4
  85. data/lib/cuprum/collections.rb +5 -1
  86. metadata +47 -11
  87. data/lib/cuprum/collections/rspec/contracts/association_contracts.rb +0 -2127
  88. data/lib/cuprum/collections/rspec/contracts/basic.rb +0 -11
  89. data/lib/cuprum/collections/rspec/contracts/collection_contracts.rb +0 -387
  90. data/lib/cuprum/collections/rspec/contracts/command_contracts.rb +0 -169
  91. data/lib/cuprum/collections/rspec/contracts/relation_contracts.rb +0 -1264
@@ -0,0 +1,857 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/sleeping_king_studios/deferred'
4
+
5
+ require 'cuprum/collections/rspec/deferred/scopes'
6
+ require 'cuprum/collections/rspec/deferred/scopes/parser_examples'
7
+
8
+ module Cuprum::Collections::RSpec::Deferred::Scopes
9
+ # Deferred examples for asserting on scope builders.
10
+ module BuilderExamples
11
+ include RSpec::SleepingKingStudios::Deferred::Provider
12
+ include Cuprum::Collections::RSpec::Deferred::Scopes::ParserExamples
13
+
14
+ deferred_examples 'should build collection Scopes' do |**deferred_options|
15
+ all_scope_class = deferred_options.fetch(:all_class) do
16
+ deferred_options[:namespace]&.const_get(:AllScope)
17
+ end
18
+ conjunction_scope_class = deferred_options.fetch(:conjunction_class) do
19
+ deferred_options[:namespace]&.const_get(:ConjunctionScope)
20
+ end
21
+ criteria_scope_class = deferred_options.fetch(:criteria_class) do
22
+ deferred_options[:namespace]&.const_get(:CriteriaScope)
23
+ end
24
+ disjunction_scope_class = deferred_options.fetch(:disjunction_class) do
25
+ deferred_options[:namespace]&.const_get(:DisjunctionScope)
26
+ end
27
+ none_scope_class = deferred_options.fetch(:none_class) do
28
+ deferred_options[:namespace]&.const_get(:NoneScope)
29
+ end
30
+
31
+ deferred_context 'with container scope helpers' do
32
+ let(:scope) { build_container(scopes:) }
33
+
34
+ # :nocov:
35
+ define_method :expected_class_for do |type|
36
+ case type
37
+ when :all then all_scope_class
38
+ when :conjunction then conjunction_scope_class
39
+ when :criteria then criteria_scope_class
40
+ when :disjunction then disjunction_scope_class
41
+ when :none then none_scope_class
42
+ else
43
+ raise "unknown scope type #{type.inspect}"
44
+ end
45
+ end
46
+
47
+ # rubocop:disable Metrics/AbcSize
48
+ define_method :should_recursively_convert_scopes \
49
+ do |original_scopes, converted|
50
+ original_scopes.zip(converted).each do |original, scope|
51
+ expect(scope).to be_a expected_class_for(original.type)
52
+
53
+ if scope.type == :criteria
54
+ expect(scope.criteria).to be == original.criteria
55
+ elsif %i[conjunction disjunction].include?(scope.type)
56
+ should_recursively_convert_scopes(original.scopes, scope.scopes)
57
+ end
58
+ end
59
+ end
60
+ # rubocop:enable Metrics/AbcSize
61
+
62
+ # :nocov:
63
+ end
64
+
65
+ deferred_examples 'should build an all scope' do
66
+ let(:scope) { build_all }
67
+
68
+ # :nocov:
69
+ unless all_scope_class
70
+ pending '(must specify :all_class option)'
71
+
72
+ next
73
+ end
74
+ # :nocov:
75
+
76
+ it { expect(scope).to be_a all_scope_class }
77
+ end
78
+
79
+ deferred_examples 'should build a conjunction scope' do
80
+ include_deferred 'with container scope helpers'
81
+
82
+ # :nocov:
83
+ unless conjunction_scope_class
84
+ pending '(must specify :conjunction_class option)'
85
+
86
+ next
87
+ end
88
+ # :nocov:
89
+
90
+ describe 'with scopes: an empty Array' do
91
+ let(:scopes) { [] }
92
+
93
+ it { expect(scope).to be_a conjunction_scope_class }
94
+
95
+ it { expect(scope.scopes).to be == scopes }
96
+ end
97
+
98
+ describe 'with scopes: an Array of Scopes' do
99
+ let(:scopes) { Array.new(3) { build_scope } }
100
+
101
+ it { expect(scope).to be_a conjunction_scope_class }
102
+
103
+ it { expect(scope.scopes.size).to be == scopes.size }
104
+
105
+ it 'should convert the scopes', :aggregate_failures do
106
+ should_recursively_convert_scopes(scopes, scope.scopes)
107
+ end
108
+ end
109
+ end
110
+
111
+ deferred_examples 'should build a criteria scope' do |inverted: false|
112
+ let(:scope) { build_criteria(criteria:) }
113
+
114
+ # :nocov:
115
+ unless criteria_scope_class
116
+ pending '(must specify :criteria_class option)'
117
+
118
+ next
119
+ end
120
+ # :nocov:
121
+
122
+ describe 'with criteria: an empty Array' do
123
+ let(:criteria) { [] }
124
+
125
+ it { expect(scope).to be_a criteria_scope_class }
126
+
127
+ it { expect(scope.criteria).to be == criteria }
128
+
129
+ it { expect(scope.inverted?).to be inverted }
130
+ end
131
+
132
+ describe 'with criteria: an Array of criteria' do
133
+ let(:criteria) do
134
+ operators = Cuprum::Collections::Queries::Operators
135
+
136
+ [
137
+ [
138
+ 'title',
139
+ operators::EQUAL,
140
+ 'The Word For World Is Forest'
141
+ ],
142
+ [
143
+ 'author',
144
+ operators::EQUAL,
145
+ 'Ursula K. LeGuin'
146
+ ]
147
+ ]
148
+ end
149
+
150
+ it { expect(scope).to be_a criteria_scope_class }
151
+
152
+ it { expect(scope.criteria).to be == criteria }
153
+
154
+ it { expect(scope.inverted?).to be inverted }
155
+ end
156
+ end
157
+
158
+ deferred_examples 'should build a disjunction scope' do
159
+ include_deferred 'with container scope helpers'
160
+
161
+ let(:scope) { build_container(scopes:) }
162
+
163
+ # :nocov:
164
+ unless disjunction_scope_class
165
+ pending '(must specify :disjunction_class option)'
166
+
167
+ next
168
+ end
169
+ # :nocov:
170
+
171
+ describe 'with scopes: an empty Array' do
172
+ let(:scopes) { [] }
173
+
174
+ it { expect(scope).to be_a disjunction_scope_class }
175
+
176
+ it { expect(scope.scopes).to be == scopes }
177
+ end
178
+
179
+ describe 'with scopes: an Array of Scopes' do
180
+ let(:scopes) { Array.new(3) { build_scope } }
181
+
182
+ it { expect(scope).to be_a disjunction_scope_class }
183
+
184
+ it { expect(scope.scopes.size).to be == scopes.size }
185
+
186
+ it 'should convert the scopes', :aggregate_failures do
187
+ should_recursively_convert_scopes(scopes, scope.scopes)
188
+ end
189
+ end
190
+ end
191
+
192
+ deferred_examples 'should build a none scope' do
193
+ let(:scope) { build_none }
194
+
195
+ # :nocov:
196
+ unless none_scope_class
197
+ pending '(must specify :none_class option)'
198
+
199
+ next
200
+ end
201
+ # :nocov:
202
+
203
+ it { expect(scope).to be_a none_scope_class }
204
+ end
205
+
206
+ deferred_examples 'should validate the criteria' do
207
+ describe 'with criteria: nil' do
208
+ let(:error_message) { 'criteria must be an Array' }
209
+
210
+ it 'should raise an exception' do
211
+ expect { build_criteria(criteria: nil) }
212
+ .to raise_error ArgumentError, error_message
213
+ end
214
+ end
215
+
216
+ describe 'with criteria: an Object' do
217
+ let(:error_message) { 'criteria must be an Array' }
218
+
219
+ it 'should raise an exception' do
220
+ expect { build_criteria(criteria: Object.new.freeze) }
221
+ .to raise_error ArgumentError, error_message
222
+ end
223
+ end
224
+
225
+ describe 'with criteria: an Array of non-Array items' do
226
+ let(:error_message) { 'criterion must be an Array of size 3' }
227
+
228
+ it 'should raise an exception' do
229
+ expect { build_criteria(criteria: [nil]) }
230
+ .to raise_error ArgumentError, error_message
231
+ end
232
+ end
233
+
234
+ describe 'with criteria: an Array of invalid Arrays' do
235
+ let(:error_message) { 'criterion must be an Array of size 3' }
236
+
237
+ it 'should raise an exception' do
238
+ expect { build_criteria(criteria: [[], [], []]) }
239
+ .to raise_error ArgumentError, error_message
240
+ end
241
+ end
242
+ end
243
+
244
+ deferred_examples 'should validate the scopes' do
245
+ describe 'with scopes: nil' do
246
+ let(:error_message) { 'scopes must be an Array' }
247
+
248
+ it 'should raise an exception' do
249
+ expect { build_container(scopes: nil) }
250
+ .to raise_error ArgumentError, error_message
251
+ end
252
+ end
253
+
254
+ describe 'with scopes: an Object' do
255
+ let(:error_message) { 'scopes must be an Array' }
256
+
257
+ it 'should raise an exception' do
258
+ expect { build_container(scopes: Object.new.freeze) }
259
+ .to raise_error ArgumentError, error_message
260
+ end
261
+ end
262
+
263
+ describe 'with scopes: an invalid Array' do
264
+ let(:error_message) { 'scope must be a Scope instance' }
265
+
266
+ it 'should raise an exception' do
267
+ expect { build_container(scopes: [nil]) }
268
+ .to raise_error ArgumentError, error_message
269
+ end
270
+ end
271
+ end
272
+
273
+ describe '.instance' do
274
+ it 'should define the class method' do
275
+ expect(described_class).to respond_to(:instance).with(0).arguments
276
+ end
277
+
278
+ it { expect(described_class.instance).to be_a described_class }
279
+
280
+ it { expect(described_class.instance).to be subject }
281
+ end
282
+
283
+ describe '#build' do
284
+ it 'should define the method' do
285
+ expect(subject)
286
+ .to respond_to(:build)
287
+ .with(0..1).arguments
288
+ .and_a_block
289
+ end
290
+
291
+ describe 'with an invalid scope' do
292
+ let(:original) { build_scope }
293
+ let(:error_class) do
294
+ Cuprum::Collections::Scopes::Building::UnknownScopeTypeError
295
+ end
296
+ let(:error_message) do
297
+ "#{described_class.name} cannot transform scopes of " \
298
+ "type #{original.type.inspect} (#{original.class.name})"
299
+ end
300
+
301
+ before(:example) do
302
+ allow(original).to receive(:type).and_return(:invalid)
303
+ end
304
+
305
+ it 'should raise an exception' do
306
+ expect { subject.build(original) }
307
+ .to raise_error error_class, error_message
308
+ end
309
+ end
310
+
311
+ next if deferred_options.fetch(:abstract, false)
312
+
313
+ describe 'with a block' do
314
+ def build_criteria(criteria:)
315
+ value = criteria.to_h do |(attribute, _, expected)|
316
+ [attribute, expected]
317
+ end
318
+ block = -> { value }
319
+
320
+ subject.build(&block)
321
+ end
322
+
323
+ def parse_criteria(...)
324
+ subject.build(...).criteria
325
+ end
326
+
327
+ include_deferred 'should build a criteria scope'
328
+
329
+ include_deferred 'should parse Scope criteria from a block'
330
+ end
331
+
332
+ describe 'with a proc' do
333
+ def build_criteria(criteria:)
334
+ value = criteria.to_h do |(attribute, _, expected)|
335
+ [attribute, expected]
336
+ end
337
+ block = -> { value }
338
+
339
+ subject.build(block)
340
+ end
341
+
342
+ def parse_criteria(...)
343
+ subject.build(...).criteria
344
+ end
345
+
346
+ include_deferred 'should build a criteria scope'
347
+
348
+ include_deferred 'should parse Scope criteria from a block'
349
+ end
350
+
351
+ describe 'with a hash' do
352
+ def build_criteria(criteria:)
353
+ value = criteria.to_h do |(attribute, _, expected)|
354
+ [attribute, expected]
355
+ end
356
+
357
+ subject.build(value)
358
+ end
359
+
360
+ def parse_criteria(value)
361
+ subject.build(value).criteria
362
+ end
363
+
364
+ include_deferred 'should build a criteria scope'
365
+
366
+ include_deferred 'should parse Scope criteria from a Hash'
367
+ end
368
+
369
+ describe 'with an all scope' do
370
+ def build_all
371
+ original = Cuprum::Collections::Scopes::AllScope.new
372
+
373
+ subject.build(original)
374
+ end
375
+
376
+ include_deferred 'should build an all scope'
377
+ end
378
+
379
+ describe 'with a conjunction scope' do
380
+ def build_container(scopes:)
381
+ original =
382
+ Cuprum::Collections::Scopes::ConjunctionScope
383
+ .new(scopes:)
384
+
385
+ subject.build(original)
386
+ end
387
+
388
+ include_deferred 'should build a conjunction scope'
389
+ end
390
+
391
+ describe 'with a criteria scope' do
392
+ def build_criteria(criteria:)
393
+ original =
394
+ Cuprum::Collections::Scopes::CriteriaScope
395
+ .new(criteria:)
396
+
397
+ subject.build(original)
398
+ end
399
+
400
+ include_deferred 'should build a criteria scope'
401
+ end
402
+
403
+ describe 'with a disjunction scope' do
404
+ def build_container(scopes:)
405
+ original =
406
+ Cuprum::Collections::Scopes::DisjunctionScope
407
+ .new(scopes:)
408
+
409
+ subject.build(original)
410
+ end
411
+
412
+ include_deferred 'should build a disjunction scope'
413
+ end
414
+
415
+ describe 'with a none scope' do
416
+ def build_none
417
+ original = Cuprum::Collections::Scopes::NoneScope.new
418
+
419
+ subject.build(original)
420
+ end
421
+
422
+ include_deferred 'should build a none scope'
423
+ end
424
+
425
+ describe 'with an all scope of matching class' do
426
+ # :nocov:
427
+ unless all_scope_class
428
+ pending '(must specify :all_class option)'
429
+
430
+ next
431
+ end
432
+ # :nocov:
433
+
434
+ let(:original) do
435
+ all_scope_class.new
436
+ end
437
+
438
+ it { expect(subject.build(original)).to be original }
439
+ end
440
+
441
+ describe 'with a conjunction scope of matching class' do
442
+ # :nocov:
443
+ unless conjunction_scope_class
444
+ pending '(must specify :conjunction_class option)'
445
+
446
+ next
447
+ end
448
+ # :nocov:
449
+
450
+ let(:original) do
451
+ conjunction_scope_class.new(scopes: [])
452
+ end
453
+
454
+ it { expect(subject.build(original)).to be original }
455
+ end
456
+
457
+ describe 'with a criteria scope of matching class' do
458
+ # :nocov:
459
+ unless criteria_scope_class
460
+ pending '(must specify :criteria_scope_class option)'
461
+
462
+ next
463
+ end
464
+ # :nocov:
465
+
466
+ let(:original) do
467
+ criteria_scope_class.new(criteria: [])
468
+ end
469
+
470
+ it { expect(subject.build(original)).to be original }
471
+ end
472
+
473
+ describe 'with a disjunction scope of matching class' do
474
+ # :nocov:
475
+ unless disjunction_scope_class
476
+ pending '(must specify :disjunction_scope_class option)'
477
+
478
+ next
479
+ end
480
+ # :nocov:
481
+
482
+ let(:original) do
483
+ disjunction_scope_class.new(scopes: [])
484
+ end
485
+
486
+ it { expect(subject.build(original)).to be original }
487
+ end
488
+
489
+ describe 'with a none scope of matching class' do
490
+ # :nocov:
491
+ unless none_scope_class
492
+ pending '(must specify :none_class option)'
493
+
494
+ next
495
+ end
496
+ # :nocov:
497
+
498
+ let(:original) do
499
+ none_scope_class.new
500
+ end
501
+
502
+ it { expect(subject.build(original)).to be original }
503
+ end
504
+ end
505
+
506
+ describe '#build_all_scope' do
507
+ define_method :build_all do
508
+ subject.build_all_scope
509
+ end
510
+
511
+ it 'should define the method' do
512
+ expect(subject)
513
+ .to respond_to(:build_all_scope)
514
+ .with(0).arguments
515
+ end
516
+
517
+ next if deferred_options.fetch(:abstract, false)
518
+
519
+ include_deferred 'should build an all scope'
520
+ end
521
+
522
+ describe '#build_conjunction_scope' do
523
+ let(:scope) { subject.build_conjunction_scope(scopes:) }
524
+
525
+ define_method :build_container do |scopes:|
526
+ subject.build_conjunction_scope(scopes:)
527
+ end
528
+
529
+ it 'should define the method' do
530
+ expect(subject)
531
+ .to respond_to(:build_conjunction_scope)
532
+ .with(0).arguments
533
+ .and_keywords(:safe, :scopes)
534
+ end
535
+
536
+ include_deferred 'should validate the scopes'
537
+
538
+ next if deferred_options.fetch(:abstract, false)
539
+
540
+ include_deferred 'should build a conjunction scope'
541
+
542
+ describe 'with safe: false' do
543
+ let(:scope) do
544
+ subject.build_conjunction_scope(scopes:, safe: false)
545
+ end
546
+
547
+ describe 'with scopes: an empty Array' do
548
+ let(:scopes) { [] }
549
+
550
+ it { expect(scope).to be_a conjunction_scope_class }
551
+
552
+ it { expect(scope.scopes).to be == scopes }
553
+ end
554
+
555
+ describe 'with scopes: an Array of Scopes' do
556
+ let(:scopes) { Array.new(3) { build_scope } }
557
+
558
+ it { expect(scope).to be_a conjunction_scope_class }
559
+
560
+ it { expect(scope.scopes).to be == scopes }
561
+ end
562
+ end
563
+ end
564
+
565
+ describe '#build_criteria_scope' do
566
+ let(:inverted) { false }
567
+
568
+ define_method :build_criteria do |criteria:|
569
+ subject.build_criteria_scope(criteria:, inverted:)
570
+ end
571
+
572
+ it 'should define the method' do
573
+ expect(subject)
574
+ .to respond_to(:build_criteria_scope)
575
+ .with(0).arguments
576
+ .and_keywords(:criteria, :inverted)
577
+ end
578
+
579
+ include_deferred 'should validate the criteria'
580
+
581
+ next if deferred_options.fetch(:abstract, false)
582
+
583
+ include_deferred 'should build a criteria scope'
584
+
585
+ context 'with inverted: true' do
586
+ let(:inverted) { true }
587
+
588
+ include_deferred 'should build a criteria scope', inverted: true
589
+ end
590
+ end
591
+
592
+ describe '#build_disjunction_scope' do
593
+ let(:scope) { subject.build_disjunction_scope(scopes:) }
594
+
595
+ define_method :build_container do |scopes:|
596
+ subject.build_disjunction_scope(scopes:)
597
+ end
598
+
599
+ it 'should define the method' do
600
+ expect(subject)
601
+ .to respond_to(:build_disjunction_scope)
602
+ .with(0).arguments
603
+ .and_keywords(:safe, :scopes)
604
+ end
605
+
606
+ include_deferred 'should validate the scopes'
607
+
608
+ next if deferred_options.fetch(:abstract, false)
609
+
610
+ include_deferred 'should build a disjunction scope'
611
+
612
+ describe 'with safe: false' do
613
+ let(:scope) do
614
+ subject.build_disjunction_scope(scopes:, safe: false)
615
+ end
616
+
617
+ describe 'with scopes: an empty Array' do
618
+ let(:scopes) { [] }
619
+
620
+ it { expect(scope).to be_a disjunction_scope_class }
621
+
622
+ it { expect(scope.scopes).to be == scopes }
623
+ end
624
+
625
+ describe 'with scopes: an Array of Scopes' do
626
+ let(:scopes) { Array.new(3) { build_scope } }
627
+
628
+ it { expect(scope).to be_a disjunction_scope_class }
629
+
630
+ it { expect(scope.scopes).to be == scopes }
631
+ end
632
+ end
633
+ end
634
+
635
+ describe '#build_none_scope' do
636
+ define_method :build_none do
637
+ subject.build_none_scope
638
+ end
639
+
640
+ it 'should define the method' do
641
+ expect(subject)
642
+ .to respond_to(:build_none_scope)
643
+ .with(0).arguments
644
+ end
645
+
646
+ next if deferred_options.fetch(:abstract, false)
647
+
648
+ include_deferred 'should build a none scope'
649
+ end
650
+
651
+ describe '#transform_scope' do
652
+ let(:scope) { subject.transform_scope(scope: original) }
653
+
654
+ it 'should define the method' do
655
+ expect(subject)
656
+ .to respond_to(:transform_scope)
657
+ .with(0).arguments
658
+ .and_keywords(:scope)
659
+ end
660
+
661
+ describe 'with scope: nil' do
662
+ let(:error_message) { 'scope must be a Scope instance' }
663
+
664
+ it 'should raise an exception' do
665
+ expect { subject.transform_scope(scope: nil) }
666
+ .to raise_error ArgumentError, error_message
667
+ end
668
+ end
669
+
670
+ describe 'with scope: an Object' do
671
+ let(:error_message) { 'scope must be a Scope instance' }
672
+
673
+ it 'should raise an exception' do
674
+ expect { subject.transform_scope(scope: Object.new.freeze) }
675
+ .to raise_error ArgumentError, error_message
676
+ end
677
+ end
678
+
679
+ describe 'with an invalid scope' do
680
+ let(:original) { build_scope }
681
+ let(:error_class) do
682
+ Cuprum::Collections::Scopes::Building::UnknownScopeTypeError
683
+ end
684
+ let(:error_message) do
685
+ "#{described_class.name} cannot transform scopes of " \
686
+ "type #{original.type.inspect} (#{original.class.name})"
687
+ end
688
+
689
+ before(:example) do
690
+ allow(original).to receive(:type).and_return(:invalid)
691
+ end
692
+
693
+ it 'should raise an exception' do
694
+ expect { subject.transform_scope(scope: original) }
695
+ .to raise_error error_class, error_message
696
+ end
697
+ end
698
+
699
+ next if deferred_options.fetch(:abstract, false)
700
+
701
+ describe 'with an all scope' do
702
+ define_method :build_all do
703
+ original = Cuprum::Collections::Scopes::AllScope.new
704
+
705
+ subject.transform_scope(scope: original)
706
+ end
707
+
708
+ include_deferred 'should build an all scope'
709
+ end
710
+
711
+ describe 'with a conjunction scope' do
712
+ define_method :build_container do |scopes:|
713
+ original =
714
+ Cuprum::Collections::Scopes::ConjunctionScope
715
+ .new(scopes:)
716
+
717
+ subject.transform_scope(scope: original)
718
+ end
719
+
720
+ include_deferred 'should build a conjunction scope'
721
+ end
722
+
723
+ describe 'with a criteria scope' do
724
+ let(:inverted) { false }
725
+
726
+ define_method :build_criteria do |criteria:|
727
+ original =
728
+ Cuprum::Collections::Scopes::CriteriaScope
729
+ .new(criteria:, inverted:)
730
+
731
+ subject.transform_scope(scope: original)
732
+ end
733
+
734
+ include_deferred 'should build a criteria scope'
735
+
736
+ context 'when the scope is inverted' do
737
+ let(:inverted) { true }
738
+
739
+ include_deferred 'should build a criteria scope', inverted: true
740
+ end
741
+ end
742
+
743
+ describe 'with a disjunction scope' do
744
+ define_method :build_container do |scopes:|
745
+ original =
746
+ Cuprum::Collections::Scopes::DisjunctionScope
747
+ .new(scopes:)
748
+
749
+ subject.transform_scope(scope: original)
750
+ end
751
+
752
+ include_deferred 'should build a disjunction scope'
753
+ end
754
+
755
+ describe 'with a none scope' do
756
+ define_method :build_none do
757
+ original = Cuprum::Collections::Scopes::NoneScope.new
758
+
759
+ subject.transform_scope(scope: original)
760
+ end
761
+
762
+ include_deferred 'should build a none scope'
763
+ end
764
+
765
+ describe 'with an all scope of matching class' do
766
+ # :nocov:
767
+ unless all_scope_class
768
+ pending '(must specify :all_class option)'
769
+
770
+ next
771
+ end
772
+ # :nocov:
773
+
774
+ let(:original) do
775
+ all_scope_class.new
776
+ end
777
+
778
+ it 'should return the original scope' do
779
+ expect(subject.transform_scope(scope: original)).to be original
780
+ end
781
+ end
782
+
783
+ describe 'with a conjunction scope of matching class' do
784
+ # :nocov:
785
+ unless conjunction_scope_class
786
+ pending '(must specify :conjunction_class option)'
787
+
788
+ next
789
+ end
790
+ # :nocov:
791
+
792
+ let(:original) do
793
+ conjunction_scope_class.new(scopes: [])
794
+ end
795
+
796
+ it 'should return the original scope' do
797
+ expect(subject.transform_scope(scope: original)).to be original
798
+ end
799
+ end
800
+
801
+ describe 'with a criteria scope of matching class' do
802
+ # :nocov:
803
+ unless criteria_scope_class
804
+ pending '(must specify :criteria_scope_class option)'
805
+
806
+ next
807
+ end
808
+ # :nocov:
809
+
810
+ let(:original) do
811
+ criteria_scope_class.new(criteria: [])
812
+ end
813
+
814
+ it 'should return the original scope' do
815
+ expect(subject.transform_scope(scope: original)).to be original
816
+ end
817
+ end
818
+
819
+ describe 'with a disjunction scope of matching class' do
820
+ # :nocov:
821
+ unless disjunction_scope_class
822
+ pending '(must specify :disjunction_scope_class option)'
823
+
824
+ next
825
+ end
826
+ # :nocov:
827
+
828
+ let(:original) do
829
+ disjunction_scope_class.new(scopes: [])
830
+ end
831
+
832
+ it 'should return the original scope' do
833
+ expect(subject.transform_scope(scope: original)).to be original
834
+ end
835
+ end
836
+
837
+ describe 'with a none scope of matching class' do
838
+ # :nocov:
839
+ unless none_scope_class
840
+ pending '(must specify :none_class option)'
841
+
842
+ next
843
+ end
844
+ # :nocov:
845
+
846
+ let(:original) do
847
+ none_scope_class.new
848
+ end
849
+
850
+ it 'should return the original scope' do
851
+ expect(subject.transform_scope(scope: original)).to be original
852
+ end
853
+ end
854
+ end
855
+ end
856
+ end
857
+ end