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,1029 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections/rspec/contracts'
4
+ require 'cuprum/collections/rspec/fixtures'
5
+ require 'cuprum/collections/scope'
6
+
7
+ module Cuprum::Collections::RSpec::Contracts
8
+ # Contracts for asserting on scope objects.
9
+ module ScopeContracts
10
+ # Contract validating the behavior of a scope implementation.
11
+ module ShouldBeAScopeContract
12
+ extend RSpec::SleepingKingStudios::Contract
13
+
14
+ # @!method apply(example_group, invertible: false)
15
+ # Adds the contract to the example group.
16
+ #
17
+ # @param example_group [RSpec::Core::ExampleGroup] the example group to
18
+ # which the contract is applied.
19
+ # @param invertible [Boolean] if true, the scope defines an
20
+ # implementation of the #invert method. Defaults to false.
21
+ contract do |invertible: false|
22
+ describe '#==' do
23
+ it { expect(subject == nil).to be false } # rubocop:disable Style/NilComparison
24
+
25
+ it { expect(subject == Object.new.freeze).to be false }
26
+
27
+ describe 'with a scope of different type' do
28
+ let(:other) { Spec::OtherScope.new }
29
+
30
+ example_class 'Spec::OtherScope',
31
+ Cuprum::Collections::Scopes::Base \
32
+ do |klass|
33
+ klass.define_method(:type) { :invalid }
34
+ end
35
+
36
+ it { expect(subject == other).to be false }
37
+ end
38
+ end
39
+
40
+ describe '#as_json' do
41
+ it { expect(subject).to respond_to(:as_json).with(0).arguments }
42
+
43
+ it { expect(subject.as_json).to be_a Hash }
44
+
45
+ it { expect(subject.as_json['type']).to be subject.type }
46
+ end
47
+
48
+ describe '#empty?' do
49
+ include_examples 'should define predicate', :empty?, -> { be_boolean }
50
+ end
51
+
52
+ describe '#invert' do
53
+ let(:error_class) do
54
+ Cuprum::Collections::Scopes::Base::UninvertibleScopeException
55
+ end
56
+ let(:error_message) do
57
+ "Scope class #{described_class} does not implement #invert"
58
+ end
59
+
60
+ it { expect(subject).to respond_to(:invert).with(0).arguments }
61
+
62
+ next if invertible
63
+
64
+ it 'should raise an exception' do
65
+ expect { subject.invert }.to raise_error error_class, error_message
66
+ end
67
+ end
68
+
69
+ describe '#type' do
70
+ include_examples 'should define reader', :type, -> { be_a(Symbol) }
71
+ end
72
+ end
73
+ end
74
+
75
+ # Contract validating the behavior of a Container scope implementation.
76
+ module ShouldBeAContainerScopeContract
77
+ extend RSpec::SleepingKingStudios::Contract
78
+
79
+ # @!method apply(example_group, invertible: false)
80
+ # Adds the contract to the example group.
81
+ #
82
+ # @param example_group [RSpec::Core::ExampleGroup] the example group to
83
+ # which the contract is applied.
84
+ # @param invertible [Boolean] if true, the scope defines an
85
+ # implementation of the #invert method. Defaults to false.
86
+ contract do |invertible: false|
87
+ shared_context 'with scopes' do
88
+ let(:scopes) do
89
+ [
90
+ build_scope({ 'title' => 'J.R.R. Tolkien' }),
91
+ build_scope({ 'series' => 'The Lord of the Rings' }),
92
+ build_scope({ 'category' => 'Science Fiction and Fantasy' })
93
+ ]
94
+ end
95
+ end
96
+
97
+ describe '.new' do
98
+ it 'should define the constructor' do
99
+ expect(described_class)
100
+ .to be_constructible
101
+ .with(0).arguments
102
+ .and_keywords(:scopes)
103
+ .and_any_keywords
104
+ end
105
+ end
106
+
107
+ include_contract('should be a scope', invertible:)
108
+
109
+ describe '#==' do
110
+ describe 'with a scope with the same class' do
111
+ let(:other) { described_class.new(scopes: other_scopes) }
112
+
113
+ describe 'with empty scopes' do
114
+ let(:other_scopes) { [] }
115
+
116
+ it { expect(subject == other).to be true }
117
+ end
118
+
119
+ describe 'with non-matching scopes' do
120
+ let(:other_scopes) do
121
+ Array.new(3) do
122
+ Cuprum::Collections::Scope.new({ 'ok' => true })
123
+ end
124
+ end
125
+
126
+ it { expect(subject == other).to be false }
127
+ end
128
+
129
+ wrap_context 'with scopes' do
130
+ describe 'with empty scopes' do
131
+ let(:other_scopes) { [] }
132
+
133
+ it { expect(subject == other).to be false }
134
+ end
135
+
136
+ describe 'with non-matching scopes' do
137
+ let(:other_scopes) do
138
+ Array.new(3) do
139
+ Cuprum::Collections::Scope.new({ 'ok' => true })
140
+ end
141
+ end
142
+
143
+ it { expect(subject == other).to be false }
144
+ end
145
+
146
+ describe 'with matching scopes' do
147
+ let(:other_scopes) { subject.scopes }
148
+
149
+ it { expect(subject == other).to be true }
150
+ end
151
+ end
152
+ end
153
+
154
+ describe 'with a scope with the same type' do
155
+ let(:other) { Spec::CustomScope.new(scopes: other_scopes) }
156
+
157
+ example_class 'Spec::CustomScope',
158
+ Cuprum::Collections::Scopes::Base \
159
+ do |klass|
160
+ klass.include Cuprum::Collections::Scopes::Container
161
+ end
162
+
163
+ before(:example) do
164
+ allow(other).to receive(:type).and_return(scope.type)
165
+ end
166
+
167
+ describe 'with empty scopes' do
168
+ let(:other_scopes) { [] }
169
+
170
+ it { expect(subject == other).to be true }
171
+ end
172
+
173
+ describe 'with non-matching scopes' do
174
+ let(:other_scopes) do
175
+ Array.new(3) do
176
+ Cuprum::Collections::Scope.new({ 'ok' => true })
177
+ end
178
+ end
179
+
180
+ it { expect(subject == other).to be false }
181
+ end
182
+
183
+ wrap_context 'with scopes' do
184
+ describe 'with empty scopes' do
185
+ let(:other_scopes) { [] }
186
+
187
+ it { expect(subject == other).to be false }
188
+ end
189
+
190
+ describe 'with non-matching scopes' do
191
+ let(:other_scopes) do
192
+ Array.new(3) do
193
+ Cuprum::Collections::Scope.new({ 'ok' => true })
194
+ end
195
+ end
196
+
197
+ it { expect(subject == other).to be false }
198
+ end
199
+
200
+ describe 'with matching scopes' do
201
+ let(:other_scopes) { subject.scopes }
202
+
203
+ it { expect(subject == other).to be true }
204
+ end
205
+ end
206
+ end
207
+ end
208
+
209
+ describe '#as_json' do
210
+ it { expect(subject.as_json['scopes']).to be == [] }
211
+
212
+ wrap_context 'with scopes' do
213
+ let(:expected) { subject.scopes.map(&:as_json) }
214
+
215
+ it { expect(subject.as_json['scopes']).to be == expected }
216
+ end
217
+ end
218
+
219
+ describe '#empty?' do
220
+ it { expect(subject.empty?).to be true }
221
+
222
+ wrap_context 'with scopes' do
223
+ it { expect(subject.empty?).to be false }
224
+ end
225
+ end
226
+
227
+ describe '#scopes' do
228
+ include_examples 'should define reader', :scopes, -> { scopes }
229
+
230
+ wrap_context 'with scopes' do
231
+ it { expect(subject.scopes).to be == scopes }
232
+ end
233
+ end
234
+
235
+ describe '#with_scopes' do
236
+ let(:new_scopes) do
237
+ [
238
+ described_class.new(scopes: []),
239
+ described_class.new(scopes: [])
240
+ ]
241
+ end
242
+
243
+ it { expect(subject).to respond_to(:with_scopes).with(1).arguments }
244
+
245
+ it 'should return a scope' do
246
+ expect(subject.with_scopes(new_scopes)).to be_a described_class
247
+ end
248
+
249
+ it "should not change the original scope's child scopes" do
250
+ expect { subject.with_scopes(new_scopes) }
251
+ .not_to change(subject, :scopes)
252
+ end
253
+
254
+ it "should set the copied scope's child scopes" do
255
+ expect(subject.with_scopes(new_scopes).scopes)
256
+ .to be == new_scopes
257
+ end
258
+
259
+ wrap_context 'with scopes' do
260
+ it "should not change the original scope's child scopes" do
261
+ expect { subject.with_scopes(new_scopes) }
262
+ .not_to change(subject, :scopes)
263
+ end
264
+
265
+ it "should set the copied scope's child scopes" do
266
+ expect(subject.with_scopes(new_scopes).scopes)
267
+ .to be == new_scopes
268
+ end
269
+ end
270
+ end
271
+ end
272
+ end
273
+
274
+ # Contract validating the behavior of an All scope implementation.
275
+ module ShouldBeAnAllScopeContract
276
+ extend RSpec::SleepingKingStudios::Contract
277
+
278
+ # @!method apply(example_group, abstract: false)
279
+ # Adds the contract to the example group.
280
+ #
281
+ # @param example_group [RSpec::Core::ExampleGroup] the example group to
282
+ # which the contract is applied.
283
+ # @param abstract [Boolean] if true, the scope is abstract and does not
284
+ # define a #call implementation. Defaults to false.
285
+ contract do |abstract: false|
286
+ include_contract 'should be a scope', invertible: true
287
+
288
+ describe '#==' do
289
+ describe 'with a scope with the same class' do
290
+ let(:other) { described_class.new }
291
+
292
+ it { expect(subject == other).to be true }
293
+ end
294
+
295
+ describe 'with a scope with the same type' do
296
+ let(:other) { Spec::CustomScope.new }
297
+
298
+ example_class 'Spec::CustomScope',
299
+ Cuprum::Collections::Scopes::Base \
300
+ do |klass|
301
+ klass.define_method(:type) { :all }
302
+ end
303
+
304
+ it { expect(subject == other).to be true }
305
+ end
306
+ end
307
+
308
+ describe '#and' do
309
+ it 'should define the method' do
310
+ expect(subject)
311
+ .to respond_to(:and)
312
+ .with(0..1).arguments
313
+ .and_a_block
314
+ end
315
+
316
+ it { expect(subject).to have_aliased_method(:and).as(:where) }
317
+
318
+ describe 'with a block' do
319
+ let(:block) { -> { { 'title' => 'A Wizard of Earthsea' } } }
320
+ let(:expected) do
321
+ Cuprum::Collections::Scope.new(&block)
322
+ end
323
+
324
+ it { expect(subject.and(&block)).to be == expected }
325
+ end
326
+
327
+ describe 'with a hash' do
328
+ let(:value) { { 'title' => 'A Wizard of Earthsea' } }
329
+ let(:expected) do
330
+ Cuprum::Collections::Scope.new(value)
331
+ end
332
+
333
+ it { expect(subject.and(value)).to be == expected }
334
+ end
335
+
336
+ describe 'with an all scope' do
337
+ let(:original) do
338
+ Cuprum::Collections::Scopes::AllScope.new
339
+ end
340
+
341
+ it { expect(subject.and(original)).to be == original }
342
+ end
343
+
344
+ describe 'with a none scope' do
345
+ let(:original) do
346
+ Cuprum::Collections::Scopes::NoneScope.new
347
+ end
348
+
349
+ it { expect(subject.and(original)).to be == original }
350
+ end
351
+
352
+ describe 'with an empty conjunction scope' do
353
+ let(:original) do
354
+ Cuprum::Collections::Scopes::ConjunctionScope.new(scopes: [])
355
+ end
356
+
357
+ it { expect(subject.and(original)).to be subject }
358
+ end
359
+
360
+ describe 'with an empty criteria scope' do
361
+ let(:original) do
362
+ Cuprum::Collections::Scopes::CriteriaScope.new(criteria: [])
363
+ end
364
+
365
+ it { expect(subject.and(original)).to be subject }
366
+ end
367
+
368
+ describe 'with an empty disjunction scope' do
369
+ let(:original) do
370
+ Cuprum::Collections::Scopes::DisjunctionScope.new(scopes: [])
371
+ end
372
+
373
+ it { expect(subject.and(original)).to be subject }
374
+ end
375
+
376
+ describe 'with a non-empty conjunction scope' do
377
+ let(:original) do
378
+ wrapped =
379
+ Cuprum::Collections::Scope
380
+ .new({ 'title' => 'A Wizard of Earthsea' })
381
+
382
+ Cuprum::Collections::Scopes::ConjunctionScope
383
+ .new(scopes: [wrapped])
384
+ end
385
+
386
+ it { expect(subject.and(original)).to be == original }
387
+ end
388
+
389
+ describe 'with a non-empty criteria scope' do
390
+ let(:original) do
391
+ Cuprum::Collections::Scope
392
+ .new({ 'title' => 'A Wizard of Earthsea' })
393
+ end
394
+
395
+ it { expect(subject.and(original)).to be == original }
396
+ end
397
+
398
+ describe 'with a non-empty disjunction scope' do
399
+ let(:original) do
400
+ wrapped =
401
+ Cuprum::Collections::Scope
402
+ .new({ 'title' => 'A Wizard of Earthsea' })
403
+
404
+ Cuprum::Collections::Scopes::DisjunctionScope
405
+ .new(scopes: [wrapped])
406
+ end
407
+
408
+ it { expect(subject.and(original)).to be == original }
409
+ end
410
+ end
411
+
412
+ describe '#as_json' do
413
+ let(:expected) { { 'type' => subject.type } }
414
+
415
+ it { expect(subject.as_json).to be == expected }
416
+ end
417
+
418
+ describe '#call' do
419
+ shared_context 'with data' do
420
+ let(:data) do
421
+ Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
422
+ end
423
+ end
424
+
425
+ next if abstract
426
+
427
+ describe 'with empty data' do
428
+ let(:data) { [] }
429
+
430
+ it { expect(filtered_data).to be == [] }
431
+ end
432
+
433
+ wrap_context 'with data' do
434
+ let(:expected) { data }
435
+
436
+ it { expect(filtered_data).to match_array expected }
437
+ end
438
+ end
439
+
440
+ describe '#empty?' do
441
+ it { expect(subject.empty?).to be false }
442
+ end
443
+
444
+ describe '#invert' do
445
+ let(:expected) { Cuprum::Collections::Scopes::NoneScope.new }
446
+
447
+ it { expect(subject.invert).to be == expected }
448
+ end
449
+
450
+ describe '#not' do
451
+ it 'should define the method' do
452
+ expect(subject)
453
+ .to respond_to(:not)
454
+ .with(0..1).arguments
455
+ .and_a_block
456
+ end
457
+
458
+ describe 'with a block' do
459
+ let(:block) { -> { { 'title' => 'A Wizard of Earthsea' } } }
460
+ let(:expected) do
461
+ Cuprum::Collections::Scope.new(&block).invert
462
+ end
463
+
464
+ it { expect(subject.not(&block)).to be == expected }
465
+ end
466
+
467
+ describe 'with a hash' do
468
+ let(:value) { { 'title' => 'A Wizard of Earthsea' } }
469
+ let(:expected) do
470
+ Cuprum::Collections::Scope.new(value).invert
471
+ end
472
+
473
+ it { expect(subject.not(value)).to be == expected }
474
+ end
475
+
476
+ describe 'with an all scope' do
477
+ let(:original) do
478
+ Cuprum::Collections::Scopes::AllScope.new
479
+ end
480
+ let(:expected) do
481
+ Cuprum::Collections::Scopes::NoneScope.new
482
+ end
483
+
484
+ it { expect(subject.not(original)).to be == expected }
485
+ end
486
+
487
+ describe 'with a none scope' do
488
+ let(:original) do
489
+ Cuprum::Collections::Scopes::NoneScope.new
490
+ end
491
+
492
+ it { expect(subject.not(original)).to be == subject }
493
+ end
494
+
495
+ describe 'with an empty conjunction scope' do
496
+ let(:original) do
497
+ Cuprum::Collections::Scopes::ConjunctionScope.new(scopes: [])
498
+ end
499
+
500
+ it { expect(subject.not(original)).to be subject }
501
+ end
502
+
503
+ describe 'with an empty criteria scope' do
504
+ let(:original) do
505
+ Cuprum::Collections::Scopes::CriteriaScope.new(criteria: [])
506
+ end
507
+
508
+ it { expect(subject.not(original)).to be subject }
509
+ end
510
+
511
+ describe 'with an empty disjunction scope' do
512
+ let(:original) do
513
+ Cuprum::Collections::Scopes::DisjunctionScope.new(scopes: [])
514
+ end
515
+
516
+ it { expect(subject.not(original)).to be subject }
517
+ end
518
+
519
+ describe 'with a non-empty conjunction scope' do
520
+ let(:original) do
521
+ wrapped =
522
+ Cuprum::Collections::Scope
523
+ .new({ 'title' => 'A Wizard of Earthsea' })
524
+
525
+ Cuprum::Collections::Scopes::ConjunctionScope
526
+ .new(scopes: [wrapped])
527
+ end
528
+
529
+ it { expect(subject.not(original)).to be == original.invert }
530
+ end
531
+
532
+ describe 'with a non-empty criteria scope' do
533
+ let(:original) do
534
+ Cuprum::Collections::Scope
535
+ .new({ 'title' => 'A Wizard of Earthsea' })
536
+ end
537
+
538
+ it { expect(subject.not(original)).to be == original.invert }
539
+ end
540
+
541
+ describe 'with a non-empty disjunction scope' do
542
+ let(:original) do
543
+ wrapped =
544
+ Cuprum::Collections::Scope
545
+ .new({ 'title' => 'A Wizard of Earthsea' })
546
+
547
+ Cuprum::Collections::Scopes::DisjunctionScope
548
+ .new(scopes: [wrapped])
549
+ end
550
+
551
+ it { expect(subject.not(original)).to be == original.invert }
552
+ end
553
+ end
554
+
555
+ describe '#or' do
556
+ it 'should define the method' do
557
+ expect(subject)
558
+ .to respond_to(:or)
559
+ .with(0..1).arguments
560
+ .and_a_block
561
+ end
562
+
563
+ describe 'with a block' do
564
+ let(:block) { -> { { 'title' => 'A Wizard of Earthsea' } } }
565
+ let(:expected) do
566
+ Cuprum::Collections::Scope.new(&block)
567
+ end
568
+
569
+ it { expect(subject.or(&block)).to be == expected }
570
+ end
571
+
572
+ describe 'with a hash' do
573
+ let(:value) { { 'title' => 'A Wizard of Earthsea' } }
574
+ let(:expected) do
575
+ Cuprum::Collections::Scope.new(value)
576
+ end
577
+
578
+ it { expect(subject.or(value)).to be == expected }
579
+ end
580
+
581
+ describe 'with an all scope' do
582
+ let(:original) do
583
+ Cuprum::Collections::Scopes::AllScope.new
584
+ end
585
+
586
+ it { expect(subject.and(original)).to be == original }
587
+ end
588
+
589
+ describe 'with an empty conjunction scope' do
590
+ let(:original) do
591
+ Cuprum::Collections::Scopes::ConjunctionScope.new(scopes: [])
592
+ end
593
+
594
+ it { expect(subject.or(original)).to be subject }
595
+ end
596
+
597
+ describe 'with an empty criteria scope' do
598
+ let(:original) do
599
+ Cuprum::Collections::Scopes::CriteriaScope.new(criteria: [])
600
+ end
601
+
602
+ it { expect(subject.or(original)).to be subject }
603
+ end
604
+
605
+ describe 'with an empty disjunction scope' do
606
+ let(:original) do
607
+ Cuprum::Collections::Scopes::DisjunctionScope.new(scopes: [])
608
+ end
609
+
610
+ it { expect(subject.or(original)).to be subject }
611
+ end
612
+
613
+ describe 'with a non-empty conjunction scope' do
614
+ let(:original) do
615
+ wrapped =
616
+ Cuprum::Collections::Scope
617
+ .new({ 'title' => 'A Wizard of Earthsea' })
618
+
619
+ Cuprum::Collections::Scopes::ConjunctionScope
620
+ .new(scopes: [wrapped])
621
+ end
622
+
623
+ it { expect(subject.or(original)).to be == original }
624
+ end
625
+
626
+ describe 'with a non-empty criteria scope' do
627
+ let(:original) do
628
+ Cuprum::Collections::Scope
629
+ .new({ 'title' => 'A Wizard of Earthsea' })
630
+ end
631
+
632
+ it { expect(subject.or(original)).to be == original }
633
+ end
634
+
635
+ describe 'with a non-empty disjunction scope' do
636
+ let(:original) do
637
+ wrapped =
638
+ Cuprum::Collections::Scope
639
+ .new({ 'title' => 'A Wizard of Earthsea' })
640
+
641
+ Cuprum::Collections::Scopes::DisjunctionScope
642
+ .new(scopes: [wrapped])
643
+ end
644
+
645
+ it { expect(subject.or(original)).to be == original }
646
+ end
647
+ end
648
+
649
+ describe '#type' do
650
+ it { expect(subject.type).to be :all }
651
+ end
652
+ end
653
+ end
654
+
655
+ # Contract validating the behavior of a None scope implementation.
656
+ module ShouldBeANoneScopeContract
657
+ extend RSpec::SleepingKingStudios::Contract
658
+
659
+ # @!method apply(example_group, abstract: false)
660
+ # Adds the contract to the example group.
661
+ #
662
+ # @param example_group [RSpec::Core::ExampleGroup] the example group to
663
+ # which the contract is applied.
664
+ # @param abstract [Boolean] if true, the scope is abstract and does not
665
+ # define a #call implementation. Defaults to false.
666
+ contract do |abstract: false|
667
+ include_contract 'should be a scope', invertible: true
668
+
669
+ describe '#==' do
670
+ describe 'with a scope with the same class' do
671
+ let(:other) { described_class.new }
672
+
673
+ it { expect(subject == other).to be true }
674
+ end
675
+
676
+ describe 'with a scope with the same type' do
677
+ let(:other) { Spec::CustomScope.new }
678
+
679
+ example_class 'Spec::CustomScope',
680
+ Cuprum::Collections::Scopes::Base \
681
+ do |klass|
682
+ klass.define_method(:type) { :none }
683
+ end
684
+
685
+ it { expect(subject == other).to be true }
686
+ end
687
+ end
688
+
689
+ describe '#and' do
690
+ it 'should define the method' do
691
+ expect(subject)
692
+ .to respond_to(:and)
693
+ .with(0..1).arguments
694
+ .and_a_block
695
+ end
696
+
697
+ it { expect(subject).to have_aliased_method(:and).as(:where) }
698
+
699
+ describe 'with a block' do
700
+ let(:block) { -> { { 'title' => 'A Wizard of Earthsea' } } }
701
+
702
+ it { expect(subject.and(&block)).to be subject }
703
+ end
704
+
705
+ describe 'with a hash' do
706
+ let(:value) { { 'title' => 'A Wizard of Earthsea' } }
707
+
708
+ it { expect(subject.and(value)).to be subject }
709
+ end
710
+
711
+ describe 'with an all scope' do
712
+ let(:original) do
713
+ Cuprum::Collections::Scopes::AllScope.new
714
+ end
715
+
716
+ it { expect(subject.and(original)).to be subject }
717
+ end
718
+
719
+ describe 'with a none scope' do
720
+ let(:original) do
721
+ Cuprum::Collections::Scopes::NoneScope.new
722
+ end
723
+
724
+ it { expect(subject.and(original)).to be subject }
725
+ end
726
+
727
+ describe 'with an empty conjunction scope' do
728
+ let(:original) do
729
+ Cuprum::Collections::Scopes::ConjunctionScope.new(scopes: [])
730
+ end
731
+
732
+ it { expect(subject.and(original)).to be subject }
733
+ end
734
+
735
+ describe 'with an empty criteria scope' do
736
+ let(:original) do
737
+ Cuprum::Collections::Scopes::CriteriaScope.new(criteria: [])
738
+ end
739
+
740
+ it { expect(subject.and(original)).to be subject }
741
+ end
742
+
743
+ describe 'with an empty disjunction scope' do
744
+ let(:original) do
745
+ Cuprum::Collections::Scopes::DisjunctionScope.new(scopes: [])
746
+ end
747
+
748
+ it { expect(subject.and(original)).to be subject }
749
+ end
750
+
751
+ describe 'with a non-empty conjunction scope' do
752
+ let(:original) do
753
+ wrapped =
754
+ Cuprum::Collections::Scope
755
+ .new({ 'title' => 'A Wizard of Earthsea' })
756
+
757
+ Cuprum::Collections::Scopes::ConjunctionScope
758
+ .new(scopes: [wrapped])
759
+ end
760
+
761
+ it { expect(subject.and(original)).to be subject }
762
+ end
763
+
764
+ describe 'with a non-empty criteria scope' do
765
+ let(:original) do
766
+ Cuprum::Collections::Scope
767
+ .new({ 'title' => 'A Wizard of Earthsea' })
768
+ end
769
+
770
+ it { expect(subject.and(original)).to be subject }
771
+ end
772
+
773
+ describe 'with a non-empty disjunction scope' do
774
+ let(:original) do
775
+ wrapped =
776
+ Cuprum::Collections::Scope
777
+ .new({ 'title' => 'A Wizard of Earthsea' })
778
+
779
+ Cuprum::Collections::Scopes::DisjunctionScope
780
+ .new(scopes: [wrapped])
781
+ end
782
+
783
+ it { expect(subject.and(original)).to be subject }
784
+ end
785
+ end
786
+
787
+ describe '#as_json' do
788
+ let(:expected) { { 'type' => subject.type } }
789
+
790
+ it { expect(subject.as_json).to be == expected }
791
+ end
792
+
793
+ describe '#call' do
794
+ shared_context 'with data' do
795
+ let(:data) do
796
+ Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
797
+ end
798
+ end
799
+
800
+ next if abstract
801
+
802
+ describe 'with empty data' do
803
+ let(:data) { [] }
804
+
805
+ it { expect(filtered_data).to be == [] }
806
+ end
807
+
808
+ wrap_context 'with data' do
809
+ let(:expected) { [] }
810
+
811
+ it { expect(filtered_data).to match_array expected }
812
+ end
813
+ end
814
+
815
+ describe '#empty?' do
816
+ it { expect(subject.empty?).to be false }
817
+ end
818
+
819
+ describe '#invert' do
820
+ let(:expected) { Cuprum::Collections::Scopes::AllScope.new }
821
+
822
+ it { expect(subject.invert).to be == expected }
823
+ end
824
+
825
+ describe '#not' do
826
+ it 'should define the method' do
827
+ expect(subject)
828
+ .to respond_to(:not)
829
+ .with(0..1).arguments
830
+ .and_a_block
831
+ end
832
+
833
+ describe 'with a block' do
834
+ let(:block) { -> { { 'title' => 'A Wizard of Earthsea' } } }
835
+
836
+ it { expect(subject.not(&block)).to be subject }
837
+ end
838
+
839
+ describe 'with a hash' do
840
+ let(:value) { { 'title' => 'A Wizard of Earthsea' } }
841
+
842
+ it { expect(subject.not(value)).to be subject }
843
+ end
844
+
845
+ describe 'with an all scope' do
846
+ let(:original) do
847
+ Cuprum::Collections::Scopes::AllScope.new
848
+ end
849
+
850
+ it { expect(subject.not(original)).to be subject }
851
+ end
852
+
853
+ describe 'with a none scope' do
854
+ let(:original) do
855
+ Cuprum::Collections::Scopes::AllScope.new
856
+ end
857
+
858
+ it { expect(subject.not(original)).to be subject }
859
+ end
860
+
861
+ describe 'with an empty conjunction scope' do
862
+ let(:original) do
863
+ Cuprum::Collections::Scopes::ConjunctionScope.new(scopes: [])
864
+ end
865
+
866
+ it { expect(subject.not(original)).to be subject }
867
+ end
868
+
869
+ describe 'with an empty criteria scope' do
870
+ let(:original) do
871
+ Cuprum::Collections::Scopes::CriteriaScope.new(criteria: [])
872
+ end
873
+
874
+ it { expect(subject.not(original)).to be subject }
875
+ end
876
+
877
+ describe 'with an empty disjunction scope' do
878
+ let(:original) do
879
+ Cuprum::Collections::Scopes::DisjunctionScope.new(scopes: [])
880
+ end
881
+
882
+ it { expect(subject.not(original)).to be subject }
883
+ end
884
+
885
+ describe 'with a non-empty conjunction scope' do
886
+ let(:original) do
887
+ wrapped =
888
+ Cuprum::Collections::Scope
889
+ .new({ 'title' => 'A Wizard of Earthsea' })
890
+
891
+ Cuprum::Collections::Scopes::ConjunctionScope
892
+ .new(scopes: [wrapped])
893
+ end
894
+
895
+ it { expect(subject.not(original)).to be subject }
896
+ end
897
+
898
+ describe 'with a non-empty criteria scope' do
899
+ let(:original) do
900
+ Cuprum::Collections::Scope
901
+ .new({ 'title' => 'A Wizard of Earthsea' })
902
+ end
903
+
904
+ it { expect(subject.not(original)).to be subject }
905
+ end
906
+
907
+ describe 'with a non-empty disjunction scope' do
908
+ let(:original) do
909
+ wrapped =
910
+ Cuprum::Collections::Scope
911
+ .new({ 'title' => 'A Wizard of Earthsea' })
912
+
913
+ Cuprum::Collections::Scopes::DisjunctionScope
914
+ .new(scopes: [wrapped])
915
+ end
916
+
917
+ it { expect(subject.not(original)).to be subject }
918
+ end
919
+ end
920
+
921
+ describe '#or' do
922
+ it 'should define the method' do
923
+ expect(subject)
924
+ .to respond_to(:or)
925
+ .with(0..1).arguments
926
+ .and_a_block
927
+ end
928
+
929
+ describe 'with a block' do
930
+ let(:block) { -> { { 'title' => 'A Wizard of Earthsea' } } }
931
+ let(:expected) do
932
+ Cuprum::Collections::Scope.new(&block)
933
+ end
934
+
935
+ it { expect(subject.or(&block)).to be == expected }
936
+ end
937
+
938
+ describe 'with a hash' do
939
+ let(:value) { { 'title' => 'A Wizard of Earthsea' } }
940
+ let(:expected) do
941
+ Cuprum::Collections::Scope.new(value)
942
+ end
943
+
944
+ it { expect(subject.or(value)).to be == expected }
945
+ end
946
+
947
+ describe 'with an all scope' do
948
+ let(:original) do
949
+ Cuprum::Collections::Scopes::AllScope.new
950
+ end
951
+
952
+ it { expect(subject.or(original)).to be == original }
953
+ end
954
+
955
+ describe 'with a none scope' do
956
+ let(:original) do
957
+ Cuprum::Collections::Scopes::NoneScope.new
958
+ end
959
+
960
+ it { expect(subject.or(original)).to be == original }
961
+ end
962
+
963
+ describe 'with an empty conjunction scope' do
964
+ let(:original) do
965
+ Cuprum::Collections::Scopes::ConjunctionScope.new(scopes: [])
966
+ end
967
+
968
+ it { expect(subject.or(original)).to be subject }
969
+ end
970
+
971
+ describe 'with an empty criteria scope' do
972
+ let(:original) do
973
+ Cuprum::Collections::Scopes::CriteriaScope.new(criteria: [])
974
+ end
975
+
976
+ it { expect(subject.or(original)).to be subject }
977
+ end
978
+
979
+ describe 'with an empty disjunction scope' do
980
+ let(:original) do
981
+ Cuprum::Collections::Scopes::DisjunctionScope.new(scopes: [])
982
+ end
983
+
984
+ it { expect(subject.or(original)).to be subject }
985
+ end
986
+
987
+ describe 'with a non-empty conjunction scope' do
988
+ let(:original) do
989
+ wrapped =
990
+ Cuprum::Collections::Scope
991
+ .new({ 'title' => 'A Wizard of Earthsea' })
992
+
993
+ Cuprum::Collections::Scopes::ConjunctionScope
994
+ .new(scopes: [wrapped])
995
+ end
996
+
997
+ it { expect(subject.or(original)).to be == original }
998
+ end
999
+
1000
+ describe 'with a non-empty criteria scope' do
1001
+ let(:original) do
1002
+ Cuprum::Collections::Scope
1003
+ .new({ 'title' => 'A Wizard of Earthsea' })
1004
+ end
1005
+
1006
+ it { expect(subject.or(original)).to be == original }
1007
+ end
1008
+
1009
+ describe 'with a non-empty disjunction scope' do
1010
+ let(:original) do
1011
+ wrapped =
1012
+ Cuprum::Collections::Scope
1013
+ .new({ 'title' => 'A Wizard of Earthsea' })
1014
+
1015
+ Cuprum::Collections::Scopes::DisjunctionScope
1016
+ .new(scopes: [wrapped])
1017
+ end
1018
+
1019
+ it { expect(subject.or(original)).to be == original }
1020
+ end
1021
+ end
1022
+
1023
+ describe '#type' do
1024
+ it { expect(subject.type).to be :none }
1025
+ end
1026
+ end
1027
+ end
1028
+ end
1029
+ end