cuprum-collections 0.5.0 → 0.6.0.rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +51 -0
  3. data/README.md +1 -1
  4. data/config/locales/en.rb +18 -0
  5. data/lib/cuprum/collections/adaptable/collection.rb +18 -0
  6. data/lib/cuprum/collections/adaptable/command.rb +22 -0
  7. data/lib/cuprum/collections/adaptable/commands/abstract_assign_one.rb +27 -0
  8. data/lib/cuprum/collections/adaptable/commands/abstract_build_one.rb +25 -0
  9. data/lib/cuprum/collections/adaptable/commands/abstract_validate_one.rb +35 -0
  10. data/lib/cuprum/collections/adaptable/commands.rb +15 -0
  11. data/lib/cuprum/collections/adaptable/query.rb +64 -0
  12. data/lib/cuprum/collections/adaptable.rb +13 -0
  13. data/lib/cuprum/collections/adapter.rb +300 -0
  14. data/lib/cuprum/collections/adapters/data_adapter.rb +82 -0
  15. data/lib/cuprum/collections/adapters/entity_adapter.rb +76 -0
  16. data/lib/cuprum/collections/adapters/hash_adapter.rb +48 -0
  17. data/lib/cuprum/collections/adapters.rb +14 -0
  18. data/lib/cuprum/collections/basic/collection.rb +2 -20
  19. data/lib/cuprum/collections/basic/commands/destroy_one.rb +1 -1
  20. data/lib/cuprum/collections/basic/commands/find_many.rb +0 -31
  21. data/lib/cuprum/collections/basic/commands/find_matching.rb +0 -94
  22. data/lib/cuprum/collections/basic/commands/find_one.rb +0 -18
  23. data/lib/cuprum/collections/basic/commands/insert_one.rb +1 -1
  24. data/lib/cuprum/collections/basic/commands/update_one.rb +1 -1
  25. data/lib/cuprum/collections/basic/scopes/criteria_scope.rb +36 -21
  26. data/lib/cuprum/collections/basic.rb +6 -5
  27. data/lib/cuprum/collections/collection.rb +6 -0
  28. data/lib/cuprum/collections/collection_command.rb +1 -1
  29. data/lib/cuprum/collections/commands/abstract_find_many.rb +40 -3
  30. data/lib/cuprum/collections/commands/abstract_find_matching.rb +102 -0
  31. data/lib/cuprum/collections/commands/abstract_find_one.rb +23 -1
  32. data/lib/cuprum/collections/commands/associations/find_many.rb +1 -3
  33. data/lib/cuprum/collections/commands/associations/require_many.rb +1 -1
  34. data/lib/cuprum/collections/commands/find_one_matching.rb +10 -10
  35. data/lib/cuprum/collections/commands/query_command.rb +6 -4
  36. data/lib/cuprum/collections/commands/upsert.rb +0 -2
  37. data/lib/cuprum/collections/constraints/order/attributes_array.rb +5 -4
  38. data/lib/cuprum/collections/constraints/order/attributes_hash.rb +5 -4
  39. data/lib/cuprum/collections/constraints/order/sort_direction.rb +2 -2
  40. data/lib/cuprum/collections/constraints/ordering.rb +11 -9
  41. data/lib/cuprum/collections/constraints/query_hash.rb +2 -2
  42. data/lib/cuprum/collections/errors/abstract_find_error.rb +101 -23
  43. data/lib/cuprum/collections/errors/extra_attributes.rb +3 -3
  44. data/lib/cuprum/collections/errors/failed_validation.rb +3 -3
  45. data/lib/cuprum/collections/errors/missing_default_contract.rb +12 -4
  46. data/lib/cuprum/collections/queries.rb +4 -0
  47. data/lib/cuprum/collections/relation.rb +0 -2
  48. data/lib/cuprum/collections/relations/parameters.rb +120 -68
  49. data/lib/cuprum/collections/repository.rb +71 -6
  50. data/lib/cuprum/collections/rspec/contracts/query_contracts.rb +23 -4
  51. data/lib/cuprum/collections/rspec/contracts/repository_contracts.rb +18 -0
  52. data/lib/cuprum/collections/rspec/contracts/scope_contracts.rb +51 -0
  53. data/lib/cuprum/collections/rspec/contracts/scopes/builder_contracts.rb +10 -0
  54. data/lib/cuprum/collections/rspec/contracts/scopes/composition_contracts.rb +8 -0
  55. data/lib/cuprum/collections/rspec/contracts/scopes/criteria_contracts.rb +18 -366
  56. data/lib/cuprum/collections/rspec/contracts/scopes/logical_contracts.rb +30 -0
  57. data/lib/cuprum/collections/rspec/contracts/scopes.rb +2 -0
  58. data/lib/cuprum/collections/rspec/contracts.rb +2 -10
  59. data/lib/cuprum/collections/rspec/deferred/adapter_examples.rb +1077 -0
  60. data/lib/cuprum/collections/rspec/deferred/collection_examples.rb +27 -7
  61. data/lib/cuprum/collections/rspec/deferred/commands/assign_one_examples.rb +4 -4
  62. data/lib/cuprum/collections/rspec/deferred/commands/build_one_examples.rb +2 -2
  63. data/lib/cuprum/collections/rspec/deferred/commands/destroy_one_examples.rb +2 -2
  64. data/lib/cuprum/collections/rspec/deferred/commands/find_many_examples.rb +5 -5
  65. data/lib/cuprum/collections/rspec/deferred/commands/find_matching_examples.rb +45 -12
  66. data/lib/cuprum/collections/rspec/deferred/commands/find_one_examples.rb +2 -2
  67. data/lib/cuprum/collections/rspec/deferred/commands/insert_one_examples.rb +1 -1
  68. data/lib/cuprum/collections/rspec/deferred/commands/update_one_examples.rb +1 -1
  69. data/lib/cuprum/collections/rspec/deferred/query_examples.rb +930 -0
  70. data/lib/cuprum/collections/rspec/deferred/relation_examples.rb +48 -17
  71. data/lib/cuprum/collections/rspec/deferred/repository_examples.rb +961 -0
  72. data/lib/cuprum/collections/rspec/deferred/scope_examples.rb +598 -0
  73. data/lib/cuprum/collections/rspec/deferred/scopes/all_examples.rb +391 -0
  74. data/lib/cuprum/collections/rspec/deferred/scopes/builder_examples.rb +857 -0
  75. data/lib/cuprum/collections/rspec/deferred/scopes/composition_examples.rb +93 -0
  76. data/lib/cuprum/collections/rspec/deferred/scopes/conjunction_examples.rb +438 -0
  77. data/lib/cuprum/collections/rspec/deferred/scopes/criteria_examples.rb +1941 -0
  78. data/lib/cuprum/collections/rspec/deferred/scopes/disjunction_examples.rb +415 -0
  79. data/lib/cuprum/collections/rspec/deferred/scopes/none_examples.rb +385 -0
  80. data/lib/cuprum/collections/rspec/deferred/scopes/parser_examples.rb +740 -0
  81. data/lib/cuprum/collections/rspec/deferred/scopes.rb +8 -0
  82. data/lib/cuprum/collections/scope.rb +2 -2
  83. data/lib/cuprum/collections/scopes/container.rb +5 -4
  84. data/lib/cuprum/collections/scopes/criteria/parser.rb +24 -48
  85. data/lib/cuprum/collections/scopes/criteria.rb +7 -6
  86. data/lib/cuprum/collections/version.rb +3 -3
  87. data/lib/cuprum/collections.rb +5 -1
  88. metadata +48 -11
  89. data/lib/cuprum/collections/rspec/contracts/association_contracts.rb +0 -2127
  90. data/lib/cuprum/collections/rspec/contracts/basic.rb +0 -11
  91. data/lib/cuprum/collections/rspec/contracts/collection_contracts.rb +0 -387
  92. data/lib/cuprum/collections/rspec/contracts/command_contracts.rb +0 -169
  93. data/lib/cuprum/collections/rspec/contracts/relation_contracts.rb +0 -1264
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/sleeping_king_studios/deferred'
4
+
5
+ require 'cuprum/collections/rspec/deferred/scopes'
6
+
7
+ module Cuprum::Collections::RSpec::Deferred::Scopes
8
+ # Deferred examples for asserting on scope composition.
9
+ module CompositionExamples
10
+ include RSpec::SleepingKingStudios::Deferred::Provider
11
+
12
+ deferred_context 'with contexts for composable scopes' do
13
+ deferred_context 'with an all scope' do
14
+ let(:original) do
15
+ Cuprum::Collections::Scopes::AllScope.new
16
+ end
17
+ end
18
+
19
+ deferred_context 'with a none scope' do
20
+ let(:original) do
21
+ Cuprum::Collections::Scopes::NoneScope.new
22
+ end
23
+ end
24
+
25
+ deferred_context 'with an empty conjunction scope' do
26
+ let(:original) do
27
+ Cuprum::Collections::Scopes::ConjunctionScope.new(scopes: [])
28
+ end
29
+ end
30
+
31
+ deferred_context 'with an empty criteria scope' do
32
+ let(:original) do
33
+ Cuprum::Collections::Scopes::CriteriaScope.new(criteria: [])
34
+ end
35
+ end
36
+
37
+ deferred_context 'with an empty disjunction scope' do
38
+ let(:original) do
39
+ Cuprum::Collections::Scopes::DisjunctionScope.new(scopes: [])
40
+ end
41
+ end
42
+
43
+ deferred_context 'with a non-empty conjunction scope' do
44
+ let(:original) do
45
+ operators = Cuprum::Collections::Queries::Operators
46
+ criteria = [
47
+ [
48
+ 'category',
49
+ operators::EQUAL,
50
+ 'Science Fiction and Fantasy'
51
+ ]
52
+ ]
53
+ wrapped =
54
+ Cuprum::Collections::Scopes::CriteriaScope.new(criteria:)
55
+
56
+ Cuprum::Collections::Scopes::ConjunctionScope.new(scopes: [wrapped])
57
+ end
58
+ end
59
+
60
+ deferred_context 'with a non-empty criteria scope' do
61
+ let(:original) do
62
+ operators = Cuprum::Collections::Queries::Operators
63
+ criteria = [
64
+ [
65
+ 'category',
66
+ operators::EQUAL,
67
+ 'Science Fiction and Fantasy'
68
+ ]
69
+ ]
70
+
71
+ Cuprum::Collections::Scopes::CriteriaScope.new(criteria:)
72
+ end
73
+ end
74
+
75
+ deferred_context 'with a non-empty disjunction scope' do
76
+ let(:original) do
77
+ operators = Cuprum::Collections::Queries::Operators
78
+ criteria = [
79
+ [
80
+ 'category',
81
+ operators::EQUAL,
82
+ 'Science Fiction and Fantasy'
83
+ ]
84
+ ]
85
+ wrapped =
86
+ Cuprum::Collections::Scopes::CriteriaScope.new(criteria:)
87
+
88
+ Cuprum::Collections::Scopes::DisjunctionScope.new(scopes: [wrapped])
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,438 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/sleeping_king_studios/deferred'
4
+
5
+ require 'cuprum/collections/rspec/deferred/scope_examples'
6
+ require 'cuprum/collections/rspec/deferred/scopes'
7
+ require 'cuprum/collections/rspec/deferred/scopes/composition_examples'
8
+
9
+ module Cuprum::Collections::RSpec::Deferred::Scopes
10
+ # Deferred examples for asserting on Conjunction scope objects.
11
+ module ConjunctionExamples
12
+ include RSpec::SleepingKingStudios::Deferred::Provider
13
+ include Cuprum::Collections::RSpec::Deferred::ScopeExamples
14
+ include Cuprum::Collections::RSpec::Deferred::Scopes::CompositionExamples
15
+
16
+ deferred_examples 'should implement the ConjunctionScope methods' \
17
+ do |**deferred_options|
18
+ deferred_context 'when the scope has many child scopes' do
19
+ let(:scopes) do
20
+ [
21
+ build_scope({ 'author' => 'J.R.R. Tolkien' }),
22
+ build_scope({ 'series' => 'The Lord of the Rings' }),
23
+ build_scope do |scope|
24
+ { 'published_at' => scope.less_than('1955-01-01') }
25
+ end
26
+ ]
27
+ end
28
+ end
29
+
30
+ include_deferred 'should implement the Scope methods'
31
+
32
+ include_deferred 'should define child scopes'
33
+
34
+ include_deferred 'should compose Scopes as a ConjunctionScope'
35
+
36
+ describe '#as_json' do
37
+ let(:expected) do
38
+ {
39
+ 'scopes' => subject.scopes.map(&:as_json),
40
+ 'type' => subject.type
41
+ }
42
+ end
43
+
44
+ it { expect(subject.as_json).to be == expected }
45
+
46
+ wrap_deferred 'with scopes' do
47
+ it { expect(subject.as_json).to be == expected }
48
+ end
49
+ end
50
+
51
+ describe '#call' do
52
+ next if deferred_options.fetch(:abstract, false)
53
+
54
+ include_deferred 'should filter data by logical AND'
55
+ end
56
+
57
+ describe '#invert' do
58
+ let(:expected) do
59
+ Cuprum::Collections::Scopes::DisjunctionScope.new(
60
+ scopes: subject.scopes.map(&:invert)
61
+ )
62
+ end
63
+
64
+ it { expect(subject.invert).to be == expected }
65
+
66
+ wrap_deferred 'with scopes' do
67
+ it { expect(subject.invert).to be == expected }
68
+ end
69
+ end
70
+
71
+ describe '#type' do
72
+ include_examples 'should define reader', :type, :conjunction
73
+ end
74
+ end
75
+
76
+ deferred_examples 'should compose Scopes as a ConjunctionScope' do
77
+ describe '#and' do
78
+ include_deferred 'with contexts for composable scopes'
79
+
80
+ describe 'with a block' do
81
+ let(:block) { -> { { 'title' => 'A Wizard of Earthsea' } } }
82
+ let(:expected) do
83
+ wrapped = Cuprum::Collections::Scope.new(&block)
84
+
85
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
86
+ scopes: [*subject.scopes, wrapped]
87
+ )
88
+ end
89
+
90
+ it { expect(subject.and(&block)).to be == expected }
91
+ end
92
+
93
+ describe 'with a hash' do
94
+ let(:value) { { 'title' => 'A Wizard of Earthsea' } }
95
+ let(:expected) do
96
+ wrapped = Cuprum::Collections::Scope.new(value)
97
+
98
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
99
+ scopes: [*subject.scopes, wrapped]
100
+ )
101
+ end
102
+
103
+ it { expect(subject.and(value)).to be == expected }
104
+ end
105
+
106
+ wrap_deferred 'with an all scope' do
107
+ it { expect(subject.and(original)).to be subject }
108
+ end
109
+
110
+ wrap_deferred 'with a none scope' do
111
+ it { expect(subject.and(original)).to be == original }
112
+ end
113
+
114
+ wrap_deferred 'with an empty conjunction scope' do
115
+ it { expect(subject.and(original)).to be subject }
116
+
117
+ wrap_deferred 'when the scope has many child scopes' do
118
+ it { expect(subject.and(original)).to be subject }
119
+ end
120
+ end
121
+
122
+ wrap_deferred 'with an empty criteria scope' do
123
+ it { expect(subject.and(original)).to be subject }
124
+ end
125
+
126
+ wrap_deferred 'with an empty disjunction scope' do
127
+ it { expect(subject.and(original)).to be subject }
128
+ end
129
+
130
+ wrap_deferred 'with a non-empty conjunction scope' do
131
+ let(:expected) do
132
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
133
+ scopes: [*subject.scopes, *original.scopes]
134
+ )
135
+ end
136
+
137
+ it { expect(subject.and(original)).to be == expected }
138
+
139
+ wrap_deferred 'when the scope has many child scopes' do
140
+ it { expect(subject.and(original)).to be == expected }
141
+ end
142
+ end
143
+
144
+ wrap_deferred 'with a non-empty criteria scope' do
145
+ let(:expected) do
146
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
147
+ scopes: [*subject.scopes, original]
148
+ )
149
+ end
150
+
151
+ it { expect(subject.and(original)).to be == expected }
152
+
153
+ wrap_deferred 'when the scope has many child scopes' do
154
+ it { expect(subject.and(original)).to be == expected }
155
+ end
156
+ end
157
+
158
+ wrap_deferred 'with a non-empty disjunction scope' do
159
+ let(:expected) do
160
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
161
+ scopes: [*subject.scopes, original]
162
+ )
163
+ end
164
+
165
+ it { expect(subject.and(original)).to be == expected }
166
+
167
+ wrap_deferred 'when the scope has many child scopes' do
168
+ it { expect(subject.and(original)).to be == expected }
169
+ end
170
+ end
171
+ end
172
+
173
+ describe '#not' do
174
+ include_deferred 'with contexts for composable scopes'
175
+
176
+ describe 'with a block' do
177
+ let(:block) { -> { { 'title' => 'A Wizard of Earthsea' } } }
178
+ let(:expected) do
179
+ wrapped = Cuprum::Collections::Scope.new(&block)
180
+
181
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
182
+ scopes: [*subject.scopes, wrapped.invert]
183
+ )
184
+ end
185
+
186
+ it { expect(subject.not(&block)).to be == expected }
187
+
188
+ wrap_deferred 'when the scope has many child scopes' do
189
+ it { expect(subject.not(&block)).to be == expected }
190
+ end
191
+ end
192
+
193
+ describe 'with a hash' do
194
+ let(:value) { { 'title' => 'A Wizard of Earthsea' } }
195
+ let(:expected) do
196
+ wrapped = Cuprum::Collections::Scope.new(value)
197
+
198
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
199
+ scopes: [*subject.scopes, wrapped.invert]
200
+ )
201
+ end
202
+
203
+ it { expect(subject.not(value)).to be == expected }
204
+
205
+ wrap_deferred 'when the scope has many child scopes' do
206
+ it { expect(subject.not(value)).to be == expected }
207
+ end
208
+ end
209
+
210
+ wrap_deferred 'with an all scope' do
211
+ let(:expected) { Cuprum::Collections::Scopes::NoneScope.new }
212
+
213
+ it { expect(subject.not(original)).to be == expected }
214
+ end
215
+
216
+ wrap_deferred 'with a none scope' do
217
+ it { expect(subject.not(original)).to be subject }
218
+ end
219
+
220
+ wrap_deferred 'with an empty conjunction scope' do
221
+ it { expect(subject.not(original)).to be subject }
222
+ end
223
+
224
+ wrap_deferred 'with an empty criteria scope' do
225
+ it { expect(subject.not(original)).to be subject }
226
+ end
227
+
228
+ wrap_deferred 'with an empty disjunction scope' do
229
+ it { expect(subject.not(original)).to be subject }
230
+ end
231
+
232
+ wrap_deferred 'with a non-empty conjunction scope' do
233
+ let(:expected) do
234
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
235
+ scopes: [*subject.scopes, original.invert]
236
+ )
237
+ end
238
+
239
+ it { expect(subject.not(original)).to be == expected }
240
+
241
+ wrap_deferred 'when the scope has many child scopes' do
242
+ it { expect(subject.not(original)).to be == expected }
243
+ end
244
+ end
245
+
246
+ wrap_deferred 'with a non-empty criteria scope' do
247
+ let(:expected) do
248
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
249
+ scopes: [*subject.scopes, original.invert]
250
+ )
251
+ end
252
+
253
+ it { expect(subject.not(original)).to be == expected }
254
+
255
+ wrap_deferred 'when the scope has many child scopes' do
256
+ it { expect(subject.not(original)).to be == expected }
257
+ end
258
+ end
259
+
260
+ wrap_deferred 'with a non-empty disjunction scope' do
261
+ let(:expected) do
262
+ Cuprum::Collections::Scopes::ConjunctionScope.new(
263
+ scopes: [*subject.scopes, *original.invert.scopes]
264
+ )
265
+ end
266
+
267
+ it { expect(subject.not(original)).to be == expected }
268
+
269
+ wrap_deferred 'when the scope has many child scopes' do
270
+ it { expect(subject.not(original)).to be == expected }
271
+ end
272
+ end
273
+ end
274
+
275
+ describe '#or' do
276
+ include_deferred 'with contexts for composable scopes'
277
+
278
+ it 'should define the method' do
279
+ expect(subject)
280
+ .to respond_to(:or)
281
+ .with(0..1).arguments
282
+ .and_a_block
283
+ end
284
+
285
+ describe 'with a block' do
286
+ let(:block) { -> { { 'title' => 'A Wizard of Earthsea' } } }
287
+ let(:expected) do
288
+ wrapped = Cuprum::Collections::Scope.new(&block)
289
+
290
+ Cuprum::Collections::Scopes::DisjunctionScope.new(
291
+ scopes: [subject, wrapped]
292
+ )
293
+ end
294
+
295
+ it { expect(subject.or(&block)).to be == expected }
296
+ end
297
+
298
+ describe 'with a hash' do
299
+ let(:value) { { 'title' => 'A Wizard of Earthsea' } }
300
+ let(:expected) do
301
+ wrapped = Cuprum::Collections::Scope.new(value)
302
+
303
+ Cuprum::Collections::Scopes::DisjunctionScope.new(
304
+ scopes: [subject, wrapped]
305
+ )
306
+ end
307
+
308
+ it { expect(subject.or(value)).to be == expected }
309
+ end
310
+
311
+ wrap_deferred 'with an all scope' do
312
+ it { expect(subject.or(original)).to be == original }
313
+ end
314
+
315
+ wrap_deferred 'with a none scope' do
316
+ it { expect(subject.or(original)).to be subject }
317
+ end
318
+
319
+ wrap_deferred 'with an empty conjunction scope' do
320
+ it { expect(subject.or(original)).to be subject }
321
+ end
322
+
323
+ wrap_deferred 'with an empty criteria scope' do
324
+ it { expect(subject.or(original)).to be subject }
325
+ end
326
+
327
+ wrap_deferred 'with an empty disjunction scope' do
328
+ it { expect(subject.or(original)).to be subject }
329
+ end
330
+
331
+ wrap_deferred 'with a non-empty conjunction scope' do
332
+ let(:expected) do
333
+ Cuprum::Collections::Scopes::DisjunctionScope.new(
334
+ scopes: [subject, original]
335
+ )
336
+ end
337
+
338
+ it { expect(subject.or(original)).to be == expected }
339
+ end
340
+
341
+ wrap_deferred 'with a non-empty criteria scope' do
342
+ let(:expected) do
343
+ Cuprum::Collections::Scopes::DisjunctionScope.new(
344
+ scopes: [subject, original]
345
+ )
346
+ end
347
+
348
+ it { expect(subject.or(original)).to be == expected }
349
+ end
350
+
351
+ wrap_deferred 'with a non-empty disjunction scope' do
352
+ let(:expected) do
353
+ Cuprum::Collections::Scopes::DisjunctionScope.new(
354
+ scopes: [subject, *original.scopes]
355
+ )
356
+ end
357
+
358
+ it { expect(subject.or(original)).to be == expected }
359
+ end
360
+ end
361
+ end
362
+
363
+ deferred_examples 'should filter data by logical AND' do
364
+ deferred_context 'with data' do
365
+ let(:data) do
366
+ Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
367
+ end
368
+ end
369
+
370
+ context 'when the scope has no child scopes' do
371
+ let(:scopes) { [] }
372
+
373
+ describe 'with empty data' do
374
+ let(:data) { [] }
375
+
376
+ it { expect(filtered_data).to be == [] }
377
+ end
378
+
379
+ wrap_deferred 'with data' do
380
+ let(:expected) { data }
381
+
382
+ it { expect(filtered_data).to match_array expected }
383
+ end
384
+ end
385
+
386
+ context 'when the scope has one child scope' do
387
+ let(:scopes) do
388
+ [
389
+ build_scope({ 'author' => 'J.R.R. Tolkien' })
390
+ ]
391
+ end
392
+
393
+ describe 'with empty data' do
394
+ let(:data) { [] }
395
+
396
+ it { expect(filtered_data).to be == [] }
397
+ end
398
+
399
+ wrap_deferred 'with data' do
400
+ let(:expected) do
401
+ data.select { |item| item['author'] == 'J.R.R. Tolkien' }
402
+ end
403
+
404
+ it { expect(filtered_data).to match_array expected }
405
+ end
406
+ end
407
+
408
+ context 'when the scope has many child scopes' do
409
+ let(:scopes) do
410
+ [
411
+ build_scope({ 'author' => 'J.R.R. Tolkien' }),
412
+ build_scope({ 'series' => 'The Lord of the Rings' }),
413
+ build_scope do |scope|
414
+ { 'published_at' => scope.less_than('1955-01-01') }
415
+ end
416
+ ]
417
+ end
418
+
419
+ describe 'with empty data' do
420
+ let(:data) { [] }
421
+
422
+ it { expect(filtered_data).to be == [] }
423
+ end
424
+
425
+ wrap_deferred 'with data' do
426
+ let(:expected) do
427
+ data
428
+ .select { |item| item['author'] == 'J.R.R. Tolkien' }
429
+ .select { |item| item['series'] == 'The Lord of the Rings' }
430
+ .select { |item| item['published_at'] < '1955-01-01' }
431
+ end
432
+
433
+ it { expect(filtered_data).to match_array expected }
434
+ end
435
+ end
436
+ end
437
+ end
438
+ end