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