cuprum-collections 0.3.0 → 0.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +48 -0
- data/DEVELOPMENT.md +2 -2
- data/README.md +13 -11
- data/lib/cuprum/collections/association.rb +256 -0
- data/lib/cuprum/collections/associations/belongs_to.rb +32 -0
- data/lib/cuprum/collections/associations/has_many.rb +23 -0
- data/lib/cuprum/collections/associations/has_one.rb +23 -0
- data/lib/cuprum/collections/associations.rb +10 -0
- data/lib/cuprum/collections/basic/collection.rb +39 -74
- data/lib/cuprum/collections/basic/commands/find_many.rb +1 -1
- data/lib/cuprum/collections/basic/commands/find_matching.rb +1 -1
- data/lib/cuprum/collections/basic/repository.rb +9 -33
- data/lib/cuprum/collections/basic.rb +1 -0
- data/lib/cuprum/collections/collection.rb +154 -0
- data/lib/cuprum/collections/commands/associations/find_many.rb +161 -0
- data/lib/cuprum/collections/commands/associations/require_many.rb +48 -0
- data/lib/cuprum/collections/commands/associations.rb +13 -0
- data/lib/cuprum/collections/commands/find_one_matching.rb +1 -1
- data/lib/cuprum/collections/commands.rb +1 -0
- data/lib/cuprum/collections/errors/abstract_find_error.rb +1 -1
- data/lib/cuprum/collections/relation.rb +401 -0
- data/lib/cuprum/collections/repository.rb +71 -4
- data/lib/cuprum/collections/resource.rb +65 -0
- data/lib/cuprum/collections/rspec/contracts/association_contracts.rb +2137 -0
- data/lib/cuprum/collections/rspec/contracts/basic/command_contracts.rb +484 -0
- data/lib/cuprum/collections/rspec/contracts/basic.rb +11 -0
- data/lib/cuprum/collections/rspec/contracts/collection_contracts.rb +429 -0
- data/lib/cuprum/collections/rspec/contracts/command_contracts.rb +1462 -0
- data/lib/cuprum/collections/rspec/contracts/query_contracts.rb +1093 -0
- data/lib/cuprum/collections/rspec/contracts/relation_contracts.rb +1381 -0
- data/lib/cuprum/collections/rspec/contracts/repository_contracts.rb +605 -0
- data/lib/cuprum/collections/rspec/contracts.rb +23 -0
- data/lib/cuprum/collections/rspec/fixtures.rb +85 -82
- data/lib/cuprum/collections/rspec.rb +4 -1
- data/lib/cuprum/collections/version.rb +1 -1
- data/lib/cuprum/collections.rb +9 -4
- metadata +23 -19
- data/lib/cuprum/collections/base.rb +0 -11
- data/lib/cuprum/collections/basic/rspec/command_contract.rb +0 -392
- data/lib/cuprum/collections/rspec/assign_one_command_contract.rb +0 -168
- data/lib/cuprum/collections/rspec/build_one_command_contract.rb +0 -93
- data/lib/cuprum/collections/rspec/collection_contract.rb +0 -190
- data/lib/cuprum/collections/rspec/destroy_one_command_contract.rb +0 -108
- data/lib/cuprum/collections/rspec/find_many_command_contract.rb +0 -407
- data/lib/cuprum/collections/rspec/find_matching_command_contract.rb +0 -194
- data/lib/cuprum/collections/rspec/find_one_command_contract.rb +0 -157
- data/lib/cuprum/collections/rspec/insert_one_command_contract.rb +0 -84
- data/lib/cuprum/collections/rspec/query_builder_contract.rb +0 -92
- data/lib/cuprum/collections/rspec/query_contract.rb +0 -650
- data/lib/cuprum/collections/rspec/querying_contract.rb +0 -298
- data/lib/cuprum/collections/rspec/repository_contract.rb +0 -235
- data/lib/cuprum/collections/rspec/update_one_command_contract.rb +0 -80
- data/lib/cuprum/collections/rspec/validate_one_command_contract.rb +0 -96
@@ -0,0 +1,1093 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/queries'
|
4
|
+
require 'cuprum/collections/rspec/contracts'
|
5
|
+
require 'cuprum/collections/rspec/fixtures'
|
6
|
+
|
7
|
+
module Cuprum::Collections::RSpec::Contracts
|
8
|
+
# Contracts for asserting on Query objects.
|
9
|
+
module QueryContracts
|
10
|
+
# Contract validating the behavior of a Query implementation.
|
11
|
+
module ShouldBeAQuery
|
12
|
+
extend RSpec::SleepingKingStudios::Contract
|
13
|
+
|
14
|
+
BOOKS_FIXTURES = Cuprum::Collections::RSpec::Fixtures::BOOKS_FIXTURES
|
15
|
+
private_constant :BOOKS_FIXTURES
|
16
|
+
|
17
|
+
OPERATORS = Cuprum::Collections::Queries::Operators
|
18
|
+
private_constant :OPERATORS
|
19
|
+
|
20
|
+
# @!method apply(example_group, operators:)
|
21
|
+
# Adds the contract to the example group.
|
22
|
+
#
|
23
|
+
# @param example_group [RSpec::Core::ExampleGroup] the example group to
|
24
|
+
# which the contract is applied.
|
25
|
+
# @param operators [Array<Symbol>] the expected operators.
|
26
|
+
contract do |operators: OPERATORS.values|
|
27
|
+
include Cuprum::Collections::RSpec::Contracts::QueryContracts
|
28
|
+
|
29
|
+
operators = Set.new(operators.map(&:to_sym))
|
30
|
+
|
31
|
+
include_contract 'with query contexts'
|
32
|
+
|
33
|
+
shared_context 'when the query has composed filters' do
|
34
|
+
let(:scoped_query) do
|
35
|
+
super()
|
36
|
+
.where { { author: 'Ursula K. LeGuin' } }
|
37
|
+
.where { { series: not_equal('Earthsea') } }
|
38
|
+
end
|
39
|
+
let(:matching_data) do
|
40
|
+
super()
|
41
|
+
.select { |item| item['author'] == 'Ursula K. LeGuin' }
|
42
|
+
.reject { |item| item['series'] == 'Earthsea' }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
let(:scoped_query) do
|
47
|
+
# :nocov:
|
48
|
+
scoped =
|
49
|
+
if filter.is_a?(Proc)
|
50
|
+
query.where(&filter)
|
51
|
+
elsif !filter.nil?
|
52
|
+
query.where(filter)
|
53
|
+
else
|
54
|
+
query
|
55
|
+
end
|
56
|
+
# :nocov:
|
57
|
+
scoped = scoped.limit(limit) if limit
|
58
|
+
scoped = scoped.offset(offset) if offset
|
59
|
+
scoped = scoped.order(order) if order
|
60
|
+
|
61
|
+
scoped
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should be enumerable' do
|
65
|
+
expect(described_class).to be < Enumerable
|
66
|
+
end
|
67
|
+
|
68
|
+
describe '#count' do
|
69
|
+
let(:data) { [] }
|
70
|
+
let(:matching_data) { data }
|
71
|
+
let(:expected_data) do
|
72
|
+
defined?(super()) ? super() : matching_data
|
73
|
+
end
|
74
|
+
|
75
|
+
it { expect(query).to respond_to(:count).with(0).arguments }
|
76
|
+
|
77
|
+
it { expect(query.count).to be == expected_data.count }
|
78
|
+
|
79
|
+
wrap_context 'when the query has composed filters' do
|
80
|
+
it { expect(scoped_query.count).to be == expected_data.count }
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'when the collection data changes' do
|
84
|
+
let(:item) { BOOKS_FIXTURES.first }
|
85
|
+
|
86
|
+
before(:example) do
|
87
|
+
query.count # Cache query results.
|
88
|
+
|
89
|
+
add_item_to_collection(item)
|
90
|
+
end
|
91
|
+
|
92
|
+
it { expect(query.count).to be == expected_data.count }
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'when the collection has many items' do
|
96
|
+
let(:data) { BOOKS_FIXTURES }
|
97
|
+
|
98
|
+
it { expect(query.count).to be == expected_data.count }
|
99
|
+
|
100
|
+
wrap_context 'when the query has composed filters' do
|
101
|
+
it { expect(scoped_query.count).to be == expected_data.count }
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'when the collection data changes' do
|
105
|
+
let(:data) { BOOKS_FIXTURES[0...-1] }
|
106
|
+
let(:item) { BOOKS_FIXTURES.last }
|
107
|
+
|
108
|
+
before(:example) do
|
109
|
+
query.count # Cache query results.
|
110
|
+
|
111
|
+
add_item_to_collection(item)
|
112
|
+
end
|
113
|
+
|
114
|
+
it { expect(query.count).to be == expected_data.count }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe '#criteria' do
|
120
|
+
include_examples 'should have reader', :criteria, []
|
121
|
+
|
122
|
+
wrap_context 'when the query has where: a simple block filter' do
|
123
|
+
let(:expected) { [['author', :equal, 'Ursula K. LeGuin']] }
|
124
|
+
|
125
|
+
it { expect(scoped_query.criteria).to be == expected }
|
126
|
+
end
|
127
|
+
|
128
|
+
wrap_context 'when the query has where: a complex block filter' do
|
129
|
+
let(:expected) do
|
130
|
+
[
|
131
|
+
['author', :equal, 'Ursula K. LeGuin'],
|
132
|
+
['series', :not_equal, 'Earthsea']
|
133
|
+
]
|
134
|
+
end
|
135
|
+
|
136
|
+
if operators.include?(OPERATORS::EQUAL) &&
|
137
|
+
operators.include?(OPERATORS::NOT_EQUAL)
|
138
|
+
it { expect(scoped_query.criteria).to be == expected }
|
139
|
+
else
|
140
|
+
# :nocov:
|
141
|
+
pending
|
142
|
+
# :nocov:
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
wrap_context 'when the query has composed filters' do
|
147
|
+
let(:expected) do
|
148
|
+
[
|
149
|
+
['author', :equal, 'Ursula K. LeGuin'],
|
150
|
+
['series', :not_equal, 'Earthsea']
|
151
|
+
]
|
152
|
+
end
|
153
|
+
|
154
|
+
it { expect(scoped_query.criteria).to be == expected }
|
155
|
+
end
|
156
|
+
|
157
|
+
wrap_context 'when the query has where: an equal block filter' do
|
158
|
+
let(:expected) { [['author', :equal, 'Ursula K. LeGuin']] }
|
159
|
+
|
160
|
+
if operators.include?(OPERATORS::EQUAL)
|
161
|
+
it { expect(scoped_query.criteria).to be == expected }
|
162
|
+
else
|
163
|
+
# :nocov:
|
164
|
+
pending
|
165
|
+
# :nocov:
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
wrap_context 'when the query has where: a not_equal block filter' do
|
170
|
+
let(:expected) { [['author', :not_equal, 'Ursula K. LeGuin']] }
|
171
|
+
|
172
|
+
if operators.include?(OPERATORS::NOT_EQUAL)
|
173
|
+
it { expect(scoped_query.criteria).to be == expected }
|
174
|
+
else
|
175
|
+
# :nocov:
|
176
|
+
pending
|
177
|
+
# :nocov:
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
describe '#each' do
|
183
|
+
shared_examples 'should enumerate the matching data' do
|
184
|
+
describe 'with no arguments' do
|
185
|
+
it { expect(scoped_query.each).to be_a Enumerator }
|
186
|
+
|
187
|
+
it { expect(scoped_query.each.count).to be == matching_data.size }
|
188
|
+
|
189
|
+
it { expect(scoped_query.each.to_a).to deep_match expected_data }
|
190
|
+
end
|
191
|
+
|
192
|
+
describe 'with a block' do
|
193
|
+
it 'should yield each matching item' do
|
194
|
+
expect { |block| scoped_query.each(&block) }
|
195
|
+
.to yield_successive_args(*expected_data)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
let(:data) { [] }
|
201
|
+
let(:matching_data) { data }
|
202
|
+
let(:expected_data) do
|
203
|
+
defined?(super()) ? super() : matching_data
|
204
|
+
end
|
205
|
+
|
206
|
+
it { expect(query).to respond_to(:each).with(0).arguments }
|
207
|
+
|
208
|
+
include_examples 'should enumerate the matching data'
|
209
|
+
|
210
|
+
include_contract 'should perform queries',
|
211
|
+
block: lambda {
|
212
|
+
include_examples 'should enumerate the matching data'
|
213
|
+
},
|
214
|
+
operators: operators
|
215
|
+
|
216
|
+
wrap_context 'when the query has composed filters' do
|
217
|
+
include_examples 'should enumerate the matching data'
|
218
|
+
end
|
219
|
+
|
220
|
+
context 'when the collection data changes' do
|
221
|
+
let(:item) { BOOKS_FIXTURES.first }
|
222
|
+
|
223
|
+
before(:example) do
|
224
|
+
query.each {} # Cache query results.
|
225
|
+
|
226
|
+
add_item_to_collection(item)
|
227
|
+
end
|
228
|
+
|
229
|
+
include_examples 'should enumerate the matching data'
|
230
|
+
end
|
231
|
+
|
232
|
+
context 'when the collection has many items' do
|
233
|
+
let(:data) { BOOKS_FIXTURES }
|
234
|
+
|
235
|
+
include_examples 'should enumerate the matching data'
|
236
|
+
|
237
|
+
include_contract 'should perform queries',
|
238
|
+
block: lambda {
|
239
|
+
include_examples 'should enumerate the matching data'
|
240
|
+
},
|
241
|
+
operators: operators
|
242
|
+
|
243
|
+
wrap_context 'when the query has composed filters' do
|
244
|
+
include_examples 'should enumerate the matching data'
|
245
|
+
end
|
246
|
+
|
247
|
+
context 'when the collection data changes' do
|
248
|
+
let(:data) { BOOKS_FIXTURES[0...-1] }
|
249
|
+
let(:item) { BOOKS_FIXTURES.last }
|
250
|
+
|
251
|
+
before(:example) do
|
252
|
+
query.each {} # Cache query results.
|
253
|
+
|
254
|
+
add_item_to_collection(item)
|
255
|
+
end
|
256
|
+
|
257
|
+
include_examples 'should enumerate the matching data'
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
describe '#exists?' do
|
263
|
+
shared_examples 'should check the existence of matching data' do
|
264
|
+
it { expect(query.exists?).to be == !matching_data.empty? }
|
265
|
+
end
|
266
|
+
|
267
|
+
let(:data) { [] }
|
268
|
+
let(:matching_data) { data }
|
269
|
+
|
270
|
+
include_examples 'should define predicate', :exists?
|
271
|
+
|
272
|
+
include_examples 'should check the existence of matching data'
|
273
|
+
|
274
|
+
include_contract 'should perform queries',
|
275
|
+
block: lambda {
|
276
|
+
include_examples 'should check the existence of matching data'
|
277
|
+
},
|
278
|
+
operators: operators
|
279
|
+
|
280
|
+
wrap_context 'when the query has composed filters' do
|
281
|
+
include_examples 'should check the existence of matching data'
|
282
|
+
end
|
283
|
+
|
284
|
+
context 'when the collection has many items' do
|
285
|
+
let(:data) { BOOKS_FIXTURES }
|
286
|
+
|
287
|
+
include_examples 'should check the existence of matching data'
|
288
|
+
|
289
|
+
include_contract 'should perform queries',
|
290
|
+
block: lambda {
|
291
|
+
include_examples 'should check the existence of matching data'
|
292
|
+
},
|
293
|
+
operators: operators
|
294
|
+
|
295
|
+
wrap_context 'when the query has composed filters' do
|
296
|
+
include_examples 'should check the existence of matching data'
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
describe '#limit' do
|
302
|
+
it { expect(query).to respond_to(:limit).with(0..1).arguments }
|
303
|
+
|
304
|
+
describe 'with no arguments' do
|
305
|
+
it { expect(query.limit).to be nil }
|
306
|
+
end
|
307
|
+
|
308
|
+
describe 'with nil' do
|
309
|
+
let(:error_message) { 'limit must be a non-negative integer' }
|
310
|
+
|
311
|
+
it 'should raise an exception' do
|
312
|
+
expect { query.limit nil }
|
313
|
+
.to raise_error ArgumentError, error_message
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
describe 'with an object' do
|
318
|
+
let(:error_message) { 'limit must be a non-negative integer' }
|
319
|
+
|
320
|
+
it 'should raise an exception' do
|
321
|
+
expect { query.limit Object.new.freeze }
|
322
|
+
.to raise_error ArgumentError, error_message
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
describe 'with a negative integer' do
|
327
|
+
let(:error_message) { 'limit must be a non-negative integer' }
|
328
|
+
|
329
|
+
it 'should raise an exception' do
|
330
|
+
expect { query.limit(-1) }
|
331
|
+
.to raise_error ArgumentError, error_message
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
describe 'with zero' do
|
336
|
+
it { expect(query.limit(0)).to be_a described_class }
|
337
|
+
|
338
|
+
it { expect(query.limit(0)).not_to be query }
|
339
|
+
|
340
|
+
it { expect(query.limit(0).limit).to be 0 }
|
341
|
+
end
|
342
|
+
|
343
|
+
describe 'with a positive integer' do
|
344
|
+
it { expect(query.limit(3)).to be_a described_class }
|
345
|
+
|
346
|
+
it { expect(query.limit(3)).not_to be query }
|
347
|
+
|
348
|
+
it { expect(query.limit(3).limit).to be 3 }
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
describe '#offset' do
|
353
|
+
it { expect(query).to respond_to(:offset).with(0..1).argument }
|
354
|
+
|
355
|
+
describe 'with no arguments' do
|
356
|
+
it { expect(query.offset).to be nil }
|
357
|
+
end
|
358
|
+
|
359
|
+
describe 'with nil' do
|
360
|
+
let(:error_message) { 'offset must be a non-negative integer' }
|
361
|
+
|
362
|
+
it 'should raise an exception' do
|
363
|
+
expect { query.offset nil }
|
364
|
+
.to raise_error ArgumentError, error_message
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
describe 'with an object' do
|
369
|
+
let(:error_message) { 'offset must be a non-negative integer' }
|
370
|
+
|
371
|
+
it 'should raise an exception' do
|
372
|
+
expect { query.offset Object.new.freeze }
|
373
|
+
.to raise_error ArgumentError, error_message
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
describe 'with a negative integer' do
|
378
|
+
let(:error_message) { 'offset must be a non-negative integer' }
|
379
|
+
|
380
|
+
it 'should raise an exception' do
|
381
|
+
expect { query.offset(-1) }
|
382
|
+
.to raise_error ArgumentError, error_message
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
describe 'with zero' do
|
387
|
+
it { expect(query.offset(0)).to be_a described_class }
|
388
|
+
|
389
|
+
it { expect(query.offset(0)).not_to be query }
|
390
|
+
|
391
|
+
it { expect(query.offset(0).offset).to be 0 }
|
392
|
+
end
|
393
|
+
|
394
|
+
describe 'with a positive integer' do
|
395
|
+
it { expect(query.offset(3)).to be_a described_class }
|
396
|
+
|
397
|
+
it { expect(query.offset(3)).not_to be query }
|
398
|
+
|
399
|
+
it { expect(query.offset(3).offset).to be 3 }
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
describe '#order' do
|
404
|
+
let(:default_order) { defined?(super()) ? super() : {} }
|
405
|
+
let(:error_message) do
|
406
|
+
'order must be a list of attribute names and/or a hash of ' \
|
407
|
+
'attribute names with values :asc or :desc'
|
408
|
+
end
|
409
|
+
|
410
|
+
it 'should define the method' do
|
411
|
+
expect(query)
|
412
|
+
.to respond_to(:order)
|
413
|
+
.with(0).arguments
|
414
|
+
.and_unlimited_arguments
|
415
|
+
end
|
416
|
+
|
417
|
+
it { expect(query).to have_aliased_method(:order).as(:order_by) }
|
418
|
+
|
419
|
+
describe 'with no arguments' do
|
420
|
+
it { expect(query.order).to be == default_order }
|
421
|
+
end
|
422
|
+
|
423
|
+
describe 'with a hash with invalid keys' do
|
424
|
+
it 'should raise an exception' do
|
425
|
+
expect { query.order({ nil => :asc }) }
|
426
|
+
.to raise_error ArgumentError, error_message
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
describe 'with a hash with empty string keys' do
|
431
|
+
it 'should raise an exception' do
|
432
|
+
expect { query.order({ '' => :asc }) }
|
433
|
+
.to raise_error ArgumentError, error_message
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
describe 'with a hash with empty symbol keys' do
|
438
|
+
it 'should raise an exception' do
|
439
|
+
expect { query.order({ '': :asc }) }
|
440
|
+
.to raise_error ArgumentError, error_message
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
describe 'with a hash with nil value' do
|
445
|
+
it 'should raise an exception' do
|
446
|
+
expect { query.order({ title: nil }) }
|
447
|
+
.to raise_error ArgumentError, error_message
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
describe 'with a hash with object value' do
|
452
|
+
it 'should raise an exception' do
|
453
|
+
expect { query.order({ title: Object.new.freeze }) }
|
454
|
+
.to raise_error ArgumentError, error_message
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
describe 'with a hash with empty value' do
|
459
|
+
it 'should raise an exception' do
|
460
|
+
expect { query.order({ title: '' }) }
|
461
|
+
.to raise_error ArgumentError, error_message
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
describe 'with a hash with invalid value' do
|
466
|
+
it 'should raise an exception' do
|
467
|
+
expect { query.order({ title: 'wibbly' }) }
|
468
|
+
.to raise_error ArgumentError, error_message
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
describe 'with a valid ordering' do
|
473
|
+
let(:expected) do
|
474
|
+
{ title: :asc }
|
475
|
+
end
|
476
|
+
|
477
|
+
it { expect(query.order(:title)).to be_a described_class }
|
478
|
+
|
479
|
+
it { expect(query.order(:title)).not_to be query }
|
480
|
+
|
481
|
+
it { expect(query.order(:title).order).to be == expected }
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
describe '#reset' do
|
486
|
+
let(:data) { [] }
|
487
|
+
let(:matching_data) { data }
|
488
|
+
let(:expected_data) do
|
489
|
+
defined?(super()) ? super() : matching_data
|
490
|
+
end
|
491
|
+
|
492
|
+
it { expect(query).to respond_to(:reset).with(0).arguments }
|
493
|
+
|
494
|
+
it { expect(query.reset).to be_a query.class }
|
495
|
+
|
496
|
+
it { expect(query.reset).not_to be query }
|
497
|
+
|
498
|
+
it { expect(query.reset.to_a).to be == query.to_a }
|
499
|
+
|
500
|
+
context 'when the collection data changes' do
|
501
|
+
let(:item) { BOOKS_FIXTURES.first }
|
502
|
+
let(:matching_data) { [item] }
|
503
|
+
|
504
|
+
before(:example) do
|
505
|
+
query.to_a # Cache query results.
|
506
|
+
|
507
|
+
add_item_to_collection(item)
|
508
|
+
end
|
509
|
+
|
510
|
+
it { expect(query.reset.count).to be expected_data.size }
|
511
|
+
|
512
|
+
it { expect(query.reset.to_a).to deep_match expected_data }
|
513
|
+
end
|
514
|
+
|
515
|
+
context 'when the collection has many items' do
|
516
|
+
let(:data) { BOOKS_FIXTURES }
|
517
|
+
|
518
|
+
it { expect(query.reset).to be_a query.class }
|
519
|
+
|
520
|
+
it { expect(query.reset).not_to be query }
|
521
|
+
|
522
|
+
it { expect(query.reset.to_a).to be == query.to_a }
|
523
|
+
|
524
|
+
context 'when the collection data changes' do
|
525
|
+
let(:data) { BOOKS_FIXTURES[0...-1] }
|
526
|
+
let(:item) { BOOKS_FIXTURES.last }
|
527
|
+
let(:matching_data) { [*data, item] }
|
528
|
+
|
529
|
+
before(:example) do
|
530
|
+
query.to_a # Cache query results.
|
531
|
+
|
532
|
+
add_item_to_collection(item)
|
533
|
+
end
|
534
|
+
|
535
|
+
it { expect(query.reset.count).to be expected_data.size }
|
536
|
+
|
537
|
+
it { expect(query.reset.to_a).to deep_match expected_data }
|
538
|
+
end
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
542
|
+
describe '#to_a' do
|
543
|
+
let(:data) { [] }
|
544
|
+
let(:matching_data) { data }
|
545
|
+
let(:expected_data) do
|
546
|
+
defined?(super()) ? super() : matching_data
|
547
|
+
end
|
548
|
+
|
549
|
+
it { expect(query).to respond_to(:to_a).with(0).arguments }
|
550
|
+
|
551
|
+
it { expect(query.to_a).to deep_match expected_data }
|
552
|
+
|
553
|
+
include_contract 'should perform queries',
|
554
|
+
block: lambda {
|
555
|
+
it { expect(scoped_query.to_a).to deep_match expected_data }
|
556
|
+
},
|
557
|
+
operators: operators
|
558
|
+
|
559
|
+
wrap_context 'when the query has composed filters' do
|
560
|
+
it { expect(scoped_query.to_a).to deep_match expected_data }
|
561
|
+
end
|
562
|
+
|
563
|
+
context 'when the collection data changes' do
|
564
|
+
let(:item) { BOOKS_FIXTURES.first }
|
565
|
+
|
566
|
+
before(:example) do
|
567
|
+
query.to_a # Cache query results.
|
568
|
+
|
569
|
+
add_item_to_collection(item)
|
570
|
+
end
|
571
|
+
|
572
|
+
it { expect(query.to_a).to deep_match expected_data }
|
573
|
+
end
|
574
|
+
|
575
|
+
context 'when the collection has many items' do
|
576
|
+
let(:data) { BOOKS_FIXTURES }
|
577
|
+
|
578
|
+
it { expect(query.to_a).to deep_match expected_data }
|
579
|
+
|
580
|
+
include_contract 'should perform queries',
|
581
|
+
block: lambda {
|
582
|
+
it { expect(scoped_query.to_a).to deep_match expected_data }
|
583
|
+
},
|
584
|
+
operators: operators
|
585
|
+
|
586
|
+
wrap_context 'when the query has composed filters' do
|
587
|
+
it { expect(scoped_query.to_a).to deep_match expected_data }
|
588
|
+
end
|
589
|
+
|
590
|
+
context 'when the collection data changes' do
|
591
|
+
let(:data) { BOOKS_FIXTURES[0...-1] }
|
592
|
+
let(:item) { BOOKS_FIXTURES.last }
|
593
|
+
|
594
|
+
before(:example) do
|
595
|
+
query.to_a # Cache query results.
|
596
|
+
|
597
|
+
add_item_to_collection(item)
|
598
|
+
end
|
599
|
+
|
600
|
+
it { expect(query.to_a).to deep_match expected_data }
|
601
|
+
end
|
602
|
+
end
|
603
|
+
end
|
604
|
+
|
605
|
+
describe '#where' do
|
606
|
+
let(:block) { -> { { title: 'The Caves of Steel' } } }
|
607
|
+
|
608
|
+
it 'should define the method' do
|
609
|
+
expect(query)
|
610
|
+
.to respond_to(:where)
|
611
|
+
.with(0..1).arguments
|
612
|
+
.and_keywords(:strategy)
|
613
|
+
.and_a_block
|
614
|
+
end
|
615
|
+
|
616
|
+
describe 'with no arguments' do
|
617
|
+
it { expect(query.where).to be_a described_class }
|
618
|
+
|
619
|
+
it { expect(query.where).not_to be query }
|
620
|
+
end
|
621
|
+
|
622
|
+
describe 'with a block' do
|
623
|
+
it { expect(query.where(&block)).to be_a described_class }
|
624
|
+
|
625
|
+
it { expect(query.where(&block)).not_to be query }
|
626
|
+
end
|
627
|
+
|
628
|
+
describe 'with a valid strategy' do
|
629
|
+
it 'should return a query instance' do
|
630
|
+
expect(query.where(strategy: :block, &block))
|
631
|
+
.to be_a described_class
|
632
|
+
end
|
633
|
+
|
634
|
+
it { expect(query.where(strategy: :block, &block)).not_to be query }
|
635
|
+
end
|
636
|
+
|
637
|
+
describe 'with parameters that do not match a strategy' do
|
638
|
+
let(:error_class) do
|
639
|
+
Cuprum::Collections::QueryBuilder::ParseError
|
640
|
+
end
|
641
|
+
let(:error_message) { 'unable to parse query with strategy nil' }
|
642
|
+
|
643
|
+
it 'should raise an exception' do
|
644
|
+
expect { query.where(%w[ichi ni san]) }
|
645
|
+
.to raise_error error_class, error_message
|
646
|
+
end
|
647
|
+
end
|
648
|
+
|
649
|
+
describe 'with an invalid strategy' do
|
650
|
+
let(:error_class) do
|
651
|
+
Cuprum::Collections::QueryBuilder::ParseError
|
652
|
+
end
|
653
|
+
let(:error_message) do
|
654
|
+
'unable to parse query with strategy :random'
|
655
|
+
end
|
656
|
+
|
657
|
+
it 'should raise an exception' do
|
658
|
+
expect { query.where(strategy: :random) }
|
659
|
+
.to raise_error error_class, error_message
|
660
|
+
end
|
661
|
+
end
|
662
|
+
|
663
|
+
describe 'with invalid parameters for a strategy' do
|
664
|
+
let(:error_class) do
|
665
|
+
Cuprum::Collections::QueryBuilder::ParseError
|
666
|
+
end
|
667
|
+
let(:error_message) { 'unable to parse query with strategy :block' }
|
668
|
+
|
669
|
+
it 'should raise an exception' do
|
670
|
+
expect { query.where(strategy: :block) }
|
671
|
+
.to raise_error error_class, error_message
|
672
|
+
end
|
673
|
+
end
|
674
|
+
end
|
675
|
+
end
|
676
|
+
end
|
677
|
+
|
678
|
+
# Contract validating the behavior of a QueryBuilder implementation.
|
679
|
+
module ShouldBeAQueryBuilderContract
|
680
|
+
extend RSpec::SleepingKingStudios::Contract
|
681
|
+
|
682
|
+
# @!method apply(example_group)
|
683
|
+
# Adds the contract to the example group.
|
684
|
+
#
|
685
|
+
# @param example_group [RSpec::Core::ExampleGroup] the example group to
|
686
|
+
# which the contract is applied.
|
687
|
+
contract do
|
688
|
+
describe '#base_query' do
|
689
|
+
include_examples 'should define reader',
|
690
|
+
:base_query,
|
691
|
+
-> { base_query }
|
692
|
+
end
|
693
|
+
|
694
|
+
describe '#call' do
|
695
|
+
let(:criteria) { [['title', :equal, 'The Naked Sun']] }
|
696
|
+
let(:expected) { criteria }
|
697
|
+
let(:filter) { { title: 'The Naked Sun' } }
|
698
|
+
let(:strategy) { :custom }
|
699
|
+
let(:parser) do
|
700
|
+
instance_double(
|
701
|
+
Cuprum::Collections::Queries::Parse,
|
702
|
+
call: Cuprum::Result.new(value: criteria)
|
703
|
+
)
|
704
|
+
end
|
705
|
+
let(:query) do
|
706
|
+
builder.call(strategy: strategy, where: filter)
|
707
|
+
end
|
708
|
+
|
709
|
+
before(:example) do
|
710
|
+
allow(Cuprum::Collections::Queries::Parse)
|
711
|
+
.to receive(:new)
|
712
|
+
.and_return(parser)
|
713
|
+
end
|
714
|
+
|
715
|
+
it 'should define the method' do
|
716
|
+
expect(builder).to respond_to(:call)
|
717
|
+
.with(0).arguments
|
718
|
+
.and_keywords(:strategy, :where)
|
719
|
+
end
|
720
|
+
|
721
|
+
it 'should parse the criteria' do
|
722
|
+
builder.call(strategy: strategy, where: filter)
|
723
|
+
|
724
|
+
expect(parser)
|
725
|
+
.to have_received(:call)
|
726
|
+
.with(strategy: strategy, where: filter)
|
727
|
+
end
|
728
|
+
|
729
|
+
it { expect(query).to be_a base_query.class }
|
730
|
+
|
731
|
+
it { expect(query).not_to be base_query }
|
732
|
+
|
733
|
+
it { expect(query.criteria).to be == expected }
|
734
|
+
|
735
|
+
describe 'with strategy: :unsafe' do
|
736
|
+
let(:strategy) { :unsafe }
|
737
|
+
let(:filter) { criteria }
|
738
|
+
|
739
|
+
it 'should not parse the criteria' do
|
740
|
+
builder.call(strategy: strategy, where: filter)
|
741
|
+
|
742
|
+
expect(parser).not_to have_received(:call)
|
743
|
+
end
|
744
|
+
|
745
|
+
it { expect(query.criteria).to be == expected }
|
746
|
+
end
|
747
|
+
|
748
|
+
context 'when the query has existing criteria' do
|
749
|
+
let(:old_criteria) { [['genre', :eq, 'Science Fiction']] }
|
750
|
+
let(:expected) { old_criteria + criteria }
|
751
|
+
let(:base_query) { super().send(:with_criteria, old_criteria) }
|
752
|
+
|
753
|
+
it { expect(query.criteria).to be == expected }
|
754
|
+
end
|
755
|
+
|
756
|
+
context 'when the parser is unable to parse the query' do
|
757
|
+
let(:error) { Cuprum::Error.new(message: 'Something went wrong.') }
|
758
|
+
let(:result) { Cuprum::Result.new(error: error) }
|
759
|
+
|
760
|
+
before(:example) do
|
761
|
+
allow(parser).to receive(:call).and_return(result)
|
762
|
+
end
|
763
|
+
|
764
|
+
it 'should raise an exception' do
|
765
|
+
expect do
|
766
|
+
builder.call(strategy: strategy, where: filter)
|
767
|
+
end
|
768
|
+
.to raise_error Cuprum::Collections::QueryBuilder::ParseError,
|
769
|
+
error.message
|
770
|
+
end
|
771
|
+
end
|
772
|
+
end
|
773
|
+
end
|
774
|
+
end
|
775
|
+
|
776
|
+
# Contract validating the behavior when performing queries.
|
777
|
+
module ShouldPerformQueriesContract
|
778
|
+
extend RSpec::SleepingKingStudios::Contract
|
779
|
+
|
780
|
+
OPERATORS = Cuprum::Collections::Queries::Operators
|
781
|
+
private_constant :OPERATORS
|
782
|
+
|
783
|
+
# @!method apply(example_group, block:, operators:)
|
784
|
+
# Adds the contract to the example group.
|
785
|
+
#
|
786
|
+
# @param example_group [RSpec::Core::ExampleGroup] the example group to
|
787
|
+
# which the contract is applied.
|
788
|
+
# @param block [Proc] the expectations for each query context.
|
789
|
+
# @param operators [Array<Symbol>] the expected operators.
|
790
|
+
contract do |block:, operators: OPERATORS.values|
|
791
|
+
operators = Set.new(operators.map(&:to_sym))
|
792
|
+
|
793
|
+
wrap_context 'when the query has limit: value' do
|
794
|
+
instance_exec(&block)
|
795
|
+
end
|
796
|
+
|
797
|
+
wrap_context 'when the query has offset: value' do
|
798
|
+
instance_exec(&block)
|
799
|
+
end
|
800
|
+
|
801
|
+
wrap_context 'when the query has order: a simple ordering' do
|
802
|
+
instance_exec(&block)
|
803
|
+
end
|
804
|
+
|
805
|
+
wrap_context 'when the query has order: a complex ordering' do
|
806
|
+
instance_exec(&block)
|
807
|
+
end
|
808
|
+
|
809
|
+
context 'when the query has where: a block filter' do
|
810
|
+
context 'with a simple filter' do
|
811
|
+
include_context 'when the query has where: a simple block filter'
|
812
|
+
|
813
|
+
instance_exec(&block)
|
814
|
+
end
|
815
|
+
|
816
|
+
context 'with a complex filter' do
|
817
|
+
include_context 'when the query has where: a complex block filter'
|
818
|
+
|
819
|
+
if operators.include?(OPERATORS::EQUAL) &&
|
820
|
+
operators.include?(OPERATORS::NOT_EQUAL)
|
821
|
+
instance_exec(&block)
|
822
|
+
else
|
823
|
+
# :nocov:
|
824
|
+
pending
|
825
|
+
# :nocov:
|
826
|
+
end
|
827
|
+
end
|
828
|
+
|
829
|
+
context 'with an equals filter' do
|
830
|
+
include_context 'when the query has where: an equal block filter'
|
831
|
+
|
832
|
+
if operators.include?(OPERATORS::EQUAL)
|
833
|
+
instance_exec(&block)
|
834
|
+
else
|
835
|
+
# :nocov:
|
836
|
+
pending
|
837
|
+
# :nocov:
|
838
|
+
end
|
839
|
+
end
|
840
|
+
|
841
|
+
context 'with a greater_than filter' do
|
842
|
+
include_context 'when the query has where: a greater_than filter'
|
843
|
+
|
844
|
+
if operators.include?(OPERATORS::GREATER_THAN)
|
845
|
+
instance_exec(&block)
|
846
|
+
else
|
847
|
+
# :nocov:
|
848
|
+
pending
|
849
|
+
# :nocov:
|
850
|
+
end
|
851
|
+
end
|
852
|
+
|
853
|
+
context 'with a greater_than_or_equal_to filter' do
|
854
|
+
include_context \
|
855
|
+
'when the query has where: a greater_than_or_equal_to filter'
|
856
|
+
|
857
|
+
if operators.include?(OPERATORS::GREATER_THAN_OR_EQUAL_TO)
|
858
|
+
instance_exec(&block)
|
859
|
+
else
|
860
|
+
# :nocov:
|
861
|
+
pending
|
862
|
+
# :nocov:
|
863
|
+
end
|
864
|
+
end
|
865
|
+
|
866
|
+
context 'with a less_than filter' do
|
867
|
+
include_context 'when the query has where: a less_than filter'
|
868
|
+
|
869
|
+
if operators.include?(OPERATORS::LESS_THAN)
|
870
|
+
instance_exec(&block)
|
871
|
+
else
|
872
|
+
# :nocov:
|
873
|
+
pending
|
874
|
+
# :nocov:
|
875
|
+
end
|
876
|
+
end
|
877
|
+
|
878
|
+
context 'with a less_than_or_equal_to filter' do
|
879
|
+
include_context \
|
880
|
+
'when the query has where: a less_than_or_equal_to filter'
|
881
|
+
|
882
|
+
if operators.include?(OPERATORS::LESS_THAN_OR_EQUAL_TO)
|
883
|
+
instance_exec(&block)
|
884
|
+
else
|
885
|
+
# :nocov:
|
886
|
+
pending
|
887
|
+
# :nocov:
|
888
|
+
end
|
889
|
+
end
|
890
|
+
|
891
|
+
context 'with a not_equal filter' do
|
892
|
+
include_context 'when the query has where: a not_equal block filter'
|
893
|
+
|
894
|
+
if operators.include?(OPERATORS::NOT_EQUAL)
|
895
|
+
instance_exec(&block)
|
896
|
+
else
|
897
|
+
# :nocov:
|
898
|
+
pending
|
899
|
+
# :nocov:
|
900
|
+
end
|
901
|
+
end
|
902
|
+
|
903
|
+
context 'with a not_one_of filter' do
|
904
|
+
include_context \
|
905
|
+
'when the query has where: a not_one_of block filter'
|
906
|
+
|
907
|
+
if operators.include?(OPERATORS::NOT_ONE_OF)
|
908
|
+
instance_exec(&block)
|
909
|
+
else
|
910
|
+
# :nocov:
|
911
|
+
pending
|
912
|
+
# :nocov:
|
913
|
+
end
|
914
|
+
end
|
915
|
+
|
916
|
+
context 'with a one_of filter' do
|
917
|
+
include_context 'when the query has where: a one_of block filter'
|
918
|
+
|
919
|
+
if operators.include?(OPERATORS::ONE_OF)
|
920
|
+
instance_exec(&block)
|
921
|
+
else
|
922
|
+
# :nocov:
|
923
|
+
pending
|
924
|
+
# :nocov:
|
925
|
+
end
|
926
|
+
end
|
927
|
+
end
|
928
|
+
|
929
|
+
wrap_context 'when the query has multiple query options' do
|
930
|
+
instance_exec(&block)
|
931
|
+
end
|
932
|
+
end
|
933
|
+
end
|
934
|
+
|
935
|
+
# Contract defining contexts for validating query behavior.
|
936
|
+
module WithQueryContextsContract
|
937
|
+
extend RSpec::SleepingKingStudios::Contract
|
938
|
+
|
939
|
+
# @!method apply(example_group)
|
940
|
+
# Adds the contract to the example group.
|
941
|
+
#
|
942
|
+
# @param example_group [RSpec::Core::ExampleGroup] the example group to
|
943
|
+
# which the contract is applied.
|
944
|
+
contract do
|
945
|
+
let(:filter) { nil }
|
946
|
+
let(:strategy) { nil }
|
947
|
+
let(:limit) { nil }
|
948
|
+
let(:offset) { nil }
|
949
|
+
let(:order) { nil }
|
950
|
+
|
951
|
+
shared_context 'when the query has limit: value' do
|
952
|
+
let(:limit) { 3 }
|
953
|
+
let(:matching_data) { super()[0...limit] }
|
954
|
+
end
|
955
|
+
|
956
|
+
shared_context 'when the query has offset: value' do
|
957
|
+
let(:offset) { 2 }
|
958
|
+
let(:matching_data) { super()[offset..] || [] }
|
959
|
+
end
|
960
|
+
|
961
|
+
shared_context 'when the query has order: a simple ordering' do
|
962
|
+
let(:order) { :title }
|
963
|
+
let(:matching_data) { super().sort_by { |item| item['title'] } }
|
964
|
+
end
|
965
|
+
|
966
|
+
shared_context 'when the query has order: a complex ordering' do
|
967
|
+
let(:order) do
|
968
|
+
{
|
969
|
+
author: :asc,
|
970
|
+
title: :desc
|
971
|
+
}
|
972
|
+
end
|
973
|
+
let(:matching_data) do
|
974
|
+
super().sort do |u, v|
|
975
|
+
cmp = u['author'] <=> v['author']
|
976
|
+
|
977
|
+
cmp.zero? ? (v['title'] <=> u['title']) : cmp
|
978
|
+
end
|
979
|
+
end
|
980
|
+
end
|
981
|
+
|
982
|
+
shared_context 'when the query has where: a simple block filter' do
|
983
|
+
let(:filter) { -> { { author: 'Ursula K. LeGuin' } } }
|
984
|
+
let(:matching_data) do
|
985
|
+
super().select { |item| item['author'] == 'Ursula K. LeGuin' }
|
986
|
+
end
|
987
|
+
end
|
988
|
+
|
989
|
+
shared_context 'when the query has where: a complex block filter' do
|
990
|
+
let(:filter) do
|
991
|
+
lambda do
|
992
|
+
{
|
993
|
+
author: equals('Ursula K. LeGuin'),
|
994
|
+
series: not_equal('Earthsea')
|
995
|
+
}
|
996
|
+
end
|
997
|
+
end
|
998
|
+
let(:matching_data) do
|
999
|
+
super()
|
1000
|
+
.select { |item| item['author'] == 'Ursula K. LeGuin' }
|
1001
|
+
.reject { |item| item['series'] == 'Earthsea' }
|
1002
|
+
end
|
1003
|
+
end
|
1004
|
+
|
1005
|
+
shared_context 'when the query has where: a greater_than filter' do
|
1006
|
+
let(:filter) { -> { { published_at: greater_than('1970-12-01') } } }
|
1007
|
+
let(:matching_data) do
|
1008
|
+
super().select { |item| item['published_at'] > '1970-12-01' }
|
1009
|
+
end
|
1010
|
+
end
|
1011
|
+
|
1012
|
+
shared_context 'when the query has where: a greater_than_or_equal_to ' \
|
1013
|
+
'filter' \
|
1014
|
+
do
|
1015
|
+
let(:filter) do
|
1016
|
+
-> { { published_at: greater_than_or_equal_to('1970-12-01') } }
|
1017
|
+
end
|
1018
|
+
let(:matching_data) do
|
1019
|
+
super().select { |item| item['published_at'] >= '1970-12-01' }
|
1020
|
+
end
|
1021
|
+
end
|
1022
|
+
|
1023
|
+
shared_context 'when the query has where: a less_than filter' do
|
1024
|
+
let(:filter) { -> { { published_at: less_than('1970-12-01') } } }
|
1025
|
+
let(:matching_data) do
|
1026
|
+
super().select { |item| item['published_at'] < '1970-12-01' }
|
1027
|
+
end
|
1028
|
+
end
|
1029
|
+
|
1030
|
+
shared_context 'when the query has where: a ' \
|
1031
|
+
'less_than_or_equal_to filter' \
|
1032
|
+
do
|
1033
|
+
let(:filter) do
|
1034
|
+
-> { { published_at: less_than_or_equal_to('1970-12-01') } }
|
1035
|
+
end
|
1036
|
+
let(:matching_data) do
|
1037
|
+
super().select { |item| item['published_at'] <= '1970-12-01' }
|
1038
|
+
end
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
shared_context 'when the query has where: an equal block filter' do
|
1042
|
+
let(:filter) { -> { { author: equals('Ursula K. LeGuin') } } }
|
1043
|
+
let(:matching_data) do
|
1044
|
+
super().select { |item| item['author'] == 'Ursula K. LeGuin' }
|
1045
|
+
end
|
1046
|
+
end
|
1047
|
+
|
1048
|
+
shared_context 'when the query has where: a not_equal block filter' do
|
1049
|
+
let(:filter) { -> { { author: not_equal('Ursula K. LeGuin') } } }
|
1050
|
+
let(:matching_data) do
|
1051
|
+
super().reject { |item| item['author'] == 'Ursula K. LeGuin' }
|
1052
|
+
end
|
1053
|
+
end
|
1054
|
+
|
1055
|
+
shared_context 'when the query has where: a not_one_of block filter' do
|
1056
|
+
let(:filter) do
|
1057
|
+
-> { { series: not_one_of(['Earthsea', 'The Lord of the Rings']) } }
|
1058
|
+
end
|
1059
|
+
let(:matching_data) do
|
1060
|
+
super().reject do |item|
|
1061
|
+
['Earthsea', 'The Lord of the Rings'].include?(item['series'])
|
1062
|
+
end
|
1063
|
+
end
|
1064
|
+
end
|
1065
|
+
|
1066
|
+
shared_context 'when the query has where: a one_of block filter' do
|
1067
|
+
let(:filter) do
|
1068
|
+
-> { { series: one_of(['Earthsea', 'The Lord of the Rings']) } }
|
1069
|
+
end
|
1070
|
+
let(:matching_data) do
|
1071
|
+
super().select do |item|
|
1072
|
+
['Earthsea', 'The Lord of the Rings'].include?(item['series'])
|
1073
|
+
end
|
1074
|
+
end
|
1075
|
+
end
|
1076
|
+
|
1077
|
+
shared_context 'when the query has multiple query options' do
|
1078
|
+
let(:filter) { -> { { author: 'Ursula K. LeGuin' } } }
|
1079
|
+
let(:strategy) { nil }
|
1080
|
+
let(:order) { { title: :desc } }
|
1081
|
+
let(:limit) { 2 }
|
1082
|
+
let(:offset) { 1 }
|
1083
|
+
let(:matching_data) do
|
1084
|
+
super()
|
1085
|
+
.select { |item| item['author'] == 'Ursula K. LeGuin' }
|
1086
|
+
.sort { |u, v| v['title'] <=> u['title'] }
|
1087
|
+
.slice(1, 2) || []
|
1088
|
+
end
|
1089
|
+
end
|
1090
|
+
end
|
1091
|
+
end
|
1092
|
+
end
|
1093
|
+
end
|