cuprum-collections 0.1.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 (72) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +59 -0
  3. data/CODE_OF_CONDUCT.md +132 -0
  4. data/DEVELOPMENT.md +25 -0
  5. data/LICENSE +22 -0
  6. data/README.md +950 -0
  7. data/lib/cuprum/collections/base.rb +11 -0
  8. data/lib/cuprum/collections/basic/collection.rb +135 -0
  9. data/lib/cuprum/collections/basic/command.rb +112 -0
  10. data/lib/cuprum/collections/basic/commands/assign_one.rb +54 -0
  11. data/lib/cuprum/collections/basic/commands/build_one.rb +45 -0
  12. data/lib/cuprum/collections/basic/commands/destroy_one.rb +48 -0
  13. data/lib/cuprum/collections/basic/commands/find_many.rb +65 -0
  14. data/lib/cuprum/collections/basic/commands/find_matching.rb +126 -0
  15. data/lib/cuprum/collections/basic/commands/find_one.rb +49 -0
  16. data/lib/cuprum/collections/basic/commands/insert_one.rb +50 -0
  17. data/lib/cuprum/collections/basic/commands/update_one.rb +52 -0
  18. data/lib/cuprum/collections/basic/commands/validate_one.rb +69 -0
  19. data/lib/cuprum/collections/basic/commands.rb +18 -0
  20. data/lib/cuprum/collections/basic/query.rb +160 -0
  21. data/lib/cuprum/collections/basic/query_builder.rb +69 -0
  22. data/lib/cuprum/collections/basic/rspec/command_contract.rb +392 -0
  23. data/lib/cuprum/collections/basic/rspec.rb +8 -0
  24. data/lib/cuprum/collections/basic.rb +22 -0
  25. data/lib/cuprum/collections/command.rb +26 -0
  26. data/lib/cuprum/collections/commands/abstract_find_many.rb +77 -0
  27. data/lib/cuprum/collections/commands/abstract_find_matching.rb +64 -0
  28. data/lib/cuprum/collections/commands/abstract_find_one.rb +44 -0
  29. data/lib/cuprum/collections/commands.rb +8 -0
  30. data/lib/cuprum/collections/constraints/attribute_name.rb +22 -0
  31. data/lib/cuprum/collections/constraints/order/attributes_array.rb +26 -0
  32. data/lib/cuprum/collections/constraints/order/attributes_hash.rb +27 -0
  33. data/lib/cuprum/collections/constraints/order/complex_ordering.rb +46 -0
  34. data/lib/cuprum/collections/constraints/order/sort_direction.rb +32 -0
  35. data/lib/cuprum/collections/constraints/order.rb +8 -0
  36. data/lib/cuprum/collections/constraints/ordering.rb +114 -0
  37. data/lib/cuprum/collections/constraints/query_hash.rb +25 -0
  38. data/lib/cuprum/collections/constraints.rb +8 -0
  39. data/lib/cuprum/collections/errors/already_exists.rb +86 -0
  40. data/lib/cuprum/collections/errors/extra_attributes.rb +66 -0
  41. data/lib/cuprum/collections/errors/failed_validation.rb +66 -0
  42. data/lib/cuprum/collections/errors/invalid_parameters.rb +50 -0
  43. data/lib/cuprum/collections/errors/invalid_query.rb +55 -0
  44. data/lib/cuprum/collections/errors/missing_default_contract.rb +49 -0
  45. data/lib/cuprum/collections/errors/not_found.rb +81 -0
  46. data/lib/cuprum/collections/errors/unknown_operator.rb +71 -0
  47. data/lib/cuprum/collections/errors.rb +8 -0
  48. data/lib/cuprum/collections/queries/ordering.rb +74 -0
  49. data/lib/cuprum/collections/queries/parse.rb +22 -0
  50. data/lib/cuprum/collections/queries/parse_block.rb +206 -0
  51. data/lib/cuprum/collections/queries/parse_strategy.rb +91 -0
  52. data/lib/cuprum/collections/queries.rb +25 -0
  53. data/lib/cuprum/collections/query.rb +247 -0
  54. data/lib/cuprum/collections/query_builder.rb +61 -0
  55. data/lib/cuprum/collections/rspec/assign_one_command_contract.rb +168 -0
  56. data/lib/cuprum/collections/rspec/build_one_command_contract.rb +93 -0
  57. data/lib/cuprum/collections/rspec/collection_contract.rb +153 -0
  58. data/lib/cuprum/collections/rspec/destroy_one_command_contract.rb +106 -0
  59. data/lib/cuprum/collections/rspec/find_many_command_contract.rb +327 -0
  60. data/lib/cuprum/collections/rspec/find_matching_command_contract.rb +194 -0
  61. data/lib/cuprum/collections/rspec/find_one_command_contract.rb +154 -0
  62. data/lib/cuprum/collections/rspec/fixtures.rb +89 -0
  63. data/lib/cuprum/collections/rspec/insert_one_command_contract.rb +83 -0
  64. data/lib/cuprum/collections/rspec/query_builder_contract.rb +92 -0
  65. data/lib/cuprum/collections/rspec/query_contract.rb +650 -0
  66. data/lib/cuprum/collections/rspec/querying_contract.rb +298 -0
  67. data/lib/cuprum/collections/rspec/update_one_command_contract.rb +79 -0
  68. data/lib/cuprum/collections/rspec/validate_one_command_contract.rb +96 -0
  69. data/lib/cuprum/collections/rspec.rb +8 -0
  70. data/lib/cuprum/collections/version.rb +59 -0
  71. data/lib/cuprum/collections.rb +26 -0
  72. metadata +219 -0
@@ -0,0 +1,650 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections/rspec'
4
+ require 'cuprum/collections/rspec/fixtures'
5
+ require 'cuprum/collections/rspec/querying_contract'
6
+
7
+ module Cuprum::Collections::RSpec # rubocop:disable Style/Documentation
8
+ default_operators = Cuprum::Collections::Queries::Operators.values
9
+
10
+ # Contract validating the behavior of a Query implementation.
11
+ QUERY_CONTRACT = lambda do |operators: default_operators.freeze|
12
+ operators = Set.new(operators.map(&:to_sym))
13
+
14
+ include_contract Cuprum::Collections::RSpec::QUERYING_CONTEXTS
15
+
16
+ shared_context 'when the query has composed filters' do
17
+ let(:scoped_query) do
18
+ super()
19
+ .where { { author: 'Ursula K. LeGuin' } }
20
+ .where { { series: not_equal('Earthsea') } }
21
+ end
22
+ let(:matching_data) do
23
+ super()
24
+ .select { |item| item['author'] == 'Ursula K. LeGuin' }
25
+ .reject { |item| item['series'] == 'Earthsea' }
26
+ end
27
+ end
28
+
29
+ let(:scoped_query) do
30
+ # :nocov:
31
+ scoped =
32
+ if filter.is_a?(Proc)
33
+ query.where(&filter)
34
+ elsif !filter.nil?
35
+ query.where(filter)
36
+ else
37
+ query
38
+ end
39
+ # :nocov:
40
+ scoped = scoped.limit(limit) if limit
41
+ scoped = scoped.offset(offset) if offset
42
+ scoped = scoped.order(order) if order
43
+
44
+ scoped
45
+ end
46
+
47
+ it 'should be enumerable' do
48
+ expect(described_class).to be < Enumerable
49
+ end
50
+
51
+ describe '#count' do
52
+ let(:data) { [] }
53
+ let(:matching_data) { data }
54
+ let(:expected_data) do
55
+ defined?(super()) ? super() : matching_data
56
+ end
57
+
58
+ it { expect(query).to respond_to(:count).with(0).arguments }
59
+
60
+ it { expect(query.count).to be == expected_data.count }
61
+
62
+ wrap_context 'when the query has composed filters' do
63
+ it { expect(scoped_query.count).to be == expected_data.count }
64
+ end
65
+
66
+ context 'when the collection data changes' do
67
+ let(:item) { BOOKS_FIXTURES.first }
68
+
69
+ before(:example) do
70
+ query.count # Cache query results.
71
+
72
+ add_item_to_collection(item)
73
+ end
74
+
75
+ it { expect(query.count).to be == expected_data.count }
76
+ end
77
+
78
+ context 'when the collection has many items' do
79
+ let(:data) { BOOKS_FIXTURES }
80
+
81
+ it { expect(query.count).to be == expected_data.count }
82
+
83
+ wrap_context 'when the query has composed filters' do
84
+ it { expect(scoped_query.count).to be == expected_data.count }
85
+ end
86
+
87
+ context 'when the collection data changes' do
88
+ let(:data) { BOOKS_FIXTURES[0...-1] }
89
+ let(:item) { BOOKS_FIXTURES.last }
90
+
91
+ before(:example) do
92
+ query.count # Cache query results.
93
+
94
+ add_item_to_collection(item)
95
+ end
96
+
97
+ it { expect(query.count).to be == expected_data.count }
98
+ end
99
+ end
100
+ end
101
+
102
+ describe '#criteria' do
103
+ include_examples 'should have reader', :criteria, []
104
+
105
+ wrap_context 'when the query has where: a simple block filter' do
106
+ let(:expected) { [['author', :equal, 'Ursula K. LeGuin']] }
107
+
108
+ it { expect(scoped_query.criteria).to be == expected }
109
+ end
110
+
111
+ wrap_context 'when the query has where: a complex block filter' do
112
+ let(:expected) do
113
+ [
114
+ ['author', :equal, 'Ursula K. LeGuin'],
115
+ ['series', :not_equal, 'Earthsea']
116
+ ]
117
+ end
118
+
119
+ if operators.include?(OPERATORS::EQUAL) &&
120
+ operators.include?(OPERATORS::NOT_EQUAL)
121
+ it { expect(scoped_query.criteria).to be == expected }
122
+ else
123
+ # :nocov:
124
+ pending
125
+ # :nocov:
126
+ end
127
+ end
128
+
129
+ wrap_context 'when the query has composed filters' do
130
+ let(:expected) do
131
+ [
132
+ ['author', :equal, 'Ursula K. LeGuin'],
133
+ ['series', :not_equal, 'Earthsea']
134
+ ]
135
+ end
136
+
137
+ it { expect(scoped_query.criteria).to be == expected }
138
+ end
139
+
140
+ wrap_context 'when the query has where: an equal block filter' do
141
+ let(:expected) { [['author', :equal, 'Ursula K. LeGuin']] }
142
+
143
+ if operators.include?(OPERATORS::EQUAL)
144
+ it { expect(scoped_query.criteria).to be == expected }
145
+ else
146
+ # :nocov:
147
+ pending
148
+ # :nocov:
149
+ end
150
+ end
151
+
152
+ wrap_context 'when the query has where: a not_equal block filter' do
153
+ let(:expected) { [['author', :not_equal, 'Ursula K. LeGuin']] }
154
+
155
+ if operators.include?(OPERATORS::NOT_EQUAL)
156
+ it { expect(scoped_query.criteria).to be == expected }
157
+ else
158
+ # :nocov:
159
+ pending
160
+ # :nocov:
161
+ end
162
+ end
163
+ end
164
+
165
+ describe '#each' do
166
+ shared_examples 'should enumerate the matching data' do
167
+ describe 'with no arguments' do
168
+ it { expect(scoped_query.each).to be_a Enumerator }
169
+
170
+ it { expect(scoped_query.each.count).to be == matching_data.size }
171
+
172
+ it { expect(scoped_query.each.to_a).to deep_match expected_data }
173
+ end
174
+
175
+ describe 'with a block' do
176
+ it 'should yield each matching item' do
177
+ expect { |block| scoped_query.each(&block) }
178
+ .to yield_successive_args(*expected_data)
179
+ end
180
+ end
181
+ end
182
+
183
+ let(:data) { [] }
184
+ let(:matching_data) { data }
185
+ let(:expected_data) do
186
+ defined?(super()) ? super() : matching_data
187
+ end
188
+
189
+ it { expect(query).to respond_to(:each).with(0).arguments }
190
+
191
+ include_examples 'should enumerate the matching data'
192
+
193
+ include_contract Cuprum::Collections::RSpec::QUERYING_CONTRACT,
194
+ block: lambda {
195
+ include_examples 'should enumerate the matching data'
196
+ },
197
+ operators: operators
198
+
199
+ wrap_context 'when the query has composed filters' do
200
+ include_examples 'should enumerate the matching data'
201
+ end
202
+
203
+ context 'when the collection data changes' do
204
+ let(:item) { BOOKS_FIXTURES.first }
205
+
206
+ before(:example) do
207
+ query.each {} # Cache query results.
208
+
209
+ add_item_to_collection(item)
210
+ end
211
+
212
+ include_examples 'should enumerate the matching data'
213
+ end
214
+
215
+ context 'when the collection has many items' do
216
+ let(:data) { BOOKS_FIXTURES }
217
+
218
+ include_examples 'should enumerate the matching data'
219
+
220
+ include_contract Cuprum::Collections::RSpec::QUERYING_CONTRACT,
221
+ block: lambda {
222
+ include_examples 'should enumerate the matching data'
223
+ },
224
+ operators: operators
225
+
226
+ wrap_context 'when the query has composed filters' do
227
+ include_examples 'should enumerate the matching data'
228
+ end
229
+
230
+ context 'when the collection data changes' do
231
+ let(:data) { BOOKS_FIXTURES[0...-1] }
232
+ let(:item) { BOOKS_FIXTURES.last }
233
+
234
+ before(:example) do
235
+ query.each {} # Cache query results.
236
+
237
+ add_item_to_collection(item)
238
+ end
239
+
240
+ include_examples 'should enumerate the matching data'
241
+ end
242
+ end
243
+ end
244
+
245
+ describe '#exists?' do
246
+ shared_examples 'should check the existence of matching data' do
247
+ it { expect(query.exists?).to be == !matching_data.empty? }
248
+ end
249
+
250
+ let(:data) { [] }
251
+ let(:matching_data) { data }
252
+
253
+ include_examples 'should define predicate', :exists?
254
+
255
+ include_examples 'should check the existence of matching data'
256
+
257
+ include_contract Cuprum::Collections::RSpec::QUERYING_CONTRACT,
258
+ block: lambda {
259
+ include_examples 'should check the existence of matching data'
260
+ },
261
+ operators: operators
262
+
263
+ wrap_context 'when the query has composed filters' do
264
+ include_examples 'should check the existence of matching data'
265
+ end
266
+
267
+ context 'when the collection has many items' do
268
+ let(:data) { BOOKS_FIXTURES }
269
+
270
+ include_examples 'should check the existence of matching data'
271
+
272
+ include_contract Cuprum::Collections::RSpec::QUERYING_CONTRACT,
273
+ block: lambda {
274
+ include_examples 'should check the existence of matching data'
275
+ },
276
+ operators: operators
277
+
278
+ wrap_context 'when the query has composed filters' do
279
+ include_examples 'should check the existence of matching data'
280
+ end
281
+ end
282
+ end
283
+
284
+ describe '#limit' do
285
+ it { expect(query).to respond_to(:limit).with(0..1).arguments }
286
+
287
+ describe 'with no arguments' do
288
+ it { expect(query.limit).to be nil }
289
+ end
290
+
291
+ describe 'with nil' do
292
+ let(:error_message) { 'limit must be a non-negative integer' }
293
+
294
+ it 'should raise an exception' do
295
+ expect { query.limit nil }
296
+ .to raise_error ArgumentError, error_message
297
+ end
298
+ end
299
+
300
+ describe 'with an object' do
301
+ let(:error_message) { 'limit must be a non-negative integer' }
302
+
303
+ it 'should raise an exception' do
304
+ expect { query.limit Object.new.freeze }
305
+ .to raise_error ArgumentError, error_message
306
+ end
307
+ end
308
+
309
+ describe 'with a negative integer' do
310
+ let(:error_message) { 'limit must be a non-negative integer' }
311
+
312
+ it 'should raise an exception' do
313
+ expect { query.limit(-1) }
314
+ .to raise_error ArgumentError, error_message
315
+ end
316
+ end
317
+
318
+ describe 'with zero' do
319
+ it { expect(query.limit 0).to be_a described_class }
320
+
321
+ it { expect(query.limit 0).not_to be query }
322
+
323
+ it { expect(query.limit(0).limit).to be 0 }
324
+ end
325
+
326
+ describe 'with a positive integer' do
327
+ it { expect(query.limit 3).to be_a described_class }
328
+
329
+ it { expect(query.limit 3).not_to be query }
330
+
331
+ it { expect(query.limit(3).limit).to be 3 }
332
+ end
333
+ end
334
+
335
+ describe '#offset' do
336
+ it { expect(query).to respond_to(:offset).with(0..1).argument }
337
+
338
+ describe 'with no arguments' do
339
+ it { expect(query.offset).to be nil }
340
+ end
341
+
342
+ describe 'with nil' do
343
+ let(:error_message) { 'offset must be a non-negative integer' }
344
+
345
+ it 'should raise an exception' do
346
+ expect { query.offset nil }
347
+ .to raise_error ArgumentError, error_message
348
+ end
349
+ end
350
+
351
+ describe 'with an object' do
352
+ let(:error_message) { 'offset must be a non-negative integer' }
353
+
354
+ it 'should raise an exception' do
355
+ expect { query.offset Object.new.freeze }
356
+ .to raise_error ArgumentError, error_message
357
+ end
358
+ end
359
+
360
+ describe 'with a negative integer' do
361
+ let(:error_message) { 'offset must be a non-negative integer' }
362
+
363
+ it 'should raise an exception' do
364
+ expect { query.offset(-1) }
365
+ .to raise_error ArgumentError, error_message
366
+ end
367
+ end
368
+
369
+ describe 'with zero' do
370
+ it { expect(query.offset 0).to be_a described_class }
371
+
372
+ it { expect(query.offset 0).not_to be query }
373
+
374
+ it { expect(query.offset(0).offset).to be 0 }
375
+ end
376
+
377
+ describe 'with a positive integer' do
378
+ it { expect(query.offset 3).to be_a described_class }
379
+
380
+ it { expect(query.offset 3).not_to be query }
381
+
382
+ it { expect(query.offset(3).offset).to be 3 }
383
+ end
384
+ end
385
+
386
+ describe '#order' do
387
+ let(:default_order) { defined?(super()) ? super() : {} }
388
+ let(:error_message) do
389
+ 'order must be a list of attribute names and/or a hash of attribute' \
390
+ ' names with values :asc or :desc'
391
+ end
392
+
393
+ it 'should define the method' do
394
+ expect(query)
395
+ .to respond_to(:order)
396
+ .with(0).arguments
397
+ .and_unlimited_arguments
398
+ end
399
+
400
+ it { expect(query).to alias_method(:order).as(:order_by) }
401
+
402
+ describe 'with no arguments' do
403
+ it { expect(query.order).to be == default_order }
404
+ end
405
+
406
+ describe 'with a hash with invalid keys' do
407
+ it 'should raise an exception' do
408
+ expect { query.order({ nil => :asc }) }
409
+ .to raise_error ArgumentError, error_message
410
+ end
411
+ end
412
+
413
+ describe 'with a hash with empty string keys' do
414
+ it 'should raise an exception' do
415
+ expect { query.order({ '' => :asc }) }
416
+ .to raise_error ArgumentError, error_message
417
+ end
418
+ end
419
+
420
+ describe 'with a hash with empty symbol keys' do
421
+ it 'should raise an exception' do
422
+ expect { query.order({ '': :asc }) }
423
+ .to raise_error ArgumentError, error_message
424
+ end
425
+ end
426
+
427
+ describe 'with a hash with nil value' do
428
+ it 'should raise an exception' do
429
+ expect { query.order({ title: nil }) }
430
+ .to raise_error ArgumentError, error_message
431
+ end
432
+ end
433
+
434
+ describe 'with a hash with object value' do
435
+ it 'should raise an exception' do
436
+ expect { query.order({ title: Object.new.freeze }) }
437
+ .to raise_error ArgumentError, error_message
438
+ end
439
+ end
440
+
441
+ describe 'with a hash with empty value' do
442
+ it 'should raise an exception' do
443
+ expect { query.order({ title: '' }) }
444
+ .to raise_error ArgumentError, error_message
445
+ end
446
+ end
447
+
448
+ describe 'with a hash with invalid value' do
449
+ it 'should raise an exception' do
450
+ expect { query.order({ title: 'wibbly' }) }
451
+ .to raise_error ArgumentError, error_message
452
+ end
453
+ end
454
+
455
+ describe 'with a valid ordering' do
456
+ let(:expected) do
457
+ { title: :asc }
458
+ end
459
+
460
+ it { expect(query.order :title).to be_a described_class }
461
+
462
+ it { expect(query.order :title).not_to be query }
463
+
464
+ it { expect(query.order(:title).order).to be == expected }
465
+ end
466
+ end
467
+
468
+ describe '#reset' do
469
+ let(:data) { [] }
470
+ let(:matching_data) { data }
471
+ let(:expected_data) do
472
+ defined?(super()) ? super() : matching_data
473
+ end
474
+
475
+ it { expect(query).to respond_to(:reset).with(0).arguments }
476
+
477
+ it { expect(query.reset).to be_a query.class }
478
+
479
+ it { expect(query.reset).not_to be query }
480
+
481
+ it { expect(query.reset.to_a).to be == query.to_a }
482
+
483
+ context 'when the collection data changes' do
484
+ let(:item) { BOOKS_FIXTURES.first }
485
+ let(:matching_data) { [item] }
486
+
487
+ before(:example) do
488
+ query.to_a # Cache query results.
489
+
490
+ add_item_to_collection(item)
491
+ end
492
+
493
+ it { expect(query.reset.count).to be expected_data.size }
494
+
495
+ it { expect(query.reset.to_a).to deep_match expected_data }
496
+ end
497
+
498
+ context 'when the collection has many items' do
499
+ let(:data) { BOOKS_FIXTURES }
500
+
501
+ it { expect(query.reset).to be_a query.class }
502
+
503
+ it { expect(query.reset).not_to be query }
504
+
505
+ it { expect(query.reset.to_a).to be == query.to_a }
506
+
507
+ context 'when the collection data changes' do
508
+ let(:data) { BOOKS_FIXTURES[0...-1] }
509
+ let(:item) { BOOKS_FIXTURES.last }
510
+ let(:matching_data) { [*data, item] }
511
+
512
+ before(:example) do
513
+ query.to_a # Cache query results.
514
+
515
+ add_item_to_collection(item)
516
+ end
517
+
518
+ it { expect(query.reset.count).to be expected_data.size }
519
+
520
+ it { expect(query.reset.to_a).to deep_match expected_data }
521
+ end
522
+ end
523
+ end
524
+
525
+ describe '#to_a' do
526
+ let(:data) { [] }
527
+ let(:matching_data) { data }
528
+ let(:expected_data) do
529
+ defined?(super()) ? super() : matching_data
530
+ end
531
+
532
+ it { expect(query).to respond_to(:to_a).with(0).arguments }
533
+
534
+ it { expect(query.to_a).to deep_match expected_data }
535
+
536
+ include_contract Cuprum::Collections::RSpec::QUERYING_CONTRACT,
537
+ block: lambda {
538
+ it { expect(scoped_query.to_a).to deep_match expected_data }
539
+ },
540
+ operators: operators
541
+
542
+ wrap_context 'when the query has composed filters' do
543
+ it { expect(scoped_query.to_a).to deep_match expected_data }
544
+ end
545
+
546
+ context 'when the collection data changes' do
547
+ let(:item) { BOOKS_FIXTURES.first }
548
+
549
+ before(:example) do
550
+ query.to_a # Cache query results.
551
+
552
+ add_item_to_collection(item)
553
+ end
554
+
555
+ it { expect(query.to_a).to deep_match expected_data }
556
+ end
557
+
558
+ context 'when the collection has many items' do
559
+ let(:data) { BOOKS_FIXTURES }
560
+
561
+ it { expect(query.to_a).to deep_match expected_data }
562
+
563
+ include_contract Cuprum::Collections::RSpec::QUERYING_CONTRACT,
564
+ block: lambda {
565
+ it { expect(scoped_query.to_a).to deep_match expected_data }
566
+ },
567
+ operators: operators
568
+
569
+ wrap_context 'when the query has composed filters' do
570
+ it { expect(scoped_query.to_a).to deep_match expected_data }
571
+ end
572
+
573
+ context 'when the collection data changes' do
574
+ let(:data) { BOOKS_FIXTURES[0...-1] }
575
+ let(:item) { BOOKS_FIXTURES.last }
576
+
577
+ before(:example) do
578
+ query.to_a # Cache query results.
579
+
580
+ add_item_to_collection(item)
581
+ end
582
+
583
+ it { expect(query.to_a).to deep_match expected_data }
584
+ end
585
+ end
586
+ end
587
+
588
+ describe '#where' do
589
+ let(:block) { -> { { title: 'The Caves of Steel' } } }
590
+
591
+ it 'should define the method' do
592
+ expect(query)
593
+ .to respond_to(:where)
594
+ .with(0..1).arguments
595
+ .and_keywords(:strategy)
596
+ .and_a_block
597
+ end
598
+
599
+ describe 'with no arguments' do
600
+ it { expect(query.where).to be_a described_class }
601
+
602
+ it { expect(query.where).not_to be query }
603
+ end
604
+
605
+ describe 'with a block' do
606
+ it { expect(query.where(&block)).to be_a described_class }
607
+
608
+ it { expect(query.where(&block)).not_to be query }
609
+ end
610
+
611
+ describe 'with a valid strategy' do
612
+ it 'should return a query instance' do
613
+ expect(query.where(strategy: :block, &block)).to be_a described_class
614
+ end
615
+
616
+ it { expect(query.where(strategy: :block, &block)).not_to be query }
617
+ end
618
+
619
+ describe 'with parameters that do not match a strategy' do
620
+ let(:error_class) { Cuprum::Collections::QueryBuilder::ParseError }
621
+ let(:error_message) { 'unable to parse query with strategy nil' }
622
+
623
+ it 'should raise an exception' do
624
+ expect { query.where(%w[ichi ni san]) }
625
+ .to raise_error error_class, error_message
626
+ end
627
+ end
628
+
629
+ describe 'with an invalid strategy' do
630
+ let(:error_class) { Cuprum::Collections::QueryBuilder::ParseError }
631
+ let(:error_message) { 'unable to parse query with strategy :random' }
632
+
633
+ it 'should raise an exception' do
634
+ expect { query.where(strategy: :random) }
635
+ .to raise_error error_class, error_message
636
+ end
637
+ end
638
+
639
+ describe 'with invalid parameters for a strategy' do
640
+ let(:error_class) { Cuprum::Collections::QueryBuilder::ParseError }
641
+ let(:error_message) { 'unable to parse query with strategy :block' }
642
+
643
+ it 'should raise an exception' do
644
+ expect { query.where(strategy: :block) }
645
+ .to raise_error error_class, error_message
646
+ end
647
+ end
648
+ end
649
+ end
650
+ end