cuprum-collections 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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