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,2217 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections/rspec/contracts/scopes'
4
+ require 'cuprum/collections/rspec/contracts/scope_contracts'
5
+ require 'cuprum/collections/rspec/contracts/scopes/composition_contracts'
6
+
7
+ module Cuprum::Collections::RSpec::Contracts::Scopes
8
+ # Contracts for asserting on criteria scope objects.
9
+ module CriteriaContracts
10
+ include Cuprum::Collections::RSpec::Contracts::ScopeContracts
11
+ include Cuprum::Collections::RSpec::Contracts::Scopes::CompositionContracts
12
+
13
+ # Contract validating the behavior of a Criteria scope implementation.
14
+ module ShouldBeACriteriaScopeContract
15
+ extend RSpec::SleepingKingStudios::Contract
16
+
17
+ # @!method apply(example_group, abstract: false, constructor: true, equality: true, ignore_uninvertible: false)
18
+ # Adds the contract to the example group.
19
+ #
20
+ # @param example_group [RSpec::Core::ExampleGroup] the example group to
21
+ # which the contract is applied.
22
+ # @param abstract [Boolean] if true, the scope is abstract and does not
23
+ # define a #call implementation. Defaults to false.
24
+ # @param equality [Boolean] if false, skips the specs for the equality
25
+ # operator #==. Defaults to true.
26
+ # @param ignore_uninvertible [Boolean] if true, skips the specs for
27
+ # uninvertible operators. Defaults to false.
28
+ contract do |
29
+ abstract: false,
30
+ equality: true,
31
+ constructor: true,
32
+ ignore_uninvertible: false
33
+ |
34
+ shared_context 'when initialized with inverted: true' do
35
+ let(:constructor_options) { super().merge(inverted: true) }
36
+ end
37
+
38
+ shared_context 'with criteria' do
39
+ let(:criteria) do
40
+ operators = Cuprum::Collections::Queries::Operators
41
+
42
+ [
43
+ ['title', operators::EQUAL, 'Gideon the Ninth'],
44
+ ['author', operators::EQUAL, 'Tamsyn Muir']
45
+ ]
46
+ end
47
+ end
48
+
49
+ let(:criteria) { [] }
50
+
51
+ describe '.new' do
52
+ next unless constructor
53
+
54
+ it 'should define the constructor' do
55
+ expect(described_class)
56
+ .to be_constructible
57
+ .with(0).arguments
58
+ .and_keywords(:criteria, :inverted)
59
+ .and_any_keywords
60
+ end
61
+ end
62
+
63
+ describe '.build' do
64
+ let(:value) { { 'title' => 'The Word for World is Forest' } }
65
+
66
+ def parse_criteria(*args, &block)
67
+ return described_class.build(&block).criteria if args.empty?
68
+
69
+ described_class.build(args.first, &block).criteria
70
+ end
71
+
72
+ it 'should define class method' do
73
+ expect(described_class)
74
+ .to respond_to(:build)
75
+ .with(0..1).arguments
76
+ .and_a_block
77
+ end
78
+
79
+ it { expect(described_class.build(value)).to be_a described_class }
80
+
81
+ include_contract 'should parse criteria'
82
+ end
83
+
84
+ describe '.parse' do
85
+ def parse_criteria(*args, &block)
86
+ return described_class.parse(&block) if args.empty?
87
+
88
+ described_class.parse(args.first, &block)
89
+ end
90
+
91
+ it 'should define class method' do
92
+ expect(described_class)
93
+ .to respond_to(:parse)
94
+ .with(0..1).arguments
95
+ .and_a_block
96
+ end
97
+
98
+ include_contract 'should parse criteria'
99
+ end
100
+
101
+ include_contract 'should be a scope', invertible: true
102
+
103
+ include_contract 'should compose scopes for criteria'
104
+
105
+ describe '#==' do
106
+ next unless equality
107
+
108
+ describe 'with a scope with the same class' do
109
+ let(:other_criteria) { [] }
110
+ let(:other_inverted) { false }
111
+ let(:other) do
112
+ described_class.new(
113
+ criteria: other_criteria,
114
+ inverted: other_inverted
115
+ )
116
+ end
117
+
118
+ describe 'with empty criteria' do
119
+ let(:other_criteria) { [] }
120
+
121
+ it { expect(subject == other).to be true }
122
+
123
+ describe 'with inverted: true' do
124
+ let(:other_inverted) { true }
125
+
126
+ it { expect(subject == other).to be false }
127
+ end
128
+ end
129
+
130
+ describe 'with non-matching criteria' do
131
+ let(:other_criteria) do
132
+ operators = Cuprum::Collections::Queries::Operators
133
+
134
+ [
135
+ [
136
+ 'ok',
137
+ operators::EQUAL,
138
+ true
139
+ ]
140
+ ]
141
+ end
142
+
143
+ it { expect(subject == other).to be false }
144
+
145
+ describe 'with inverted: true' do
146
+ let(:other_inverted) { true }
147
+
148
+ it { expect(subject == other).to be false }
149
+ end
150
+ end
151
+
152
+ wrap_context 'with criteria' do
153
+ describe 'with empty criteria' do
154
+ let(:other_criteria) { [] }
155
+
156
+ it { expect(subject == other).to be false }
157
+
158
+ describe 'with inverted: true' do
159
+ let(:other_inverted) { true }
160
+
161
+ it { expect(subject == other).to be false }
162
+ end
163
+ end
164
+
165
+ describe 'with non-matching criteria' do
166
+ let(:other_criteria) do
167
+ operators = Cuprum::Collections::Queries::Operators
168
+
169
+ [
170
+ [
171
+ 'ok',
172
+ operators::EQUAL,
173
+ true
174
+ ]
175
+ ]
176
+ end
177
+
178
+ it { expect(subject == other).to be false }
179
+
180
+ describe 'with inverted: true' do
181
+ let(:other_inverted) { true }
182
+
183
+ it { expect(subject == other).to be false }
184
+ end
185
+ end
186
+
187
+ describe 'with matching criteria' do
188
+ let(:other_criteria) { subject.criteria }
189
+
190
+ it { expect(subject == other).to be true }
191
+
192
+ describe 'with inverted: true' do
193
+ let(:other_inverted) { true }
194
+
195
+ it { expect(subject == other).to be false }
196
+ end
197
+ end
198
+ end
199
+
200
+ wrap_context 'when initialized with inverted: true' do
201
+ describe 'with empty criteria' do
202
+ let(:other_criteria) { [] }
203
+
204
+ it { expect(subject == other).to be false }
205
+
206
+ describe 'with inverted: true' do
207
+ let(:other_inverted) { true }
208
+
209
+ it { expect(subject == other).to be true }
210
+ end
211
+ end
212
+
213
+ describe 'with non-matching criteria' do
214
+ let(:other_criteria) do
215
+ operators = Cuprum::Collections::Queries::Operators
216
+
217
+ [
218
+ [
219
+ 'ok',
220
+ operators::EQUAL,
221
+ true
222
+ ]
223
+ ]
224
+ end
225
+
226
+ it { expect(subject == other).to be false }
227
+
228
+ describe 'with inverted: true' do
229
+ let(:other_inverted) { true }
230
+
231
+ it { expect(subject == other).to be false }
232
+ end
233
+ end
234
+
235
+ wrap_context 'with criteria' do
236
+ describe 'with empty criteria' do
237
+ let(:other_criteria) { [] }
238
+
239
+ it { expect(subject == other).to be false }
240
+
241
+ describe 'with inverted: true' do
242
+ let(:other_inverted) { true }
243
+
244
+ it { expect(subject == other).to be false }
245
+ end
246
+ end
247
+
248
+ describe 'with non-matching criteria' do
249
+ let(:other_criteria) do
250
+ operators = Cuprum::Collections::Queries::Operators
251
+
252
+ [
253
+ [
254
+ 'ok',
255
+ operators::EQUAL,
256
+ true
257
+ ]
258
+ ]
259
+ end
260
+
261
+ it { expect(subject == other).to be false }
262
+
263
+ describe 'with inverted: true' do
264
+ let(:other_inverted) { true }
265
+
266
+ it { expect(subject == other).to be false }
267
+ end
268
+ end
269
+
270
+ describe 'with matching criteria' do
271
+ let(:other_criteria) { subject.criteria }
272
+
273
+ it { expect(subject == other).to be false }
274
+
275
+ describe 'with inverted: true' do
276
+ let(:other_inverted) { true }
277
+
278
+ it { expect(subject == other).to be true }
279
+ end
280
+ end
281
+ end
282
+ end
283
+ end
284
+
285
+ describe 'with a scope with the same type' do
286
+ let(:other_criteria) { [] }
287
+ let(:other_inverted) { false }
288
+ let(:other) do
289
+ described_class.new(
290
+ criteria: other_criteria,
291
+ inverted: other_inverted
292
+ )
293
+ end
294
+
295
+ example_class 'Spec::CustomScope',
296
+ Cuprum::Collections::Scopes::Base \
297
+ do |klass|
298
+ klass.include Cuprum::Collections::Scopes::Criteria
299
+ end
300
+
301
+ describe 'with empty criteria' do
302
+ let(:other_criteria) { [] }
303
+
304
+ it { expect(subject == other).to be true }
305
+
306
+ describe 'with inverted: true' do
307
+ let(:other_inverted) { true }
308
+
309
+ it { expect(subject == other).to be false }
310
+ end
311
+ end
312
+
313
+ describe 'with non-matching criteria' do
314
+ let(:other_criteria) do
315
+ operators = Cuprum::Collections::Queries::Operators
316
+
317
+ [
318
+ [
319
+ 'ok',
320
+ operators::EQUAL,
321
+ true
322
+ ]
323
+ ]
324
+ end
325
+
326
+ it { expect(subject == other).to be false }
327
+
328
+ describe 'with inverted: true' do
329
+ let(:other_inverted) { true }
330
+
331
+ it { expect(subject == other).to be false }
332
+ end
333
+ end
334
+
335
+ wrap_context 'with criteria' do
336
+ describe 'with empty criteria' do
337
+ let(:other_criteria) { [] }
338
+
339
+ it { expect(subject == other).to be false }
340
+
341
+ describe 'with inverted: true' do
342
+ let(:other_inverted) { true }
343
+
344
+ it { expect(subject == other).to be false }
345
+ end
346
+ end
347
+
348
+ describe 'with non-matching criteria' do
349
+ let(:other_criteria) do
350
+ operators = Cuprum::Collections::Queries::Operators
351
+
352
+ [
353
+ [
354
+ 'ok',
355
+ operators::EQUAL,
356
+ true
357
+ ]
358
+ ]
359
+ end
360
+
361
+ it { expect(subject == other).to be false }
362
+
363
+ describe 'with inverted: true' do
364
+ let(:other_inverted) { true }
365
+
366
+ it { expect(subject == other).to be false }
367
+ end
368
+ end
369
+
370
+ describe 'with matching criteria' do
371
+ let(:other_criteria) { subject.criteria }
372
+
373
+ it { expect(subject == other).to be true }
374
+
375
+ describe 'with inverted: true' do
376
+ let(:other_inverted) { true }
377
+
378
+ it { expect(subject == other).to be false }
379
+ end
380
+ end
381
+ end
382
+
383
+ wrap_context 'when initialized with inverted: true' do
384
+ describe 'with empty criteria' do
385
+ let(:other_criteria) { [] }
386
+
387
+ it { expect(subject == other).to be false }
388
+
389
+ describe 'with inverted: true' do
390
+ let(:other_inverted) { true }
391
+
392
+ it { expect(subject == other).to be true }
393
+ end
394
+ end
395
+
396
+ describe 'with non-matching criteria' do
397
+ let(:other_criteria) do
398
+ operators = Cuprum::Collections::Queries::Operators
399
+
400
+ [
401
+ [
402
+ 'ok',
403
+ operators::EQUAL,
404
+ true
405
+ ]
406
+ ]
407
+ end
408
+
409
+ it { expect(subject == other).to be false }
410
+
411
+ describe 'with inverted: true' do
412
+ let(:other_inverted) { true }
413
+
414
+ it { expect(subject == other).to be false }
415
+ end
416
+ end
417
+
418
+ wrap_context 'with criteria' do
419
+ describe 'with empty criteria' do
420
+ let(:other_criteria) { [] }
421
+
422
+ it { expect(subject == other).to be false }
423
+
424
+ describe 'with inverted: true' do
425
+ let(:other_inverted) { true }
426
+
427
+ it { expect(subject == other).to be false }
428
+ end
429
+ end
430
+
431
+ describe 'with non-matching criteria' do
432
+ let(:other_criteria) do
433
+ operators = Cuprum::Collections::Queries::Operators
434
+
435
+ [
436
+ [
437
+ 'ok',
438
+ operators::EQUAL,
439
+ true
440
+ ]
441
+ ]
442
+ end
443
+
444
+ it { expect(subject == other).to be false }
445
+
446
+ describe 'with inverted: true' do
447
+ let(:other_inverted) { true }
448
+
449
+ it { expect(subject == other).to be false }
450
+ end
451
+ end
452
+
453
+ describe 'with matching criteria' do
454
+ let(:other_criteria) { subject.criteria }
455
+
456
+ it { expect(subject == other).to be false }
457
+
458
+ describe 'with inverted: true' do
459
+ let(:other_inverted) { true }
460
+
461
+ it { expect(subject == other).to be true }
462
+ end
463
+ end
464
+ end
465
+ end
466
+ end
467
+ end
468
+
469
+ describe '#as_json' do
470
+ let(:expected) do
471
+ {
472
+ 'criteria' => subject.criteria,
473
+ 'inverted' => subject.inverted?,
474
+ 'type' => subject.type
475
+ }
476
+ end
477
+
478
+ it { expect(subject.as_json).to be == expected }
479
+
480
+ wrap_context 'when initialized with inverted: true' do
481
+ it { expect(subject.as_json).to be == expected }
482
+ end
483
+
484
+ wrap_context 'with criteria' do
485
+ it { expect(subject.as_json).to be == expected }
486
+
487
+ wrap_context 'when initialized with inverted: true' do
488
+ it { expect(subject.as_json).to be == expected }
489
+ end
490
+ end
491
+ end
492
+
493
+ describe '#call' do
494
+ next if abstract
495
+
496
+ it { expect(subject).to respond_to(:call) }
497
+
498
+ include_contract 'should filter data by criteria'
499
+
500
+ context 'when the scope is inverted' do
501
+ subject { super().invert }
502
+
503
+ let(:matching_data) { data - filtered_data }
504
+
505
+ include_contract 'should filter data by criteria',
506
+ ignore_invalid: true,
507
+ inverted: true
508
+ end
509
+ end
510
+
511
+ describe '#criteria' do
512
+ include_examples 'should define reader', :criteria, -> { criteria }
513
+
514
+ wrap_context 'with criteria' do
515
+ it { expect(subject.criteria).to be == criteria }
516
+ end
517
+ end
518
+
519
+ describe '#empty?' do
520
+ include_examples 'should define predicate', :empty?, true
521
+
522
+ wrap_context 'with criteria' do
523
+ it { expect(subject.empty?).to be false }
524
+ end
525
+ end
526
+
527
+ describe '#invert' do
528
+ shared_examples 'should invert the criteria' do
529
+ it { expect(copy.criteria).to be == [] }
530
+
531
+ context 'when the scope has an equality criterion' do
532
+ let(:criteria) do
533
+ described_class.parse({
534
+ 'title' => 'The Word for World is Forest'
535
+ })
536
+ end
537
+ let(:expected) do
538
+ operators = Cuprum::Collections::Queries::Operators
539
+
540
+ [
541
+ [
542
+ 'title',
543
+ operators::NOT_EQUAL,
544
+ 'The Word for World is Forest'
545
+ ]
546
+ ]
547
+ end
548
+
549
+ it { expect(copy.criteria).to be == expected }
550
+ end
551
+
552
+ context 'when the scope has a greater than criterion' do
553
+ let(:criteria) do
554
+ described_class.parse do |scope|
555
+ { 'published_at' => scope.greater_than('1972-03-13') }
556
+ end
557
+ end
558
+ let(:expected) do
559
+ operators = Cuprum::Collections::Queries::Operators
560
+
561
+ [
562
+ [
563
+ 'published_at',
564
+ operators::LESS_THAN_OR_EQUAL_TO,
565
+ '1972-03-13'
566
+ ]
567
+ ]
568
+ end
569
+
570
+ it { expect(copy.criteria).to be == expected }
571
+ end
572
+
573
+ context 'when the scope has a greater than or equal to criterion' do
574
+ let(:criteria) do
575
+ described_class.parse do |scope|
576
+ {
577
+ 'published_at' => scope.greater_than_or_equal_to(
578
+ '1972-03-13'
579
+ )
580
+ }
581
+ end
582
+ end
583
+ let(:expected) do
584
+ operators = Cuprum::Collections::Queries::Operators
585
+
586
+ [
587
+ [
588
+ 'published_at',
589
+ operators::LESS_THAN,
590
+ '1972-03-13'
591
+ ]
592
+ ]
593
+ end
594
+
595
+ it { expect(copy.criteria).to be == expected }
596
+ end
597
+
598
+ context 'when the scope has a less than criterion' do
599
+ let(:criteria) do
600
+ described_class.parse do |scope|
601
+ { 'published_at' => scope.less_than('1972-03-13') }
602
+ end
603
+ end
604
+ let(:expected) do
605
+ operators = Cuprum::Collections::Queries::Operators
606
+
607
+ [
608
+ [
609
+ 'published_at',
610
+ operators::GREATER_THAN_OR_EQUAL_TO,
611
+ '1972-03-13'
612
+ ]
613
+ ]
614
+ end
615
+
616
+ it { expect(copy.criteria).to be == expected }
617
+ end
618
+
619
+ context 'when the scope has a less than or equal to criterion' do
620
+ let(:criteria) do
621
+ described_class.parse do |scope|
622
+ {
623
+ 'published_at' => scope.less_than_or_equal_to(
624
+ '1972-03-13'
625
+ )
626
+ }
627
+ end
628
+ end
629
+ let(:expected) do
630
+ operators = Cuprum::Collections::Queries::Operators
631
+
632
+ [
633
+ [
634
+ 'published_at',
635
+ operators::GREATER_THAN,
636
+ '1972-03-13'
637
+ ]
638
+ ]
639
+ end
640
+
641
+ it { expect(copy.criteria).to be == expected }
642
+ end
643
+
644
+ context 'when the scope has a not equal criterion' do
645
+ let(:criteria) do
646
+ described_class.parse do |scope|
647
+ { 'author' => scope.not_equal('J.R.R. Tolkien') }
648
+ end
649
+ end
650
+ let(:expected) do
651
+ operators = Cuprum::Collections::Queries::Operators
652
+
653
+ [
654
+ [
655
+ 'author',
656
+ operators::EQUAL,
657
+ 'J.R.R. Tolkien'
658
+ ]
659
+ ]
660
+ end
661
+
662
+ it { expect(copy.criteria).to be == expected }
663
+ end
664
+
665
+ context 'when the scope has a not one of criterion' do
666
+ let(:criteria) do
667
+ described_class.parse do |scope|
668
+ titles = ['The Fellowship Of The Ring', 'The Two Towers']
669
+
670
+ { 'title' => scope.not_one_of(titles) }
671
+ end
672
+ end
673
+ let(:expected) do
674
+ operators = Cuprum::Collections::Queries::Operators
675
+
676
+ [
677
+ [
678
+ 'title',
679
+ operators::ONE_OF,
680
+ ['The Fellowship Of The Ring', 'The Two Towers']
681
+ ]
682
+ ]
683
+ end
684
+
685
+ it { expect(copy.criteria).to be == expected }
686
+ end
687
+
688
+ context 'when the scope has a one of criterion' do
689
+ let(:criteria) do
690
+ described_class.parse do |scope|
691
+ titles = ['The Fellowship Of The Ring', 'The Two Towers']
692
+
693
+ { 'title' => scope.one_of(titles) }
694
+ end
695
+ end
696
+ let(:expected) do
697
+ operators = Cuprum::Collections::Queries::Operators
698
+
699
+ [
700
+ [
701
+ 'title',
702
+ operators::NOT_ONE_OF,
703
+ ['The Fellowship Of The Ring', 'The Two Towers']
704
+ ]
705
+ ]
706
+ end
707
+
708
+ it { expect(copy.criteria).to be == expected }
709
+ end
710
+
711
+ context 'when the scope has multiple criteria' do
712
+ let(:criteria) do
713
+ described_class.parse do |scope|
714
+ titles = ['The Fellowship Of The Ring', 'The Two Towers']
715
+
716
+ {
717
+ 'author' => 'J.R.R. Tolkien',
718
+ 'title' => scope.not_one_of(titles)
719
+ }
720
+ end
721
+ end
722
+ let(:expected) do
723
+ operators = Cuprum::Collections::Queries::Operators
724
+
725
+ [
726
+ [
727
+ 'author',
728
+ operators::NOT_EQUAL,
729
+ 'J.R.R. Tolkien'
730
+ ],
731
+ [
732
+ 'title',
733
+ operators::ONE_OF,
734
+ ['The Fellowship Of The Ring', 'The Two Towers']
735
+ ]
736
+ ]
737
+ end
738
+
739
+ it { expect(copy.criteria).to be == expected }
740
+ end
741
+
742
+ context 'when initialized with uninvertible criteria' do
743
+ next if ignore_uninvertible
744
+
745
+ let(:criteria) do
746
+ [
747
+ [
748
+ 'title',
749
+ 'random',
750
+ nil
751
+ ]
752
+ ]
753
+ end
754
+ let(:error_class) do
755
+ Cuprum::Collections::Queries::UninvertibleOperatorException
756
+ end
757
+ let(:error_message) { 'uninvertible operator "random"' }
758
+
759
+ it 'should raise an exception' do
760
+ expect { subject.invert }
761
+ .to raise_error error_class, error_message
762
+ end
763
+ end
764
+ end
765
+
766
+ let(:copy) { subject.invert }
767
+
768
+ it { expect(copy).to be_a described_class }
769
+
770
+ it { expect(copy.inverted?).to be true }
771
+
772
+ include_examples 'should invert the criteria'
773
+
774
+ wrap_context 'when initialized with inverted: true' do
775
+ it { expect(copy).to be_a described_class }
776
+
777
+ it { expect(copy.inverted?).to be false }
778
+
779
+ include_examples 'should invert the criteria'
780
+ end
781
+ end
782
+
783
+ describe '#inverted?' do
784
+ include_examples 'should define predicate', :inverted?, false
785
+
786
+ wrap_context 'when initialized with inverted: true' do
787
+ it { expect(subject.inverted?).to be true }
788
+ end
789
+ end
790
+
791
+ describe '#type' do
792
+ include_examples 'should define reader', :type, :criteria
793
+ end
794
+
795
+ describe '#with_criteria' do
796
+ let(:new_criteria) { ['author', 'eq', 'Ursula K. LeGuin'] }
797
+
798
+ it { expect(subject).to respond_to(:with_criteria).with(1).argument }
799
+
800
+ it 'should return a scope' do
801
+ expect(subject.with_criteria(new_criteria)).to be_a described_class
802
+ end
803
+
804
+ it "should not change the original scope's criteria" do
805
+ expect { subject.with_criteria(new_criteria) }
806
+ .not_to change(subject, :criteria)
807
+ end
808
+
809
+ it "should set the copied scope's criteria" do
810
+ expect(subject.with_criteria(new_criteria).criteria)
811
+ .to be == new_criteria
812
+ end
813
+
814
+ it 'should return an uninverted scope' do
815
+ expect(subject.with_criteria(new_criteria).inverted?).to be false
816
+ end
817
+
818
+ wrap_context 'with criteria' do
819
+ it "should not change the original scope's criteria" do
820
+ expect { subject.with_criteria(new_criteria) }
821
+ .not_to change(subject, :criteria)
822
+ end
823
+
824
+ it "should set the copied scope's criteria" do
825
+ expect(subject.with_criteria(new_criteria).criteria)
826
+ .to be == new_criteria
827
+ end
828
+ end
829
+
830
+ wrap_context 'when initialized with inverted: true' do
831
+ it 'should return an inverted scope' do
832
+ expect(subject.with_criteria(new_criteria).inverted?).to be true
833
+ end
834
+ end
835
+ end
836
+ end
837
+ end
838
+
839
+ # Contract validating the scope filters data based on the criteria.
840
+ module ShouldFilterDataByCriteriaContract
841
+ extend RSpec::SleepingKingStudios::Contract
842
+
843
+ # @!method apply(example_group, **options)
844
+ # Adds the contract to the example group.
845
+ #
846
+ # @param example_group [RSpec::Core::ExampleGroup] the example group to
847
+ # which the contract is applied.
848
+ # @param options [Hash] additional options for the contract.
849
+ #
850
+ # @option options ignore_invalid [Boolean] if true, skips tests for
851
+ # handling invalid operators. Defaults to false.
852
+ # @option options inverted [Boolean] if true, filters data that does not
853
+ # match the criteria. Defaults to false.
854
+ contract do |ignore_invalid: false, inverted: false|
855
+ shared_context 'with data' do
856
+ let(:data) do
857
+ Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
858
+ end
859
+ end
860
+
861
+ let(:matching) { data }
862
+ let(:expected) { inverted ? data - matching : matching }
863
+
864
+ context 'when the scope has no criteria' do
865
+ describe 'with empty data' do
866
+ let(:data) { [] }
867
+
868
+ it { expect(filtered_data).to be == [] }
869
+ end
870
+
871
+ wrap_context 'with data' do
872
+ let(:matching) { data }
873
+
874
+ it { expect(filtered_data).to match_array expected }
875
+ end
876
+ end
877
+
878
+ context 'when the scope has an equality criterion' do
879
+ let(:criteria) do
880
+ described_class.parse({ 'title' => 'The Word for World is Forest' })
881
+ end
882
+
883
+ describe 'with empty data' do
884
+ let(:data) { [] }
885
+
886
+ it { expect(filtered_data).to be == [] }
887
+ end
888
+
889
+ wrap_context 'with data' do
890
+ let(:matching) do
891
+ data.select do |item|
892
+ item['title'] == 'The Word for World is Forest'
893
+ end
894
+ end
895
+
896
+ it { expect(filtered_data).to match_array expected }
897
+ end
898
+ end
899
+
900
+ context 'when the scope has a greater than criterion' do
901
+ let(:criteria) do
902
+ described_class.parse do |scope|
903
+ { 'published_at' => scope.greater_than('1972-03-13') }
904
+ end
905
+ end
906
+
907
+ describe 'with empty data' do
908
+ let(:data) { [] }
909
+
910
+ it { expect(filtered_data).to be == [] }
911
+ end
912
+
913
+ wrap_context 'with data' do
914
+ let(:matching) do
915
+ data.select do |item|
916
+ item['published_at'] > '1972-03-13'
917
+ end
918
+ end
919
+
920
+ it { expect(filtered_data).to match_array expected }
921
+ end
922
+ end
923
+
924
+ context 'when the scope has a greater than or equal to criterion' do
925
+ let(:criteria) do
926
+ described_class.parse do |scope|
927
+ { 'published_at' => scope.greater_than_or_equal_to('1972-03-13') }
928
+ end
929
+ end
930
+
931
+ describe 'with empty data' do
932
+ let(:data) { [] }
933
+
934
+ it { expect(filtered_data).to be == [] }
935
+ end
936
+
937
+ wrap_context 'with data' do
938
+ let(:matching) do
939
+ data.select do |item|
940
+ item['published_at'] >= '1972-03-13'
941
+ end
942
+ end
943
+
944
+ it { expect(filtered_data).to match_array expected }
945
+ end
946
+ end
947
+
948
+ context 'when the scope has a less than criterion' do
949
+ let(:criteria) do
950
+ described_class.parse do |scope|
951
+ { 'published_at' => scope.less_than('1972-03-13') }
952
+ end
953
+ end
954
+
955
+ describe 'with empty data' do
956
+ let(:data) { [] }
957
+
958
+ it { expect(filtered_data).to be == [] }
959
+ end
960
+
961
+ wrap_context 'with data' do
962
+ let(:matching) do
963
+ data.select do |item|
964
+ item['published_at'] < '1972-03-13'
965
+ end
966
+ end
967
+
968
+ it { expect(filtered_data).to match_array expected }
969
+ end
970
+ end
971
+
972
+ context 'when the scope has a less than or equal to criterion' do
973
+ let(:criteria) do
974
+ described_class.parse do |scope|
975
+ { 'published_at' => scope.less_than_or_equal_to('1972-03-13') }
976
+ end
977
+ end
978
+
979
+ describe 'with empty data' do
980
+ let(:data) { [] }
981
+
982
+ it { expect(filtered_data).to be == [] }
983
+ end
984
+
985
+ wrap_context 'with data' do
986
+ let(:matching) do
987
+ data.select do |item|
988
+ item['published_at'] <= '1972-03-13'
989
+ end
990
+ end
991
+
992
+ it { expect(filtered_data).to match_array expected }
993
+ end
994
+ end
995
+
996
+ context 'when the scope has a not equal criterion' do
997
+ let(:criteria) do
998
+ described_class.parse do |scope|
999
+ { 'author' => scope.not_equal('J.R.R. Tolkien') }
1000
+ end
1001
+ end
1002
+
1003
+ describe 'with empty data' do
1004
+ let(:data) { [] }
1005
+
1006
+ it { expect(filtered_data).to be == [] }
1007
+ end
1008
+
1009
+ wrap_context 'with data' do
1010
+ let(:matching) do
1011
+ data.reject do |item|
1012
+ item['author'] == 'J.R.R. Tolkien'
1013
+ end
1014
+ end
1015
+
1016
+ it { expect(filtered_data).to match_array expected }
1017
+ end
1018
+ end
1019
+
1020
+ context 'when the scope has a not one of criterion' do
1021
+ let(:criteria) do
1022
+ described_class.parse do |scope|
1023
+ titles = ['The Fellowship Of The Ring', 'The Two Towers']
1024
+
1025
+ { 'title' => scope.not_one_of(titles) }
1026
+ end
1027
+ end
1028
+
1029
+ describe 'with empty data' do
1030
+ let(:data) { [] }
1031
+
1032
+ it { expect(filtered_data).to be == [] }
1033
+ end
1034
+
1035
+ wrap_context 'with data' do
1036
+ let(:matching) do
1037
+ data.reject do |item|
1038
+ ['The Fellowship Of The Ring', 'The Two Towers']
1039
+ .include?(item['title'])
1040
+ end
1041
+ end
1042
+
1043
+ it { expect(filtered_data).to match_array expected }
1044
+ end
1045
+ end
1046
+
1047
+ context 'when the scope has a one of criterion' do
1048
+ let(:criteria) do
1049
+ described_class.parse do |scope|
1050
+ titles = ['The Fellowship Of The Ring', 'The Two Towers']
1051
+
1052
+ { 'title' => scope.one_of(titles) }
1053
+ end
1054
+ end
1055
+
1056
+ describe 'with empty data' do
1057
+ let(:data) { [] }
1058
+
1059
+ it { expect(filtered_data).to be == [] }
1060
+ end
1061
+
1062
+ wrap_context 'with data' do
1063
+ let(:matching) do
1064
+ data.select do |item|
1065
+ ['The Fellowship Of The Ring', 'The Two Towers']
1066
+ .include?(item['title'])
1067
+ end
1068
+ end
1069
+
1070
+ it { expect(filtered_data).to match_array expected }
1071
+ end
1072
+ end
1073
+
1074
+ context 'when the scope has mixed criteria' do
1075
+ let(:criteria) do
1076
+ described_class.parse do |scope|
1077
+ titles = ['The Fellowship Of The Ring', 'The Two Towers']
1078
+
1079
+ {
1080
+ 'author' => 'J.R.R. Tolkien',
1081
+ 'title' => scope.not_one_of(titles)
1082
+ }
1083
+ end
1084
+ end
1085
+
1086
+ describe 'with empty data' do
1087
+ let(:data) { [] }
1088
+
1089
+ it { expect(filtered_data).to be == [] }
1090
+ end
1091
+
1092
+ wrap_context 'with data' do
1093
+ let(:matching) do
1094
+ data
1095
+ .select { |item| item['author'] == 'J.R.R. Tolkien' }
1096
+ .reject do |item|
1097
+ ['The Fellowship Of The Ring', 'The Two Towers']
1098
+ .include?(item['title'])
1099
+ end
1100
+ end
1101
+
1102
+ it { expect(filtered_data).to match_array expected }
1103
+ end
1104
+ end
1105
+
1106
+ context 'when the scope has multiple matching criteria' do
1107
+ let(:criteria) do
1108
+ described_class.parse({
1109
+ 'author' => 'J.R.R. Tolkien',
1110
+ 'series' => 'The Lord of the Rings'
1111
+ })
1112
+ end
1113
+
1114
+ describe 'with empty data' do
1115
+ let(:data) { [] }
1116
+
1117
+ it { expect(filtered_data).to be == [] }
1118
+ end
1119
+
1120
+ wrap_context 'with data' do
1121
+ let(:matching) do
1122
+ data
1123
+ .select { |item| item['author'] == 'J.R.R. Tolkien' }
1124
+ .select { |item| item['series'] == 'The Lord of the Rings' }
1125
+ end
1126
+
1127
+ it { expect(filtered_data).to match_array expected }
1128
+ end
1129
+ end
1130
+
1131
+ context 'when the scope has non-overlapping criteria' do
1132
+ let(:criteria) do
1133
+ described_class.parse({
1134
+ 'author' => 'J.R.R. Tolkien',
1135
+ 'series' => 'Earthsea'
1136
+ })
1137
+ end
1138
+
1139
+ describe 'with empty data' do
1140
+ let(:data) { [] }
1141
+
1142
+ it { expect(filtered_data).to be == [] }
1143
+ end
1144
+
1145
+ wrap_context 'with data' do
1146
+ let(:matching) do
1147
+ data
1148
+ .select { |item| item['author'] == 'J.R.R. Tolkien' }
1149
+ .select { |item| item['series'] == 'Earthsea' }
1150
+ end
1151
+
1152
+ it { expect(filtered_data).to match_array expected }
1153
+ end
1154
+ end
1155
+
1156
+ context 'when the scope has invalid operators' do
1157
+ next if ignore_invalid
1158
+
1159
+ let(:criteria) { [['title', :random, nil]] }
1160
+ let(:error_class) do
1161
+ Cuprum::Collections::Queries::UnknownOperatorException
1162
+ end
1163
+ let(:error_message) do
1164
+ 'unknown operator "random"'
1165
+ end
1166
+
1167
+ describe 'with empty data' do
1168
+ let(:data) { [] }
1169
+
1170
+ it 'should return an empty result or raise an exception',
1171
+ :aggregate_failures \
1172
+ do
1173
+ # :nocov:
1174
+ begin
1175
+ actual = filtered_data
1176
+ rescue StandardError => exception
1177
+ expect(exception).to be_a error_class
1178
+ expect(exception.message).to be == error_message
1179
+
1180
+ next
1181
+ end
1182
+
1183
+ expect(actual).to be == []
1184
+ # :nocov:
1185
+ end
1186
+ end
1187
+
1188
+ wrap_context 'with data' do
1189
+ it 'should raise an exception' do
1190
+ expect { filtered_data }
1191
+ .to raise_error error_class, error_message
1192
+ end
1193
+ end
1194
+ end
1195
+ end
1196
+ end
1197
+
1198
+ # Contract validating the parsing of criteria.
1199
+ module ShouldParseCriteriaContract
1200
+ extend RSpec::SleepingKingStudios::Contract
1201
+
1202
+ # @!method apply(example_group)
1203
+ # Adds the contract to the example group.
1204
+ #
1205
+ # @param example_group [RSpec::Core::ExampleGroup] the example group to
1206
+ # which the contract is applied.
1207
+ contract do
1208
+ describe 'with a block' do
1209
+ include_contract 'should parse criteria from a block'
1210
+ end
1211
+
1212
+ describe 'with a hash' do
1213
+ include_contract 'should parse criteria from a hash'
1214
+ end
1215
+
1216
+ describe 'with a hash and a block' do
1217
+ let(:operators) { Cuprum::Collections::Queries::Operators }
1218
+ let(:block) do
1219
+ lambda do |scope|
1220
+ { 'author' => scope.one_of('J.R.R. Tolkien', 'Ursula K. LeGuin') }
1221
+ end
1222
+ end
1223
+ let(:value) do
1224
+ { 'category' => 'Science Fiction and Fantasy' }
1225
+ end
1226
+ let(:expected) do
1227
+ [
1228
+ [
1229
+ 'category',
1230
+ operators::EQUAL,
1231
+ 'Science Fiction and Fantasy'
1232
+ ],
1233
+ [
1234
+ 'author',
1235
+ operators::ONE_OF,
1236
+ ['J.R.R. Tolkien', 'Ursula K. LeGuin']
1237
+ ]
1238
+ ]
1239
+ end
1240
+
1241
+ it 'should parse the criteria' do
1242
+ expect(parse_criteria(value, &block)).to be == expected
1243
+ end
1244
+ end
1245
+ end
1246
+ end
1247
+
1248
+ # Contract validating the parsing of criteria from a Hash.
1249
+ module ShouldParseCriteriaFromABlockContract
1250
+ extend RSpec::SleepingKingStudios::Contract
1251
+
1252
+ # @!method apply(example_group)
1253
+ # Adds the contract to the example group.
1254
+ #
1255
+ # @param example_group [RSpec::Core::ExampleGroup] the example group to
1256
+ # which the contract is applied.
1257
+ contract do
1258
+ let(:operators) { Cuprum::Collections::Queries::Operators }
1259
+
1260
+ describe 'without a block' do
1261
+ let(:error_message) { 'no block given' }
1262
+
1263
+ it 'should raise an exception' do
1264
+ expect { parse_criteria }
1265
+ .to raise_error ArgumentError, error_message
1266
+ end
1267
+ end
1268
+
1269
+ describe 'with a block returning nil' do
1270
+ let(:error_message) do
1271
+ 'value must be a Hash with String or Symbol keys'
1272
+ end
1273
+
1274
+ it 'should raise an exception' do
1275
+ expect { parse_criteria { nil } }
1276
+ .to raise_error ArgumentError, error_message
1277
+ end
1278
+ end
1279
+
1280
+ describe 'with a block returning an Object' do
1281
+ let(:error_message) do
1282
+ 'value must be a Hash with String or Symbol keys'
1283
+ end
1284
+
1285
+ it 'should raise an exception' do
1286
+ expect { parse_criteria { Object.new.freeze } }
1287
+ .to raise_error ArgumentError, error_message
1288
+ end
1289
+ end
1290
+
1291
+ describe 'with a block returning a Hash with invalid keys' do
1292
+ let(:error_message) do
1293
+ 'value must be a Hash with String or Symbol keys'
1294
+ end
1295
+ let(:block) { -> { { nil => 'invalid' } } }
1296
+
1297
+ it 'should raise an exception' do
1298
+ expect { parse_criteria(&block) }
1299
+ .to raise_error ArgumentError, error_message
1300
+ end
1301
+ end
1302
+
1303
+ describe 'with a block returning an empty Hash' do
1304
+ let(:block) { -> { {} } }
1305
+
1306
+ it { expect(parse_criteria(&block)).to be == [] }
1307
+ end
1308
+
1309
+ describe 'with a block returning a Hash with one key' do
1310
+ let(:operators) { Cuprum::Collections::Queries::Operators }
1311
+ let(:block) do
1312
+ -> { { 'title' => 'A Wizard of Earthsea' } }
1313
+ end
1314
+ let(:expected) do
1315
+ [['title', operators::EQUAL, 'A Wizard of Earthsea']]
1316
+ end
1317
+
1318
+ it { expect(parse_criteria(&block)).to be == expected }
1319
+ end
1320
+
1321
+ describe 'with a Hash with many String keys' do
1322
+ let(:operators) { Cuprum::Collections::Queries::Operators }
1323
+ let(:block) do
1324
+ lambda do
1325
+ {
1326
+ 'title' => 'A Wizard of Earthsea',
1327
+ 'author' => 'Ursula K. LeGuin',
1328
+ 'series' => 'Earthsea'
1329
+ }
1330
+ end
1331
+ end
1332
+ let(:expected) do
1333
+ [
1334
+ ['title', operators::EQUAL, 'A Wizard of Earthsea'],
1335
+ ['author', operators::EQUAL, 'Ursula K. LeGuin'],
1336
+ ['series', operators::EQUAL, 'Earthsea']
1337
+ ]
1338
+ end
1339
+
1340
+ it { expect(parse_criteria(&block)).to be == expected }
1341
+ end
1342
+
1343
+ describe 'with a Hash with many Symbol keys' do
1344
+ let(:operators) { Cuprum::Collections::Queries::Operators }
1345
+ let(:block) do
1346
+ lambda do
1347
+ {
1348
+ title: 'A Wizard of Earthsea',
1349
+ author: 'Ursula K. LeGuin',
1350
+ series: 'Earthsea'
1351
+ }
1352
+ end
1353
+ end
1354
+ let(:expected) do
1355
+ [
1356
+ ['title', operators::EQUAL, 'A Wizard of Earthsea'],
1357
+ ['author', operators::EQUAL, 'Ursula K. LeGuin'],
1358
+ ['series', operators::EQUAL, 'Earthsea']
1359
+ ]
1360
+ end
1361
+
1362
+ it { expect(parse_criteria(&block)).to be == expected }
1363
+ end
1364
+
1365
+ describe 'with a Hash with an "eq" operator' do
1366
+ let(:block) do
1367
+ ->(scope) { { 'title' => scope.eq('A Wizard of Earthsea') } }
1368
+ end
1369
+ let(:expected) do
1370
+ [['title', operators::EQUAL, 'A Wizard of Earthsea']]
1371
+ end
1372
+
1373
+ it 'should parse the criteria' do
1374
+ expect(parse_criteria(&block)).to be == expected
1375
+ end
1376
+ end
1377
+
1378
+ describe 'with a Hash with an "equal" operator' do
1379
+ let(:block) do
1380
+ ->(scope) { { 'title' => scope.equal('A Wizard of Earthsea') } }
1381
+ end
1382
+ let(:expected) do
1383
+ [['title', operators::EQUAL, 'A Wizard of Earthsea']]
1384
+ end
1385
+
1386
+ it 'should parse the criteria' do
1387
+ expect(parse_criteria(&block)).to be == expected
1388
+ end
1389
+ end
1390
+
1391
+ describe 'with a Hash with an "equals" operator' do
1392
+ let(:block) do
1393
+ ->(scope) { { 'title' => scope.equals('A Wizard of Earthsea') } }
1394
+ end
1395
+ let(:expected) do
1396
+ [['title', operators::EQUAL, 'A Wizard of Earthsea']]
1397
+ end
1398
+
1399
+ it 'should parse the criteria' do
1400
+ expect(parse_criteria(&block)).to be == expected
1401
+ end
1402
+ end
1403
+
1404
+ describe 'with a Hash with a "greater_than" operator' do
1405
+ let(:block) do
1406
+ ->(scope) { { 'published_at' => scope.greater_than('1970-01-01') } }
1407
+ end
1408
+ let(:expected) do
1409
+ [['published_at', operators::GREATER_THAN, '1970-01-01']]
1410
+ end
1411
+
1412
+ it 'should parse the criteria' do
1413
+ expect(parse_criteria(&block)).to be == expected
1414
+ end
1415
+ end
1416
+
1417
+ describe 'with a Hash with a "greater_than_or_equal_to" operator' do
1418
+ let(:block) do
1419
+ lambda do |scope|
1420
+ { 'published_at' => scope.greater_than_or_equal_to('1970-01-01') }
1421
+ end
1422
+ end
1423
+ let(:expected) do
1424
+ [
1425
+ [
1426
+ 'published_at',
1427
+ operators::GREATER_THAN_OR_EQUAL_TO,
1428
+ '1970-01-01'
1429
+ ]
1430
+ ]
1431
+ end
1432
+
1433
+ it 'should parse the criteria' do
1434
+ expect(parse_criteria(&block)).to be == expected
1435
+ end
1436
+ end
1437
+
1438
+ describe 'with a Hash with a "gt" operator' do
1439
+ let(:block) do
1440
+ ->(scope) { { 'published_at' => scope.gt('1970-01-01') } }
1441
+ end
1442
+ let(:expected) do
1443
+ [['published_at', operators::GREATER_THAN, '1970-01-01']]
1444
+ end
1445
+
1446
+ it 'should parse the criteria' do
1447
+ expect(parse_criteria(&block)).to be == expected
1448
+ end
1449
+ end
1450
+
1451
+ describe 'with a Hash with a "gte" operator' do
1452
+ let(:block) do
1453
+ ->(scope) { { 'published_at' => scope.gte('1970-01-01') } }
1454
+ end
1455
+ let(:expected) do
1456
+ [
1457
+ [
1458
+ 'published_at',
1459
+ operators::GREATER_THAN_OR_EQUAL_TO,
1460
+ '1970-01-01'
1461
+ ]
1462
+ ]
1463
+ end
1464
+
1465
+ it 'should parse the criteria' do
1466
+ expect(parse_criteria(&block)).to be == expected
1467
+ end
1468
+ end
1469
+
1470
+ describe 'with a Hash with a "less_than" operator' do
1471
+ let(:block) do
1472
+ ->(scope) { { 'published_at' => scope.less_than('1970-01-01') } }
1473
+ end
1474
+ let(:expected) do
1475
+ [['published_at', operators::LESS_THAN, '1970-01-01']]
1476
+ end
1477
+
1478
+ it 'should parse the criteria' do
1479
+ expect(parse_criteria(&block)).to be == expected
1480
+ end
1481
+ end
1482
+
1483
+ describe 'with a Hash with a "less_than_or_equal_to" operator' do
1484
+ let(:block) do
1485
+ lambda do |scope|
1486
+ { 'published_at' => scope.less_than_or_equal_to('1970-01-01') }
1487
+ end
1488
+ end
1489
+ let(:expected) do
1490
+ [['published_at', operators::LESS_THAN_OR_EQUAL_TO, '1970-01-01']]
1491
+ end
1492
+
1493
+ it 'should parse the criteria' do
1494
+ expect(parse_criteria(&block)).to be == expected
1495
+ end
1496
+ end
1497
+
1498
+ describe 'with a Hash with a "lt" operator' do
1499
+ let(:block) do
1500
+ ->(scope) { { 'published_at' => scope.lt('1970-01-01') } }
1501
+ end
1502
+ let(:expected) do
1503
+ [['published_at', operators::LESS_THAN, '1970-01-01']]
1504
+ end
1505
+
1506
+ it 'should parse the criteria' do
1507
+ expect(parse_criteria(&block)).to be == expected
1508
+ end
1509
+ end
1510
+
1511
+ describe 'with a Hash with a "lte" operator' do
1512
+ let(:block) do
1513
+ ->(scope) { { 'published_at' => scope.lte('1970-01-01') } }
1514
+ end
1515
+ let(:expected) do
1516
+ [['published_at', operators::LESS_THAN_OR_EQUAL_TO, '1970-01-01']]
1517
+ end
1518
+
1519
+ it 'should parse the criteria' do
1520
+ expect(parse_criteria(&block)).to be == expected
1521
+ end
1522
+ end
1523
+
1524
+ describe 'with a Hash with a "ne" operator' do
1525
+ let(:block) do
1526
+ ->(scope) { { 'series' => scope.ne('Earthsea') } }
1527
+ end
1528
+ let(:expected) do
1529
+ [['series', operators::NOT_EQUAL, 'Earthsea']]
1530
+ end
1531
+
1532
+ it 'should parse the criteria' do
1533
+ expect(parse_criteria(&block)).to be == expected
1534
+ end
1535
+ end
1536
+
1537
+ describe 'with a Hash with a "not_equal" operator' do
1538
+ let(:block) do
1539
+ ->(scope) { { 'series' => scope.not_equal('Earthsea') } }
1540
+ end
1541
+ let(:expected) do
1542
+ [['series', operators::NOT_EQUAL, 'Earthsea']]
1543
+ end
1544
+
1545
+ it 'should parse the criteria' do
1546
+ expect(parse_criteria(&block)).to be == expected
1547
+ end
1548
+ end
1549
+
1550
+ describe 'with a Hash with a "not_one_of" operator and an Array' do
1551
+ let(:block) do
1552
+ lambda do |scope|
1553
+ {
1554
+ 'series' => scope.not_one_of(
1555
+ ['Earthsea', 'The Lord of the Rings']
1556
+ )
1557
+ }
1558
+ end
1559
+ end
1560
+ let(:expected) do
1561
+ [
1562
+ [
1563
+ 'series',
1564
+ operators::NOT_ONE_OF,
1565
+ ['Earthsea', 'The Lord of the Rings']
1566
+ ]
1567
+ ]
1568
+ end
1569
+
1570
+ it 'should parse the criteria' do
1571
+ expect(parse_criteria(&block)).to be == expected
1572
+ end
1573
+ end
1574
+
1575
+ describe 'with a Hash with a "not_one_of" operator and values' do
1576
+ let(:block) do
1577
+ lambda do |scope|
1578
+ {
1579
+ 'series' => scope.not_one_of(
1580
+ 'Earthsea',
1581
+ 'The Lord of the Rings'
1582
+ )
1583
+ }
1584
+ end
1585
+ end
1586
+ let(:expected) do
1587
+ [
1588
+ [
1589
+ 'series',
1590
+ operators::NOT_ONE_OF,
1591
+ ['Earthsea', 'The Lord of the Rings']
1592
+ ]
1593
+ ]
1594
+ end
1595
+
1596
+ it 'should parse the criteria' do
1597
+ expect(parse_criteria(&block)).to be == expected
1598
+ end
1599
+ end
1600
+
1601
+ describe 'with a Hash with a "one_of" operator and an Array' do
1602
+ let(:block) do
1603
+ lambda do |scope|
1604
+ {
1605
+ 'series' => scope.one_of(
1606
+ ['Earthsea', 'The Lord of the Rings']
1607
+ )
1608
+ }
1609
+ end
1610
+ end
1611
+ let(:expected) do
1612
+ [
1613
+ [
1614
+ 'series',
1615
+ operators::ONE_OF,
1616
+ ['Earthsea', 'The Lord of the Rings']
1617
+ ]
1618
+ ]
1619
+ end
1620
+
1621
+ it 'should parse the criteria' do
1622
+ expect(parse_criteria(&block)).to be == expected
1623
+ end
1624
+ end
1625
+
1626
+ describe 'with a Hash with a "one_of" operator and values' do
1627
+ let(:block) do
1628
+ lambda do |scope|
1629
+ {
1630
+ 'series' => scope.one_of(
1631
+ 'Earthsea',
1632
+ 'The Lord of the Rings'
1633
+ )
1634
+ }
1635
+ end
1636
+ end
1637
+ let(:expected) do
1638
+ [
1639
+ [
1640
+ 'series',
1641
+ operators::ONE_OF,
1642
+ ['Earthsea', 'The Lord of the Rings']
1643
+ ]
1644
+ ]
1645
+ end
1646
+
1647
+ it 'should parse the criteria' do
1648
+ expect(parse_criteria(&block)).to be == expected
1649
+ end
1650
+ end
1651
+
1652
+ describe 'with a Hash with an unknown operator' do
1653
+ let(:error_class) do
1654
+ Cuprum::Collections::Queries::UnknownOperatorException
1655
+ end
1656
+ let(:error_message) do
1657
+ 'unknown operator "random"'
1658
+ end
1659
+ let(:block) do
1660
+ -> { { 'genre' => random('Science Fiction', 'Fantasy') } }
1661
+ end
1662
+
1663
+ it 'should raise an exception' do
1664
+ expect { parse_criteria(&block) }
1665
+ .to raise_error error_class, error_message
1666
+ end
1667
+
1668
+ it 'should preserve the original exception', :aggregate_failures do
1669
+ parse_criteria(&block)
1670
+ rescue error_class => exception
1671
+ expect(exception.cause).to be_a NameError
1672
+ expect(exception.name).to be == :random
1673
+ end
1674
+ end
1675
+
1676
+ describe 'with a Hash with multiple operators' do
1677
+ let(:block) do
1678
+ lambda do |scope|
1679
+ {
1680
+ 'published_at' => scope.greater_than('1970-01-01'),
1681
+ 'series' => scope.one_of(
1682
+ ['Earthsea', 'The Lord of the Rings']
1683
+ ),
1684
+ 'title' => scope.not_equal('The Tombs of Atuan')
1685
+ }
1686
+ end
1687
+ end
1688
+ let(:expected) do
1689
+ [
1690
+ [
1691
+ 'published_at',
1692
+ operators::GREATER_THAN,
1693
+ '1970-01-01'
1694
+ ],
1695
+ [
1696
+ 'series',
1697
+ operators::ONE_OF,
1698
+ ['Earthsea', 'The Lord of the Rings']
1699
+ ],
1700
+ [
1701
+ 'title',
1702
+ operators::NOT_EQUAL,
1703
+ 'The Tombs of Atuan'
1704
+ ]
1705
+ ]
1706
+ end
1707
+
1708
+ it 'should parse the criteria' do
1709
+ expect(parse_criteria(&block)).to be == expected
1710
+ end
1711
+ end
1712
+
1713
+ describe 'with a Hash with mixed keys and operators' do
1714
+ let(:block) do
1715
+ lambda do |scope|
1716
+ {
1717
+ 'author' => scope.one_of(
1718
+ 'J.R.R. Tolkien',
1719
+ 'Ursula K. LeGuin'
1720
+ ),
1721
+ 'category' => 'Science Fiction and Fantasy'
1722
+ }
1723
+ end
1724
+ end
1725
+ let(:expected) do
1726
+ [
1727
+ [
1728
+ 'author',
1729
+ operators::ONE_OF,
1730
+ ['J.R.R. Tolkien', 'Ursula K. LeGuin']
1731
+ ],
1732
+ [
1733
+ 'category',
1734
+ operators::EQUAL,
1735
+ 'Science Fiction and Fantasy'
1736
+ ]
1737
+ ]
1738
+ end
1739
+
1740
+ it 'should parse the criteria' do
1741
+ expect(parse_criteria(&block)).to be == expected
1742
+ end
1743
+ end
1744
+
1745
+ # @deprecated v0.5.0 Implicit receivers are deprecated.
1746
+ describe 'with a block with legacy implicit receiver' do
1747
+ shared_examples 'should print a deprecation warning for' \
1748
+ do |operator_name|
1749
+ let(:deprecation_warning) do
1750
+ '#parse with implicit receiver'
1751
+ end
1752
+ let(:deprecation_message) do
1753
+ 'Pass a block with one parameter to #parse: { |scope| { scope' \
1754
+ ".#{operator_name}: value } }"
1755
+ end
1756
+
1757
+ it 'should print a deprecation warning' do
1758
+ parse_criteria(&block)
1759
+
1760
+ expect(tools.core_tools)
1761
+ .to have_received(:deprecate)
1762
+ .with(deprecation_warning, message: deprecation_message)
1763
+ end
1764
+ end
1765
+
1766
+ def tools
1767
+ SleepingKingStudios::Tools::Toolbelt.instance
1768
+ end
1769
+
1770
+ before(:example) do
1771
+ allow(tools.core_tools).to receive(:deprecate)
1772
+ end
1773
+
1774
+ describe 'with a Hash with an "eq" operator' do
1775
+ let(:block) do
1776
+ -> { { 'title' => eq('A Wizard of Earthsea') } }
1777
+ end
1778
+ let(:expected) do
1779
+ [['title', operators::EQUAL, 'A Wizard of Earthsea']]
1780
+ end
1781
+
1782
+ it 'should parse the criteria' do
1783
+ expect(parse_criteria(&block)).to be == expected
1784
+ end
1785
+
1786
+ include_examples 'should print a deprecation warning for', 'eq'
1787
+ end
1788
+
1789
+ describe 'with a Hash with an "equal" operator' do
1790
+ let(:block) do
1791
+ -> { { 'title' => equal('A Wizard of Earthsea') } }
1792
+ end
1793
+ let(:expected) do
1794
+ [['title', operators::EQUAL, 'A Wizard of Earthsea']]
1795
+ end
1796
+
1797
+ it 'should parse the criteria' do
1798
+ expect(parse_criteria(&block)).to be == expected
1799
+ end
1800
+
1801
+ include_examples 'should print a deprecation warning for', 'equal'
1802
+ end
1803
+
1804
+ describe 'with a Hash with an "equals" operator' do
1805
+ let(:block) do
1806
+ -> { { 'title' => equals('A Wizard of Earthsea') } }
1807
+ end
1808
+ let(:expected) do
1809
+ [['title', operators::EQUAL, 'A Wizard of Earthsea']]
1810
+ end
1811
+
1812
+ it 'should parse the criteria' do
1813
+ expect(parse_criteria(&block)).to be == expected
1814
+ end
1815
+
1816
+ include_examples 'should print a deprecation warning for', 'equals'
1817
+ end
1818
+
1819
+ describe 'with a Hash with a "greater_than" operator' do
1820
+ let(:block) do
1821
+ -> { { 'published_at' => greater_than('1970-01-01') } }
1822
+ end
1823
+ let(:expected) do
1824
+ [['published_at', operators::GREATER_THAN, '1970-01-01']]
1825
+ end
1826
+
1827
+ it 'should parse the criteria' do
1828
+ expect(parse_criteria(&block)).to be == expected
1829
+ end
1830
+
1831
+ include_examples 'should print a deprecation warning for',
1832
+ 'greater_than'
1833
+ end
1834
+
1835
+ describe 'with a Hash with a "greater_than_or_equal_to" operator' do
1836
+ let(:block) do
1837
+ lambda do
1838
+ { 'published_at' => greater_than_or_equal_to('1970-01-01') }
1839
+ end
1840
+ end
1841
+ let(:expected) do
1842
+ [
1843
+ [
1844
+ 'published_at',
1845
+ operators::GREATER_THAN_OR_EQUAL_TO,
1846
+ '1970-01-01'
1847
+ ]
1848
+ ]
1849
+ end
1850
+
1851
+ it 'should parse the criteria' do
1852
+ expect(parse_criteria(&block)).to be == expected
1853
+ end
1854
+
1855
+ include_examples 'should print a deprecation warning for',
1856
+ 'greater_than_or_equal_to'
1857
+ end
1858
+
1859
+ describe 'with a Hash with a "gt" operator' do
1860
+ let(:block) do
1861
+ -> { { 'published_at' => gt('1970-01-01') } }
1862
+ end
1863
+ let(:expected) do
1864
+ [['published_at', operators::GREATER_THAN, '1970-01-01']]
1865
+ end
1866
+
1867
+ it 'should parse the criteria' do
1868
+ expect(parse_criteria(&block)).to be == expected
1869
+ end
1870
+
1871
+ include_examples 'should print a deprecation warning for', 'gt'
1872
+ end
1873
+
1874
+ describe 'with a Hash with a "gte" operator' do
1875
+ let(:block) do
1876
+ -> { { 'published_at' => gte('1970-01-01') } }
1877
+ end
1878
+ let(:expected) do
1879
+ [
1880
+ [
1881
+ 'published_at',
1882
+ operators::GREATER_THAN_OR_EQUAL_TO,
1883
+ '1970-01-01'
1884
+ ]
1885
+ ]
1886
+ end
1887
+
1888
+ it 'should parse the criteria' do
1889
+ expect(parse_criteria(&block)).to be == expected
1890
+ end
1891
+
1892
+ include_examples 'should print a deprecation warning for', 'gte'
1893
+ end
1894
+
1895
+ describe 'with a Hash with a "less_than" operator' do
1896
+ let(:block) do
1897
+ -> { { 'published_at' => less_than('1970-01-01') } }
1898
+ end
1899
+ let(:expected) do
1900
+ [['published_at', operators::LESS_THAN, '1970-01-01']]
1901
+ end
1902
+
1903
+ it 'should parse the criteria' do
1904
+ expect(parse_criteria(&block)).to be == expected
1905
+ end
1906
+
1907
+ include_examples 'should print a deprecation warning for',
1908
+ 'less_than'
1909
+ end
1910
+
1911
+ describe 'with a Hash with a "less_than_or_equal_to" operator' do
1912
+ let(:block) do
1913
+ -> { { 'published_at' => less_than_or_equal_to('1970-01-01') } }
1914
+ end
1915
+ let(:expected) do
1916
+ [['published_at', operators::LESS_THAN_OR_EQUAL_TO, '1970-01-01']]
1917
+ end
1918
+
1919
+ it 'should parse the criteria' do
1920
+ expect(parse_criteria(&block)).to be == expected
1921
+ end
1922
+
1923
+ include_examples 'should print a deprecation warning for',
1924
+ 'less_than_or_equal_to'
1925
+ end
1926
+
1927
+ describe 'with a Hash with a "lt" operator' do
1928
+ let(:block) do
1929
+ -> { { 'published_at' => lt('1970-01-01') } }
1930
+ end
1931
+ let(:expected) do
1932
+ [['published_at', operators::LESS_THAN, '1970-01-01']]
1933
+ end
1934
+
1935
+ it 'should parse the criteria' do
1936
+ expect(parse_criteria(&block)).to be == expected
1937
+ end
1938
+
1939
+ include_examples 'should print a deprecation warning for', 'lt'
1940
+ end
1941
+
1942
+ describe 'with a Hash with a "lte" operator' do
1943
+ let(:block) do
1944
+ -> { { 'published_at' => lte('1970-01-01') } }
1945
+ end
1946
+ let(:expected) do
1947
+ [['published_at', operators::LESS_THAN_OR_EQUAL_TO, '1970-01-01']]
1948
+ end
1949
+
1950
+ it 'should parse the criteria' do
1951
+ expect(parse_criteria(&block)).to be == expected
1952
+ end
1953
+
1954
+ include_examples 'should print a deprecation warning for', 'lte'
1955
+ end
1956
+
1957
+ describe 'with a Hash with a "ne" operator' do
1958
+ let(:block) do
1959
+ -> { { 'series' => ne('Earthsea') } }
1960
+ end
1961
+ let(:expected) do
1962
+ [['series', operators::NOT_EQUAL, 'Earthsea']]
1963
+ end
1964
+
1965
+ it 'should parse the criteria' do
1966
+ expect(parse_criteria(&block)).to be == expected
1967
+ end
1968
+
1969
+ include_examples 'should print a deprecation warning for', 'ne'
1970
+ end
1971
+
1972
+ describe 'with a Hash with a "not_equal" operator' do
1973
+ let(:block) do
1974
+ -> { { 'series' => not_equal('Earthsea') } }
1975
+ end
1976
+ let(:expected) do
1977
+ [['series', operators::NOT_EQUAL, 'Earthsea']]
1978
+ end
1979
+
1980
+ it 'should parse the criteria' do
1981
+ expect(parse_criteria(&block)).to be == expected
1982
+ end
1983
+
1984
+ include_examples 'should print a deprecation warning for',
1985
+ 'not_equal'
1986
+ end
1987
+
1988
+ describe 'with a Hash with a "not_one_of" operator and an Array' do
1989
+ let(:block) do
1990
+ lambda do
1991
+ {
1992
+ 'series' => not_one_of(
1993
+ ['Earthsea', 'The Lord of the Rings']
1994
+ )
1995
+ }
1996
+ end
1997
+ end
1998
+ let(:expected) do
1999
+ [
2000
+ [
2001
+ 'series',
2002
+ operators::NOT_ONE_OF,
2003
+ ['Earthsea', 'The Lord of the Rings']
2004
+ ]
2005
+ ]
2006
+ end
2007
+
2008
+ it 'should parse the criteria' do
2009
+ expect(parse_criteria(&block)).to be == expected
2010
+ end
2011
+
2012
+ include_examples 'should print a deprecation warning for',
2013
+ 'not_one_of'
2014
+ end
2015
+
2016
+ describe 'with a Hash with a "not_one_of" operator and values' do
2017
+ let(:block) do
2018
+ lambda do
2019
+ { 'series' => not_one_of('Earthsea', 'The Lord of the Rings') }
2020
+ end
2021
+ end
2022
+ let(:expected) do
2023
+ [
2024
+ [
2025
+ 'series',
2026
+ operators::NOT_ONE_OF,
2027
+ ['Earthsea', 'The Lord of the Rings']
2028
+ ]
2029
+ ]
2030
+ end
2031
+
2032
+ it 'should parse the criteria' do
2033
+ expect(parse_criteria(&block)).to be == expected
2034
+ end
2035
+
2036
+ include_examples 'should print a deprecation warning for',
2037
+ 'not_one_of'
2038
+ end
2039
+
2040
+ describe 'with a Hash with a "one_of" operator and an Array' do
2041
+ let(:block) do
2042
+ lambda do
2043
+ { 'series' => one_of(['Earthsea', 'The Lord of the Rings']) }
2044
+ end
2045
+ end
2046
+ let(:expected) do
2047
+ [
2048
+ [
2049
+ 'series',
2050
+ operators::ONE_OF,
2051
+ ['Earthsea', 'The Lord of the Rings']
2052
+ ]
2053
+ ]
2054
+ end
2055
+
2056
+ it 'should parse the criteria' do
2057
+ expect(parse_criteria(&block)).to be == expected
2058
+ end
2059
+
2060
+ include_examples 'should print a deprecation warning for', 'one_of'
2061
+ end
2062
+
2063
+ describe 'with a Hash with a "one_of" operator and values' do
2064
+ let(:block) do
2065
+ lambda do
2066
+ { 'series' => one_of('Earthsea', 'The Lord of the Rings') }
2067
+ end
2068
+ end
2069
+ let(:expected) do
2070
+ [
2071
+ [
2072
+ 'series',
2073
+ operators::ONE_OF,
2074
+ ['Earthsea', 'The Lord of the Rings']
2075
+ ]
2076
+ ]
2077
+ end
2078
+
2079
+ it 'should parse the criteria' do
2080
+ expect(parse_criteria(&block)).to be == expected
2081
+ end
2082
+
2083
+ include_examples 'should print a deprecation warning for', 'one_of'
2084
+ end
2085
+
2086
+ describe 'with a Hash with an unknown operator' do
2087
+ let(:error_class) do
2088
+ Cuprum::Collections::Queries::UnknownOperatorException
2089
+ end
2090
+ let(:error_message) do
2091
+ 'unknown operator "random"'
2092
+ end
2093
+ let(:block) do
2094
+ -> { { 'genre' => random('Science Fiction', 'Fantasy') } }
2095
+ end
2096
+
2097
+ it 'should raise an exception' do
2098
+ expect { parse_criteria(&block) }
2099
+ .to raise_error error_class, error_message
2100
+ end
2101
+
2102
+ it 'should preserve the original exception', :aggregate_failures do
2103
+ parse_criteria(&block)
2104
+ rescue error_class => exception
2105
+ expect(exception.cause).to be_a NameError
2106
+ expect(exception.name).to be == :random
2107
+ end
2108
+ end
2109
+ end
2110
+ end
2111
+ end
2112
+
2113
+ # Contract validating the parsing of criteria from a Hash.
2114
+ module ShouldParseCriteriaFromAHashContract
2115
+ extend RSpec::SleepingKingStudios::Contract
2116
+
2117
+ # @!method apply(example_group)
2118
+ # Adds the contract to the example group.
2119
+ #
2120
+ # @param example_group [RSpec::Core::ExampleGroup] the example group to
2121
+ # which the contract is applied.
2122
+ contract do
2123
+ describe 'with nil' do
2124
+ let(:error_message) do
2125
+ 'value must be a Hash with String or Symbol keys'
2126
+ end
2127
+
2128
+ it 'should raise an exception' do
2129
+ expect { parse_criteria(nil) }
2130
+ .to raise_error ArgumentError, error_message
2131
+ end
2132
+ end
2133
+
2134
+ describe 'with an Object' do
2135
+ let(:error_message) do
2136
+ 'value must be a Hash with String or Symbol keys'
2137
+ end
2138
+
2139
+ it 'should raise an exception' do
2140
+ expect { parse_criteria(Object.new.freeze) }
2141
+ .to raise_error ArgumentError, error_message
2142
+ end
2143
+ end
2144
+
2145
+ describe 'with a Hash with invalid keys' do
2146
+ let(:error_message) do
2147
+ 'value must be a Hash with String or Symbol keys'
2148
+ end
2149
+ let(:value) { { nil => 'invalid' } }
2150
+
2151
+ it 'should raise an exception' do
2152
+ expect { parse_criteria(value) }
2153
+ .to raise_error ArgumentError, error_message
2154
+ end
2155
+ end
2156
+
2157
+ describe 'with an empty Hash' do
2158
+ let(:value) { {} }
2159
+
2160
+ it { expect(parse_criteria(value)).to be == [] }
2161
+ end
2162
+
2163
+ describe 'with a Hash with one key' do
2164
+ let(:operators) { Cuprum::Collections::Queries::Operators }
2165
+ let(:value) do
2166
+ { 'title' => 'A Wizard of Earthsea' }
2167
+ end
2168
+ let(:expected) do
2169
+ [['title', operators::EQUAL, 'A Wizard of Earthsea']]
2170
+ end
2171
+
2172
+ it { expect(parse_criteria(value)).to be == expected }
2173
+ end
2174
+
2175
+ describe 'with a Hash with many String keys' do
2176
+ let(:operators) { Cuprum::Collections::Queries::Operators }
2177
+ let(:value) do
2178
+ {
2179
+ 'title' => 'A Wizard of Earthsea',
2180
+ 'author' => 'Ursula K. LeGuin',
2181
+ 'series' => 'Earthsea'
2182
+ }
2183
+ end
2184
+ let(:expected) do
2185
+ [
2186
+ ['title', operators::EQUAL, 'A Wizard of Earthsea'],
2187
+ ['author', operators::EQUAL, 'Ursula K. LeGuin'],
2188
+ ['series', operators::EQUAL, 'Earthsea']
2189
+ ]
2190
+ end
2191
+
2192
+ it { expect(parse_criteria(value)).to be == expected }
2193
+ end
2194
+
2195
+ describe 'with a Hash with many Symbol keys' do
2196
+ let(:operators) { Cuprum::Collections::Queries::Operators }
2197
+ let(:value) do
2198
+ {
2199
+ title: 'A Wizard of Earthsea',
2200
+ author: 'Ursula K. LeGuin',
2201
+ series: 'Earthsea'
2202
+ }
2203
+ end
2204
+ let(:expected) do
2205
+ [
2206
+ ['title', operators::EQUAL, 'A Wizard of Earthsea'],
2207
+ ['author', operators::EQUAL, 'Ursula K. LeGuin'],
2208
+ ['series', operators::EQUAL, 'Earthsea']
2209
+ ]
2210
+ end
2211
+
2212
+ it { expect(parse_criteria(value)).to be == expected }
2213
+ end
2214
+ end
2215
+ end
2216
+ end
2217
+ end