cuprum-collections 0.3.0 → 0.4.0.rc.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +48 -0
  3. data/DEVELOPMENT.md +2 -2
  4. data/README.md +13 -11
  5. data/lib/cuprum/collections/association.rb +256 -0
  6. data/lib/cuprum/collections/associations/belongs_to.rb +32 -0
  7. data/lib/cuprum/collections/associations/has_many.rb +23 -0
  8. data/lib/cuprum/collections/associations/has_one.rb +23 -0
  9. data/lib/cuprum/collections/associations.rb +10 -0
  10. data/lib/cuprum/collections/basic/collection.rb +39 -74
  11. data/lib/cuprum/collections/basic/commands/find_many.rb +1 -1
  12. data/lib/cuprum/collections/basic/commands/find_matching.rb +1 -1
  13. data/lib/cuprum/collections/basic/repository.rb +9 -33
  14. data/lib/cuprum/collections/basic.rb +1 -0
  15. data/lib/cuprum/collections/collection.rb +154 -0
  16. data/lib/cuprum/collections/commands/associations/find_many.rb +161 -0
  17. data/lib/cuprum/collections/commands/associations/require_many.rb +48 -0
  18. data/lib/cuprum/collections/commands/associations.rb +13 -0
  19. data/lib/cuprum/collections/commands/find_one_matching.rb +1 -1
  20. data/lib/cuprum/collections/commands.rb +1 -0
  21. data/lib/cuprum/collections/errors/abstract_find_error.rb +1 -1
  22. data/lib/cuprum/collections/relation.rb +401 -0
  23. data/lib/cuprum/collections/repository.rb +71 -4
  24. data/lib/cuprum/collections/resource.rb +65 -0
  25. data/lib/cuprum/collections/rspec/contracts/association_contracts.rb +2137 -0
  26. data/lib/cuprum/collections/rspec/contracts/basic/command_contracts.rb +484 -0
  27. data/lib/cuprum/collections/rspec/contracts/basic.rb +11 -0
  28. data/lib/cuprum/collections/rspec/contracts/collection_contracts.rb +429 -0
  29. data/lib/cuprum/collections/rspec/contracts/command_contracts.rb +1462 -0
  30. data/lib/cuprum/collections/rspec/contracts/query_contracts.rb +1093 -0
  31. data/lib/cuprum/collections/rspec/contracts/relation_contracts.rb +1381 -0
  32. data/lib/cuprum/collections/rspec/contracts/repository_contracts.rb +605 -0
  33. data/lib/cuprum/collections/rspec/contracts.rb +23 -0
  34. data/lib/cuprum/collections/rspec/fixtures.rb +85 -82
  35. data/lib/cuprum/collections/rspec.rb +4 -1
  36. data/lib/cuprum/collections/version.rb +3 -3
  37. data/lib/cuprum/collections.rb +9 -4
  38. metadata +25 -21
  39. data/lib/cuprum/collections/base.rb +0 -11
  40. data/lib/cuprum/collections/basic/rspec/command_contract.rb +0 -392
  41. data/lib/cuprum/collections/rspec/assign_one_command_contract.rb +0 -168
  42. data/lib/cuprum/collections/rspec/build_one_command_contract.rb +0 -93
  43. data/lib/cuprum/collections/rspec/collection_contract.rb +0 -190
  44. data/lib/cuprum/collections/rspec/destroy_one_command_contract.rb +0 -108
  45. data/lib/cuprum/collections/rspec/find_many_command_contract.rb +0 -407
  46. data/lib/cuprum/collections/rspec/find_matching_command_contract.rb +0 -194
  47. data/lib/cuprum/collections/rspec/find_one_command_contract.rb +0 -157
  48. data/lib/cuprum/collections/rspec/insert_one_command_contract.rb +0 -84
  49. data/lib/cuprum/collections/rspec/query_builder_contract.rb +0 -92
  50. data/lib/cuprum/collections/rspec/query_contract.rb +0 -650
  51. data/lib/cuprum/collections/rspec/querying_contract.rb +0 -298
  52. data/lib/cuprum/collections/rspec/repository_contract.rb +0 -235
  53. data/lib/cuprum/collections/rspec/update_one_command_contract.rb +0 -80
  54. data/lib/cuprum/collections/rspec/validate_one_command_contract.rb +0 -96
@@ -1,650 +0,0 @@
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 have_aliased_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