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,1430 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections/queries'
4
+ require 'cuprum/collections/rspec/contracts/scopes'
5
+ require 'cuprum/collections/scopes/base'
6
+ require 'cuprum/collections/scopes/builder'
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 validating the behavior of scope composition.
14
+ module CompositionContracts
15
+ # Contract validating the fluent interface for scope composition.
16
+ module ShouldComposeScopesContract
17
+ extend RSpec::SleepingKingStudios::Contract
18
+
19
+ # @!method apply(example_group, except: [])
20
+ # Adds the contract to the example group.
21
+ #
22
+ # @param example_group [RSpec::Core::ExampleGroup] the example group to
23
+ # which the contract is applied.
24
+ # @param except [Array<Symbol>] names of composition methods where the
25
+ # scope defines custom behavior.
26
+ contract do |except: []|
27
+ shared_context 'with an all scope' do
28
+ let(:original) do
29
+ Cuprum::Collections::Scopes::AllScope.new
30
+ end
31
+ end
32
+
33
+ shared_context 'with a none scope' do
34
+ let(:original) do
35
+ Cuprum::Collections::Scopes::NoneScope.new
36
+ end
37
+ end
38
+
39
+ shared_context 'with an empty conjunction scope' do
40
+ let(:original) do
41
+ Cuprum::Collections::Scopes::ConjunctionScope.new(scopes: [])
42
+ end
43
+ end
44
+
45
+ shared_context 'with an empty criteria scope' do
46
+ let(:original) do
47
+ Cuprum::Collections::Scopes::CriteriaScope.new(criteria: [])
48
+ end
49
+ end
50
+
51
+ shared_context 'with an empty disjunction scope' do
52
+ let(:original) do
53
+ Cuprum::Collections::Scopes::DisjunctionScope.new(scopes: [])
54
+ end
55
+ end
56
+
57
+ shared_context 'with a non-empty conjunction scope' do
58
+ let(:original) do
59
+ operators = Cuprum::Collections::Queries::Operators
60
+ criteria = [
61
+ [
62
+ 'category',
63
+ operators::EQUAL,
64
+ 'Science Fiction and Fantasy'
65
+ ]
66
+ ]
67
+ wrapped =
68
+ Cuprum::Collections::Scopes::CriteriaScope.new(criteria:)
69
+
70
+ Cuprum::Collections::Scopes::ConjunctionScope.new(scopes: [wrapped])
71
+ end
72
+ end
73
+
74
+ shared_examples 'with a non-empty criteria scope' do
75
+ let(:original) do
76
+ operators = Cuprum::Collections::Queries::Operators
77
+ criteria = [
78
+ [
79
+ 'category',
80
+ operators::EQUAL,
81
+ 'Science Fiction and Fantasy'
82
+ ]
83
+ ]
84
+
85
+ Cuprum::Collections::Scopes::CriteriaScope.new(criteria:)
86
+ end
87
+ end
88
+
89
+ shared_context 'with a non-empty disjunction scope' do
90
+ let(:original) do
91
+ operators = Cuprum::Collections::Queries::Operators
92
+ criteria = [
93
+ [
94
+ 'category',
95
+ operators::EQUAL,
96
+ 'Science Fiction and Fantasy'
97
+ ]
98
+ ]
99
+ wrapped =
100
+ Cuprum::Collections::Scopes::CriteriaScope.new(criteria:)
101
+
102
+ Cuprum::Collections::Scopes::DisjunctionScope.new(scopes: [wrapped])
103
+ end
104
+ end
105
+
106
+ describe '#and' do
107
+ it 'should define the method' do
108
+ expect(subject)
109
+ .to respond_to(:and)
110
+ .with(0..1).arguments
111
+ .and_a_block
112
+ end
113
+
114
+ it { expect(subject).to have_aliased_method(:and).as(:where) }
115
+
116
+ next if except.include?(:and)
117
+
118
+ describe 'with a block' do
119
+ let(:block) { -> { { 'title' => 'A Wizard of Earthsea' } } }
120
+ let(:expected) do
121
+ wrapped = Cuprum::Collections::Scope.new(&block)
122
+
123
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
124
+ scopes: [subject, wrapped]
125
+ )
126
+ end
127
+
128
+ it { expect(subject.and(&block)).to be == expected }
129
+ end
130
+
131
+ describe 'with a hash' do
132
+ let(:value) { { 'title' => 'A Wizard of Earthsea' } }
133
+ let(:expected) do
134
+ wrapped = Cuprum::Collections::Scope.new(value)
135
+
136
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
137
+ scopes: [subject, wrapped]
138
+ )
139
+ end
140
+
141
+ it { expect(subject.and(value)).to be == expected }
142
+ end
143
+
144
+ wrap_context 'with an all scope' do
145
+ it { expect(subject.and(original)).to be subject }
146
+ end
147
+
148
+ wrap_context 'with a none scope' do
149
+ it { expect(subject.and(original)).to be == original }
150
+ end
151
+
152
+ wrap_context 'with an empty conjunction scope' do
153
+ it { expect(subject.and(original)).to be subject }
154
+ end
155
+
156
+ wrap_context 'with an empty criteria scope' do
157
+ it { expect(subject.and(original)).to be subject }
158
+ end
159
+
160
+ wrap_context 'with an empty disjunction scope' do
161
+ it { expect(subject.and(original)).to be subject }
162
+ end
163
+
164
+ wrap_context 'with a non-empty conjunction scope' do
165
+ let(:expected) do
166
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
167
+ scopes: [subject, *original.scopes]
168
+ )
169
+ end
170
+
171
+ it { expect(subject.and(original)).to be == expected }
172
+ end
173
+
174
+ wrap_context 'with a non-empty criteria scope' do
175
+ let(:expected) do
176
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
177
+ scopes: [subject, original]
178
+ )
179
+ end
180
+
181
+ it { expect(subject.and(original)).to be == expected }
182
+ end
183
+
184
+ wrap_context 'with a non-empty disjunction scope' do
185
+ let(:expected) do
186
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
187
+ scopes: [subject, original]
188
+ )
189
+ end
190
+
191
+ it { expect(subject.and(original)).to be == expected }
192
+ end
193
+ end
194
+
195
+ describe '#not' do
196
+ it 'should define the method' do
197
+ expect(subject)
198
+ .to respond_to(:not)
199
+ .with(0..1).arguments
200
+ .and_a_block
201
+ end
202
+
203
+ next if except.include?(:not)
204
+
205
+ describe 'with a block' do
206
+ let(:block) { -> { { 'title' => 'A Wizard of Earthsea' } } }
207
+ let(:block) { -> { { 'title' => 'A Wizard of Earthsea' } } }
208
+ let(:expected) do
209
+ wrapped = Cuprum::Collections::Scope.new(&block)
210
+
211
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
212
+ scopes: [subject, wrapped.invert]
213
+ )
214
+ end
215
+
216
+ it { expect(subject.not(&block)).to be == expected }
217
+ end
218
+
219
+ describe 'with a hash' do
220
+ let(:value) { { 'title' => 'A Wizard of Earthsea' } }
221
+ let(:expected) do
222
+ wrapped = Cuprum::Collections::Scope.new(value)
223
+
224
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
225
+ scopes: [subject, wrapped.invert]
226
+ )
227
+ end
228
+
229
+ it { expect(subject.not(value)).to be == expected }
230
+ end
231
+
232
+ wrap_context 'with an all scope' do
233
+ let(:expected) { Cuprum::Collections::Scopes::NoneScope.new }
234
+
235
+ it { expect(subject.not(original)).to be == expected }
236
+ end
237
+
238
+ wrap_context 'with a none scope' do
239
+ it { expect(subject.not(original)).to be subject }
240
+ end
241
+
242
+ wrap_context 'with an empty conjunction scope' do
243
+ it { expect(subject.not(original)).to be subject }
244
+ end
245
+
246
+ wrap_context 'with an empty criteria scope' do
247
+ it { expect(subject.not(original)).to be subject }
248
+ end
249
+
250
+ wrap_context 'with an empty disjunction scope' do
251
+ it { expect(subject.not(original)).to be subject }
252
+ end
253
+
254
+ wrap_context 'with a non-empty conjunction scope' do
255
+ let(:expected) do
256
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
257
+ scopes: [subject, original.invert]
258
+ )
259
+ end
260
+
261
+ it { expect(subject.not(original)).to be == expected }
262
+ end
263
+
264
+ wrap_context 'with a non-empty criteria scope' do
265
+ let(:expected) do
266
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
267
+ scopes: [subject, original.invert]
268
+ )
269
+ end
270
+
271
+ it { expect(subject.not(original)).to be == expected }
272
+ end
273
+
274
+ wrap_context 'with a non-empty disjunction scope' do
275
+ let(:expected) do
276
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
277
+ scopes: [subject, *original.invert.scopes]
278
+ )
279
+ end
280
+
281
+ it { expect(subject.not(original)).to be == expected }
282
+ end
283
+ end
284
+
285
+ describe '#or' do
286
+ it 'should define the method' do
287
+ expect(subject)
288
+ .to respond_to(:or)
289
+ .with(0..1).arguments
290
+ .and_a_block
291
+ end
292
+
293
+ next if except.include?(:or)
294
+
295
+ describe 'with a block' do
296
+ let(:block) { -> { { 'title' => 'A Wizard of Earthsea' } } }
297
+ let(:expected) do
298
+ wrapped = Cuprum::Collections::Scope.new(&block)
299
+
300
+ Cuprum::Collections::Scopes::DisjunctionScope.new(
301
+ scopes: [subject, wrapped]
302
+ )
303
+ end
304
+
305
+ it { expect(subject.or(&block)).to be == expected }
306
+ end
307
+
308
+ describe 'with a hash' do
309
+ let(:value) { { 'title' => 'A Wizard of Earthsea' } }
310
+ let(:expected) do
311
+ wrapped = Cuprum::Collections::Scope.new(value)
312
+
313
+ Cuprum::Collections::Scopes::DisjunctionScope.new(
314
+ scopes: [subject, wrapped]
315
+ )
316
+ end
317
+
318
+ it { expect(subject.or(value)).to be == expected }
319
+ end
320
+
321
+ wrap_context 'with an all scope' do
322
+ it { expect(subject.or(original)).to be == original }
323
+ end
324
+
325
+ wrap_context 'with a none scope' do
326
+ it { expect(subject.or(original)).to be subject }
327
+ end
328
+
329
+ wrap_context 'with an empty conjunction scope' do
330
+ it { expect(subject.or(original)).to be subject }
331
+ end
332
+
333
+ wrap_context 'with an empty criteria scope' do
334
+ it { expect(subject.or(original)).to be subject }
335
+ end
336
+
337
+ wrap_context 'with an empty disjunction scope' do
338
+ it { expect(subject.or(original)).to be subject }
339
+ end
340
+
341
+ wrap_context 'with a non-empty conjunction scope' do
342
+ let(:expected) do
343
+ Cuprum::Collections::Scopes::DisjunctionScope.new(
344
+ scopes: [subject, original]
345
+ )
346
+ end
347
+
348
+ it { expect(subject.or(original)).to be == expected }
349
+ end
350
+
351
+ wrap_context 'with a non-empty criteria scope' do
352
+ let(:expected) do
353
+ Cuprum::Collections::Scopes::DisjunctionScope.new(
354
+ scopes: [subject, original]
355
+ )
356
+ end
357
+
358
+ it { expect(subject.or(original)).to be == expected }
359
+ end
360
+
361
+ wrap_context 'with a non-empty disjunction scope' do
362
+ let(:expected) do
363
+ Cuprum::Collections::Scopes::DisjunctionScope.new(
364
+ scopes: [subject, *original.scopes]
365
+ )
366
+ end
367
+
368
+ it { expect(subject.or(original)).to be == expected }
369
+ end
370
+ end
371
+ end
372
+ end
373
+
374
+ # Contract validating scope composition for conjunction scopes.
375
+ module ShouldComposeScopesForConjunctionContract
376
+ extend RSpec::SleepingKingStudios::Contract
377
+ include Cuprum::Collections::RSpec::Contracts::Scopes::CompositionContracts # rubocop:disable Layout/LineLength
378
+
379
+ # @!method apply(example_group)
380
+ # Adds the contract to the example group.
381
+ #
382
+ # @param example_group [RSpec::Core::ExampleGroup] the example group to
383
+ # which the contract is applied.
384
+ contract do
385
+ shared_context 'when the scope has many child scopes' do
386
+ let(:scopes) do
387
+ [
388
+ build_scope({ 'author' => 'J.R.R. Tolkien' }),
389
+ build_scope({ 'series' => 'The Lord of the Rings' }),
390
+ build_scope do |scope|
391
+ { 'published_at' => scope.less_than('1955-01-01') }
392
+ end
393
+ ]
394
+ end
395
+ end
396
+
397
+ include_contract 'should compose scopes', except: %i[and not]
398
+
399
+ describe '#and' do
400
+ describe 'with a block' do
401
+ let(:block) { -> { { 'title' => 'A Wizard of Earthsea' } } }
402
+ let(:expected) do
403
+ wrapped = Cuprum::Collections::Scope.new(&block)
404
+
405
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
406
+ scopes: [*subject.scopes, wrapped]
407
+ )
408
+ end
409
+
410
+ it { expect(subject.and(&block)).to be == expected }
411
+ end
412
+
413
+ describe 'with a hash' do
414
+ let(:value) { { 'title' => 'A Wizard of Earthsea' } }
415
+ let(:expected) do
416
+ wrapped = Cuprum::Collections::Scope.new(value)
417
+
418
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
419
+ scopes: [*subject.scopes, wrapped]
420
+ )
421
+ end
422
+
423
+ it { expect(subject.and(value)).to be == expected }
424
+ end
425
+
426
+ wrap_context 'with an all scope' do
427
+ it { expect(subject.and(original)).to be subject }
428
+ end
429
+
430
+ wrap_context 'with a none scope' do
431
+ it { expect(subject.and(original)).to be == original }
432
+ end
433
+
434
+ wrap_context 'with an empty conjunction scope' do
435
+ it { expect(subject.and(original)).to be subject }
436
+
437
+ wrap_context 'when the scope has many child scopes' do
438
+ it { expect(subject.and(original)).to be subject }
439
+ end
440
+ end
441
+
442
+ wrap_context 'with an empty criteria scope' do
443
+ it { expect(subject.and(original)).to be subject }
444
+ end
445
+
446
+ wrap_context 'with an empty disjunction scope' do
447
+ it { expect(subject.and(original)).to be subject }
448
+ end
449
+
450
+ wrap_context 'with a non-empty conjunction scope' do
451
+ let(:expected) do
452
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
453
+ scopes: [*subject.scopes, *original.scopes]
454
+ )
455
+ end
456
+
457
+ it { expect(subject.and(original)).to be == expected }
458
+
459
+ wrap_context 'when the scope has many child scopes' do
460
+ it { expect(subject.and(original)).to be == expected }
461
+ end
462
+ end
463
+
464
+ wrap_context 'with a non-empty criteria scope' do
465
+ let(:expected) do
466
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
467
+ scopes: [*subject.scopes, original]
468
+ )
469
+ end
470
+
471
+ it { expect(subject.and(original)).to be == expected }
472
+
473
+ wrap_context 'when the scope has many child scopes' do
474
+ it { expect(subject.and(original)).to be == expected }
475
+ end
476
+ end
477
+
478
+ wrap_context 'with a non-empty disjunction scope' do
479
+ let(:expected) do
480
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
481
+ scopes: [*subject.scopes, original]
482
+ )
483
+ end
484
+
485
+ it { expect(subject.and(original)).to be == expected }
486
+
487
+ wrap_context 'when the scope has many child scopes' do
488
+ it { expect(subject.and(original)).to be == expected }
489
+ end
490
+ end
491
+ end
492
+
493
+ describe '#not' do
494
+ describe 'with a block' do
495
+ let(:block) { -> { { 'title' => 'A Wizard of Earthsea' } } }
496
+ let(:expected) do
497
+ wrapped = Cuprum::Collections::Scope.new(&block)
498
+
499
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
500
+ scopes: [*subject.scopes, wrapped.invert]
501
+ )
502
+ end
503
+
504
+ it { expect(subject.not(&block)).to be == expected }
505
+
506
+ wrap_context 'when the scope has many child scopes' do
507
+ it { expect(subject.not(&block)).to be == expected }
508
+ end
509
+ end
510
+
511
+ describe 'with a hash' do
512
+ let(:value) { { 'title' => 'A Wizard of Earthsea' } }
513
+ let(:expected) do
514
+ wrapped = Cuprum::Collections::Scope.new(value)
515
+
516
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
517
+ scopes: [*subject.scopes, wrapped.invert]
518
+ )
519
+ end
520
+
521
+ it { expect(subject.not(value)).to be == expected }
522
+
523
+ wrap_context 'when the scope has many child scopes' do
524
+ it { expect(subject.not(value)).to be == expected }
525
+ end
526
+ end
527
+
528
+ wrap_context 'with an all scope' do
529
+ let(:expected) { Cuprum::Collections::Scopes::NoneScope.new }
530
+
531
+ it { expect(subject.not(original)).to be == expected }
532
+ end
533
+
534
+ wrap_context 'with a none scope' do
535
+ it { expect(subject.not(original)).to be subject }
536
+ end
537
+
538
+ wrap_context 'with an empty conjunction scope' do
539
+ it { expect(subject.not(original)).to be subject }
540
+ end
541
+
542
+ wrap_context 'with an empty criteria scope' do
543
+ it { expect(subject.not(original)).to be subject }
544
+ end
545
+
546
+ wrap_context 'with an empty disjunction scope' do
547
+ it { expect(subject.not(original)).to be subject }
548
+ end
549
+
550
+ wrap_context 'with a non-empty conjunction scope' do
551
+ let(:expected) do
552
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
553
+ scopes: [*subject.scopes, original.invert]
554
+ )
555
+ end
556
+
557
+ it { expect(subject.not(original)).to be == expected }
558
+
559
+ wrap_context 'when the scope has many child scopes' do
560
+ it { expect(subject.not(original)).to be == expected }
561
+ end
562
+ end
563
+
564
+ wrap_context 'with a non-empty criteria scope' do
565
+ let(:expected) do
566
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
567
+ scopes: [*subject.scopes, original.invert]
568
+ )
569
+ end
570
+
571
+ it { expect(subject.not(original)).to be == expected }
572
+
573
+ wrap_context 'when the scope has many child scopes' do
574
+ it { expect(subject.not(original)).to be == expected }
575
+ end
576
+ end
577
+
578
+ wrap_context 'with a non-empty disjunction scope' do
579
+ let(:expected) do
580
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
581
+ scopes: [*subject.scopes, *original.invert.scopes]
582
+ )
583
+ end
584
+
585
+ it { expect(subject.not(original)).to be == expected }
586
+
587
+ wrap_context 'when the scope has many child scopes' do
588
+ it { expect(subject.not(original)).to be == expected }
589
+ end
590
+ end
591
+ end
592
+ end
593
+ end
594
+
595
+ # Contract validating scope composition for criteria scopes.
596
+ module ShouldComposeScopesForCriteriaContract
597
+ extend RSpec::SleepingKingStudios::Contract
598
+ include Cuprum::Collections::RSpec::Contracts::Scopes::CompositionContracts # rubocop:disable Layout/LineLength
599
+
600
+ # @!method apply(example_group)
601
+ # Adds the contract to the example group.
602
+ #
603
+ # @param example_group [RSpec::Core::ExampleGroup] the example group to
604
+ # which the contract is applied.
605
+ contract do
606
+ shared_context 'when the scope has multiple criteria' do
607
+ let(:criteria) do
608
+ operators = Cuprum::Collections::Queries::Operators
609
+
610
+ [
611
+ [
612
+ 'author',
613
+ operators::EQUAL,
614
+ 'Ursula K. LeGuin'
615
+ ],
616
+ [
617
+ 'published_at',
618
+ operators::LESS_THAN,
619
+ '1970-01-01'
620
+ ]
621
+ ]
622
+ end
623
+ end
624
+
625
+ include_contract 'should compose scopes', except: %i[and not or]
626
+
627
+ describe '#and' do
628
+ describe 'with a block' do
629
+ let(:block) { ->(_) { { 'title' => 'A Wizard of Earthsea' } } }
630
+ let(:expected) do
631
+ operators = Cuprum::Collections::Queries::Operators
632
+ criteria = [
633
+ [
634
+ 'title',
635
+ operators::EQUAL,
636
+ 'A Wizard of Earthsea'
637
+ ]
638
+ ]
639
+
640
+ Cuprum::Collections::Scopes::CriteriaScope.new(
641
+ criteria: [*self.criteria, *criteria]
642
+ )
643
+ end
644
+
645
+ it { expect(subject.and(&block)).to be == expected }
646
+
647
+ wrap_context 'when the scope has multiple criteria' do
648
+ it { expect(subject.and(&block)).to be == expected }
649
+ end
650
+ end
651
+
652
+ describe 'with a hash' do
653
+ let(:value) { { 'title' => 'A Wizard of Earthsea' } }
654
+ let(:expected) do
655
+ operators = Cuprum::Collections::Queries::Operators
656
+ criteria = [
657
+ [
658
+ 'title',
659
+ operators::EQUAL,
660
+ 'A Wizard of Earthsea'
661
+ ]
662
+ ]
663
+
664
+ Cuprum::Collections::Scopes::CriteriaScope.new(
665
+ criteria: [*self.criteria, *criteria]
666
+ )
667
+ end
668
+
669
+ it { expect(subject.and(value)).to be == expected }
670
+
671
+ wrap_context 'when the scope has multiple criteria' do
672
+ it { expect(subject.and(value)).to be == expected }
673
+ end
674
+ end
675
+
676
+ wrap_context 'with an all scope' do
677
+ it { expect(subject.and(original)).to be == original }
678
+
679
+ wrap_context 'when the scope has multiple criteria' do
680
+ it { expect(subject.and(original)).to be subject }
681
+ end
682
+ end
683
+
684
+ wrap_context 'with a none scope' do
685
+ it { expect(subject.and(original)).to be == original }
686
+
687
+ wrap_context 'when the scope has multiple criteria' do
688
+ it { expect(subject.and(original)).to be == original }
689
+ end
690
+ end
691
+
692
+ wrap_context 'with an empty conjunction scope' do
693
+ it { expect(subject.and(original)).to be subject }
694
+
695
+ wrap_context 'when the scope has multiple criteria' do
696
+ it { expect(subject.and(original)).to be subject }
697
+ end
698
+ end
699
+
700
+ wrap_context 'with an empty criteria scope' do
701
+ it { expect(subject.and(original)).to be subject }
702
+
703
+ wrap_context 'when the scope has multiple criteria' do
704
+ it { expect(subject.and(original)).to be subject }
705
+ end
706
+ end
707
+
708
+ context 'with an empty inverted criteria scope' do
709
+ include_context 'with an empty criteria scope'
710
+
711
+ let(:inverted) { original.invert }
712
+
713
+ it { expect(subject.and(inverted)).to be subject }
714
+
715
+ wrap_context 'when the scope has multiple criteria' do
716
+ it { expect(subject.and(original)).to be subject }
717
+ end
718
+ end
719
+
720
+ wrap_context 'with an empty disjunction scope' do
721
+ it { expect(subject.and(original)).to be subject }
722
+
723
+ wrap_context 'when the scope has multiple criteria' do
724
+ it { expect(subject.and(original)).to be subject }
725
+ end
726
+ end
727
+
728
+ wrap_context 'with a non-empty conjunction scope' do
729
+ it { expect(subject.and(original)).to be == original }
730
+
731
+ wrap_context 'when the scope has multiple criteria' do
732
+ let(:expected) do
733
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
734
+ scopes: [subject, *original.scopes]
735
+ )
736
+ end
737
+
738
+ it { expect(subject.and(original)).to be == expected }
739
+ end
740
+ end
741
+
742
+ wrap_context 'with a non-empty criteria scope' do
743
+ let(:expected) do
744
+ Cuprum::Collections::Scopes::CriteriaScope.new(
745
+ criteria: [*subject.criteria, *original.criteria]
746
+ )
747
+ end
748
+
749
+ it { expect(subject.and(original)).to be == expected }
750
+
751
+ wrap_context 'when the scope has multiple criteria' do
752
+ it { expect(subject.and(original)).to be == expected }
753
+ end
754
+ end
755
+
756
+ context 'with a non-empty inverted criteria scope' do
757
+ include_context 'with a non-empty criteria scope'
758
+
759
+ let(:inverted) { original.invert }
760
+
761
+ it { expect(subject.and(inverted)).to be == inverted }
762
+
763
+ wrap_context 'when the scope has multiple criteria' do
764
+ let(:expected) do
765
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
766
+ scopes: [subject, inverted]
767
+ )
768
+ end
769
+
770
+ it { expect(subject.and(inverted)).to be == expected }
771
+ end
772
+ end
773
+
774
+ wrap_context 'with a non-empty disjunction scope' do
775
+ it { expect(subject.and(original)).to be == original }
776
+
777
+ wrap_context 'when the scope has multiple criteria' do
778
+ let(:expected) do
779
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
780
+ scopes: [subject, original]
781
+ )
782
+ end
783
+
784
+ it { expect(subject.and(original)).to be == expected }
785
+ end
786
+ end
787
+
788
+ wrap_context 'when initialized with inverted: true' do
789
+ describe 'with a block' do
790
+ let(:block) { -> { { 'title' => 'A Wizard of Earthsea' } } }
791
+ let(:expected) do
792
+ Cuprum::Collections::Scope.new(&block)
793
+ end
794
+
795
+ it { expect(subject.and(&block)).to be == expected }
796
+
797
+ wrap_context 'when the scope has multiple criteria' do
798
+ let(:expected) do
799
+ wrapped = Cuprum::Collections::Scope.new(&block)
800
+
801
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
802
+ scopes: [subject, wrapped]
803
+ )
804
+ end
805
+
806
+ it { expect(subject.and(&block)).to be == expected }
807
+ end
808
+ end
809
+
810
+ describe 'with a hash' do
811
+ let(:value) { { 'title' => 'A Wizard of Earthsea' } }
812
+ let(:expected) do
813
+ Cuprum::Collections::Scope.new(value)
814
+ end
815
+
816
+ it { expect(subject.and(value)).to be == expected }
817
+
818
+ wrap_context 'when the scope has multiple criteria' do
819
+ let(:expected) do
820
+ wrapped = Cuprum::Collections::Scope.new(value)
821
+
822
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
823
+ scopes: [subject, wrapped]
824
+ )
825
+ end
826
+
827
+ it { expect(subject.and(value)).to be == expected }
828
+ end
829
+ end
830
+
831
+ wrap_context 'with an empty criteria scope' do
832
+ it { expect(subject.and(original)).to be subject }
833
+
834
+ wrap_context 'when the scope has multiple criteria' do
835
+ it { expect(subject.and(original)).to be subject }
836
+ end
837
+ end
838
+
839
+ context 'with an empty inverted criteria scope' do
840
+ include_context 'with an empty criteria scope'
841
+
842
+ let(:inverted) { original.invert }
843
+
844
+ it { expect(subject.and(inverted)).to be subject }
845
+
846
+ wrap_context 'when the scope has multiple criteria' do
847
+ it { expect(subject.and(inverted)).to be subject }
848
+ end
849
+ end
850
+
851
+ wrap_context 'with a non-empty criteria scope' do
852
+ it { expect(subject.and(original)).to be == original }
853
+
854
+ wrap_context 'when the scope has multiple criteria' do
855
+ let(:expected) do
856
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
857
+ scopes: [subject, original]
858
+ )
859
+ end
860
+
861
+ it { expect(subject.and(original)).to be == expected }
862
+ end
863
+ end
864
+
865
+ context 'with a non-empty inverted criteria scope' do
866
+ include_context 'with a non-empty criteria scope'
867
+
868
+ let(:inverted) { original.invert }
869
+
870
+ it { expect(subject.and(inverted)).to be == inverted }
871
+
872
+ wrap_context 'when the scope has multiple criteria' do
873
+ let(:expected) do
874
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
875
+ scopes: [subject, inverted]
876
+ )
877
+ end
878
+
879
+ it { expect(subject.and(inverted)).to be == expected }
880
+ end
881
+ end
882
+ end
883
+ end
884
+
885
+ describe '#not' do
886
+ describe 'with a block' do
887
+ let(:block) { -> { { 'title' => 'A Wizard of Earthsea' } } }
888
+ let(:expected) do
889
+ Cuprum::Collections::Scope.new(&block).invert
890
+ end
891
+
892
+ it { expect(subject.not(&block)).to be == expected }
893
+
894
+ wrap_context 'when the scope has multiple criteria' do
895
+ let(:expected) do
896
+ wrapped = Cuprum::Collections::Scope.new(&block)
897
+
898
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
899
+ scopes: [subject, wrapped.invert]
900
+ )
901
+ end
902
+
903
+ it { expect(subject.not(&block)).to be == expected }
904
+ end
905
+ end
906
+
907
+ describe 'with a hash' do
908
+ let(:value) { { 'title' => 'A Wizard of Earthsea' } }
909
+ let(:expected) do
910
+ Cuprum::Collections::Scope.new(value).invert
911
+ end
912
+
913
+ it { expect(subject.not(value)).to be == expected }
914
+
915
+ wrap_context 'when the scope has multiple criteria' do
916
+ let(:expected) do
917
+ wrapped = Cuprum::Collections::Scope.new(value)
918
+
919
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
920
+ scopes: [subject, wrapped.invert]
921
+ )
922
+ end
923
+
924
+ it { expect(subject.not(value)).to be == expected }
925
+ end
926
+ end
927
+
928
+ wrap_context 'with an all scope' do
929
+ it { expect(subject.not(original)).to be == original.invert }
930
+
931
+ wrap_context 'when the scope has multiple criteria' do
932
+ it { expect(subject.not(original)).to be == original.invert }
933
+ end
934
+ end
935
+
936
+ wrap_context 'with a none scope' do
937
+ it { expect(subject.not(original)).to be == original.invert }
938
+
939
+ wrap_context 'when the scope has multiple criteria' do
940
+ it { expect(subject.not(original)).to be subject }
941
+ end
942
+ end
943
+
944
+ wrap_context 'with an empty conjunction scope' do
945
+ it { expect(subject.not(original)).to be subject }
946
+
947
+ wrap_context 'when the scope has multiple criteria' do
948
+ it { expect(subject.not(original)).to be subject }
949
+ end
950
+ end
951
+
952
+ wrap_context 'with an empty criteria scope' do
953
+ it { expect(subject.not(original)).to be subject }
954
+
955
+ wrap_context 'when the scope has multiple criteria' do
956
+ it { expect(subject.not(original)).to be subject }
957
+ end
958
+ end
959
+
960
+ context 'with an empty inverted criteria scope' do
961
+ include_context 'with an empty criteria scope'
962
+
963
+ let(:inverted) { original.invert }
964
+
965
+ it { expect(subject.not(inverted)).to be subject }
966
+
967
+ wrap_context 'when the scope has multiple criteria' do
968
+ it { expect(subject.not(original)).to be subject }
969
+ end
970
+ end
971
+
972
+ wrap_context 'with an empty disjunction scope' do
973
+ it { expect(subject.not(original)).to be subject }
974
+
975
+ wrap_context 'when the scope has multiple criteria' do
976
+ it { expect(subject.not(original)).to be subject }
977
+ end
978
+ end
979
+
980
+ wrap_context 'with a non-empty conjunction scope' do
981
+ it { expect(subject.not(original)).to be == original.invert }
982
+
983
+ wrap_context 'when the scope has multiple criteria' do
984
+ let(:expected) do
985
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
986
+ scopes: [subject, original.invert]
987
+ )
988
+ end
989
+
990
+ it { expect(subject.not(original)).to be == expected }
991
+ end
992
+ end
993
+
994
+ wrap_context 'with a non-empty criteria scope' do
995
+ it { expect(subject.not(original)).to be == original.invert }
996
+
997
+ wrap_context 'when the scope has multiple criteria' do
998
+ let(:expected) do
999
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
1000
+ scopes: [subject, original.invert]
1001
+ )
1002
+ end
1003
+
1004
+ it { expect(subject.not(original)).to be == expected }
1005
+ end
1006
+ end
1007
+
1008
+ context 'with a non-empty inverted criteria scope' do
1009
+ include_context 'with a non-empty criteria scope'
1010
+
1011
+ let(:inverted) { original.invert }
1012
+
1013
+ it { expect(subject.not(inverted)).to be == original }
1014
+
1015
+ wrap_context 'when the scope has multiple criteria' do
1016
+ let(:expected) do
1017
+ Cuprum::Collections::Scopes::CriteriaScope.new(
1018
+ criteria: [*subject.criteria, *original.criteria]
1019
+ )
1020
+ end
1021
+
1022
+ it { expect(subject.not(inverted)).to be == expected }
1023
+ end
1024
+ end
1025
+
1026
+ wrap_context 'with a non-empty disjunction scope' do
1027
+ it { expect(subject.not(original)).to be == original.invert }
1028
+
1029
+ wrap_context 'when the scope has multiple criteria' do
1030
+ let(:expected) do
1031
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
1032
+ scopes: [subject, *original.invert.scopes]
1033
+ )
1034
+ end
1035
+
1036
+ it { expect(subject.not(original)).to be == expected }
1037
+ end
1038
+ end
1039
+
1040
+ wrap_context 'when initialized with inverted: true' do
1041
+ describe 'with a block' do
1042
+ let(:block) { -> { { 'title' => 'A Wizard of Earthsea' } } }
1043
+ let(:expected) do
1044
+ Cuprum::Collections::Scope.new(&block).invert
1045
+ end
1046
+
1047
+ it { expect(subject.not(&block)).to be == expected }
1048
+
1049
+ wrap_context 'when the scope has multiple criteria' do
1050
+ let(:expected) do
1051
+ wrapped = Cuprum::Collections::Scope.new(&block)
1052
+
1053
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
1054
+ scopes: [subject, wrapped.invert]
1055
+ )
1056
+ end
1057
+
1058
+ it { expect(subject.not(&block)).to be == expected }
1059
+ end
1060
+ end
1061
+
1062
+ describe 'with a hash' do
1063
+ let(:value) { { 'title' => 'A Wizard of Earthsea' } }
1064
+ let(:expected) do
1065
+ Cuprum::Collections::Scope.new(value).invert
1066
+ end
1067
+
1068
+ it { expect(subject.not(value)).to be == expected }
1069
+
1070
+ wrap_context 'when the scope has multiple criteria' do
1071
+ let(:expected) do
1072
+ wrapped = Cuprum::Collections::Scope.new(value)
1073
+
1074
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
1075
+ scopes: [subject, wrapped.invert]
1076
+ )
1077
+ end
1078
+
1079
+ it { expect(subject.not(value)).to be == expected }
1080
+ end
1081
+ end
1082
+
1083
+ wrap_context 'with an all scope' do
1084
+ it { expect(subject.not(original)).to be == original.invert }
1085
+
1086
+ wrap_context 'when the scope has multiple criteria' do
1087
+ it { expect(subject.not(original)).to be == original.invert }
1088
+ end
1089
+ end
1090
+
1091
+ wrap_context 'with a none scope' do
1092
+ it { expect(subject.not(original)).to be == original.invert }
1093
+
1094
+ wrap_context 'when the scope has multiple criteria' do
1095
+ it { expect(subject.not(original)).to be subject }
1096
+ end
1097
+ end
1098
+
1099
+ wrap_context 'with an empty conjunction scope' do
1100
+ it { expect(subject.not(original)).to be subject }
1101
+
1102
+ wrap_context 'when the scope has multiple criteria' do
1103
+ it { expect(subject.not(original)).to be subject }
1104
+ end
1105
+ end
1106
+
1107
+ wrap_context 'with an empty criteria scope' do
1108
+ it { expect(subject.not(original)).to be subject }
1109
+
1110
+ wrap_context 'when the scope has multiple criteria' do
1111
+ it { expect(subject.not(original)).to be subject }
1112
+ end
1113
+ end
1114
+
1115
+ context 'with an empty inverted criteria scope' do
1116
+ include_context 'with an empty criteria scope'
1117
+
1118
+ let(:inverted) { original.invert }
1119
+
1120
+ it { expect(subject.not(inverted)).to be subject }
1121
+
1122
+ wrap_context 'when the scope has multiple criteria' do
1123
+ it { expect(subject.not(original)).to be subject }
1124
+ end
1125
+ end
1126
+
1127
+ wrap_context 'with an empty disjunction scope' do
1128
+ it { expect(subject.not(original)).to be subject }
1129
+
1130
+ wrap_context 'when the scope has multiple criteria' do
1131
+ it { expect(subject.not(original)).to be subject }
1132
+ end
1133
+ end
1134
+
1135
+ wrap_context 'with a non-empty conjunction scope' do
1136
+ it { expect(subject.not(original)).to be == original.invert }
1137
+
1138
+ wrap_context 'when the scope has multiple criteria' do
1139
+ let(:expected) do
1140
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
1141
+ scopes: [subject, original.invert]
1142
+ )
1143
+ end
1144
+
1145
+ it { expect(subject.not(original)).to be == expected }
1146
+ end
1147
+ end
1148
+
1149
+ wrap_context 'with a non-empty criteria scope' do
1150
+ it { expect(subject.not(original)).to be == original.invert }
1151
+
1152
+ wrap_context 'when the scope has multiple criteria' do
1153
+ let(:expected) do
1154
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
1155
+ scopes: [subject, original.invert]
1156
+ )
1157
+ end
1158
+
1159
+ it { expect(subject.not(original)).to be == expected }
1160
+ end
1161
+ end
1162
+
1163
+ context 'with a non-empty inverted criteria scope' do
1164
+ include_context 'with a non-empty criteria scope'
1165
+
1166
+ let(:inverted) { original.invert }
1167
+
1168
+ it { expect(subject.not(inverted)).to be == original }
1169
+
1170
+ wrap_context 'when the scope has multiple criteria' do
1171
+ let(:expected) do
1172
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
1173
+ scopes: [subject, original]
1174
+ )
1175
+ end
1176
+
1177
+ it { expect(subject.not(inverted)).to be == expected }
1178
+ end
1179
+ end
1180
+
1181
+ wrap_context 'with a non-empty disjunction scope' do
1182
+ it { expect(subject.not(original)).to be == original.invert }
1183
+
1184
+ wrap_context 'when the scope has multiple criteria' do
1185
+ let(:expected) do
1186
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
1187
+ scopes: [subject, *original.invert.scopes]
1188
+ )
1189
+ end
1190
+
1191
+ it { expect(subject.not(original)).to be == expected }
1192
+ end
1193
+ end
1194
+ end
1195
+ end
1196
+
1197
+ describe '#or' do
1198
+ describe 'with a block' do
1199
+ let(:block) { -> { { 'title' => 'A Wizard of Earthsea' } } }
1200
+ let(:expected) do
1201
+ Cuprum::Collections::Scope.new(&block)
1202
+ end
1203
+
1204
+ it { expect(subject.or(&block)).to be == expected }
1205
+
1206
+ wrap_context 'when the scope has multiple criteria' do
1207
+ let(:expected) do
1208
+ Cuprum::Collections::Scopes::DisjunctionScope.new(
1209
+ scopes: [subject, super()]
1210
+ )
1211
+ end
1212
+
1213
+ it { expect(subject.or(&block)).to be == expected }
1214
+ end
1215
+ end
1216
+
1217
+ describe 'with a hash' do
1218
+ let(:value) { { 'title' => 'A Wizard of Earthsea' } }
1219
+ let(:expected) do
1220
+ Cuprum::Collections::Scope.new(value)
1221
+ end
1222
+
1223
+ it { expect(subject.or(value)).to be == expected }
1224
+
1225
+ wrap_context 'when the scope has multiple criteria' do
1226
+ let(:expected) do
1227
+ Cuprum::Collections::Scopes::DisjunctionScope.new(
1228
+ scopes: [subject, super()]
1229
+ )
1230
+ end
1231
+
1232
+ it { expect(subject.or(value)).to be == expected }
1233
+ end
1234
+ end
1235
+
1236
+ wrap_context 'with an all scope' do
1237
+ it { expect(subject.or(original)).to be == original }
1238
+
1239
+ wrap_context 'when the scope has multiple criteria' do
1240
+ it { expect(subject.or(original)).to be == original }
1241
+ end
1242
+ end
1243
+
1244
+ wrap_context 'with a none scope' do
1245
+ it { expect(subject.or(original)).to be subject }
1246
+
1247
+ wrap_context 'when the scope has multiple criteria' do
1248
+ it { expect(subject.or(original)).to be subject }
1249
+ end
1250
+ end
1251
+
1252
+ wrap_context 'with an empty conjunction scope' do
1253
+ it { expect(subject.or(original)).to be subject }
1254
+
1255
+ wrap_context 'when the scope has multiple criteria' do
1256
+ it { expect(subject.or(original)).to be subject }
1257
+ end
1258
+ end
1259
+
1260
+ wrap_context 'with an empty criteria scope' do
1261
+ it { expect(subject.or(original)).to be subject }
1262
+
1263
+ wrap_context 'when the scope has multiple criteria' do
1264
+ it { expect(subject.or(original)).to be subject }
1265
+ end
1266
+ end
1267
+
1268
+ wrap_context 'with an empty disjunction scope' do
1269
+ it { expect(subject.or(original)).to be subject }
1270
+
1271
+ wrap_context 'when the scope has multiple criteria' do
1272
+ it { expect(subject.or(original)).to be subject }
1273
+ end
1274
+ end
1275
+
1276
+ wrap_context 'with a non-empty conjunction scope' do
1277
+ it { expect(subject.or(original)).to be == original }
1278
+
1279
+ wrap_context 'when the scope has multiple criteria' do
1280
+ let(:expected) do
1281
+ Cuprum::Collections::Scopes::DisjunctionScope.new(
1282
+ scopes: [subject, original]
1283
+ )
1284
+ end
1285
+
1286
+ it { expect(subject.or(original)).to be == expected }
1287
+ end
1288
+ end
1289
+
1290
+ wrap_context 'with a non-empty criteria scope' do
1291
+ it { expect(subject.or(original)).to be == original }
1292
+
1293
+ wrap_context 'when the scope has multiple criteria' do
1294
+ let(:expected) do
1295
+ Cuprum::Collections::Scopes::DisjunctionScope.new(
1296
+ scopes: [subject, original]
1297
+ )
1298
+ end
1299
+
1300
+ it { expect(subject.or(original)).to be == expected }
1301
+ end
1302
+ end
1303
+
1304
+ wrap_context 'with a non-empty disjunction scope' do
1305
+ it { expect(subject.or(original)).to be == original }
1306
+
1307
+ wrap_context 'when the scope has multiple criteria' do
1308
+ let(:expected) do
1309
+ Cuprum::Collections::Scopes::DisjunctionScope.new(
1310
+ scopes: [subject, *original.scopes]
1311
+ )
1312
+ end
1313
+
1314
+ it { expect(subject.or(original)).to be == expected }
1315
+ end
1316
+ end
1317
+ end
1318
+ end
1319
+ end
1320
+
1321
+ # Contract validating scope composition for disjunction scopes.
1322
+ module ShouldComposeScopesForDisjunctionContract
1323
+ extend RSpec::SleepingKingStudios::Contract
1324
+ include Cuprum::Collections::RSpec::Contracts::Scopes::CompositionContracts # rubocop:disable Layout/LineLength
1325
+
1326
+ # @!method apply(example_group)
1327
+ # Adds the contract to the example group.
1328
+ #
1329
+ # @param example_group [RSpec::Core::ExampleGroup] the example group to
1330
+ # which the contract is applied.
1331
+ contract do
1332
+ shared_context 'when the scope has many child scopes' do
1333
+ let(:scopes) do
1334
+ [
1335
+ build_scope({ 'author' => 'J.R.R. Tolkien' }),
1336
+ build_scope({ 'series' => 'The Lord of the Rings' }),
1337
+ build_scope do |scope|
1338
+ { 'published_at' => scope.less_than('1955-01-01') }
1339
+ end
1340
+ ]
1341
+ end
1342
+ end
1343
+
1344
+ include_contract 'should compose scopes', except: %i[or]
1345
+
1346
+ describe '#or' do
1347
+ describe 'with a block' do
1348
+ let(:block) { -> { { 'title' => 'A Wizard of Earthsea' } } }
1349
+ let(:expected) do
1350
+ wrapped = Cuprum::Collections::Scope.new(&block)
1351
+
1352
+ Cuprum::Collections::Scopes::DisjunctionScope.new(
1353
+ scopes: [*subject.scopes, wrapped]
1354
+ )
1355
+ end
1356
+
1357
+ it { expect(subject.or(&block)).to be == expected }
1358
+ end
1359
+
1360
+ describe 'with a hash' do
1361
+ let(:value) { { 'title' => 'A Wizard of Earthsea' } }
1362
+ let(:expected) do
1363
+ wrapped = Cuprum::Collections::Scope.new(value)
1364
+
1365
+ Cuprum::Collections::Scopes::DisjunctionScope.new(
1366
+ scopes: [*subject.scopes, wrapped]
1367
+ )
1368
+ end
1369
+
1370
+ it { expect(subject.or(value)).to be == expected }
1371
+ end
1372
+
1373
+ wrap_context 'with an all scope' do
1374
+ it { expect(subject.or(original)).to be == original }
1375
+ end
1376
+
1377
+ wrap_context 'with a none scope' do
1378
+ it { expect(subject.or(original)).to be subject }
1379
+ end
1380
+
1381
+ wrap_context 'with an empty conjunction scope' do
1382
+ it { expect(subject.or(original)).to be subject }
1383
+ end
1384
+
1385
+ wrap_context 'with an empty criteria scope' do
1386
+ it { expect(subject.or(original)).to be subject }
1387
+ end
1388
+
1389
+ wrap_context 'with an empty disjunction scope' do
1390
+ it { expect(subject.or(original)).to be subject }
1391
+ end
1392
+
1393
+ wrap_context 'with a non-empty conjunction scope' do
1394
+ let(:expected) do
1395
+ Cuprum::Collections::Scopes::DisjunctionScope.new(
1396
+ scopes: [*subject.scopes, original]
1397
+ )
1398
+ end
1399
+
1400
+ it { expect(subject.or(original)).to be == expected }
1401
+ end
1402
+
1403
+ wrap_context 'with a non-empty criteria scope' do
1404
+ let(:expected) do
1405
+ Cuprum::Collections::Scopes::DisjunctionScope.new(
1406
+ scopes: [*subject.scopes, original]
1407
+ )
1408
+ end
1409
+
1410
+ it { expect(subject.or(original)).to be == expected }
1411
+ end
1412
+
1413
+ wrap_context 'with a non-empty disjunction scope' do
1414
+ let(:expected) do
1415
+ Cuprum::Collections::Scopes::DisjunctionScope.new(
1416
+ scopes: [*subject.scopes, *original.scopes]
1417
+ )
1418
+ end
1419
+
1420
+ it { expect(subject.or(original)).to be == expected }
1421
+
1422
+ wrap_context 'when the scope has many child scopes' do
1423
+ it { expect(subject.or(original)).to be == expected }
1424
+ end
1425
+ end
1426
+ end
1427
+ end
1428
+ end
1429
+ end
1430
+ end