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
@@ -0,0 +1,2137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections/rspec/contracts'
4
+ require 'cuprum/collections/rspec/contracts/relation_contracts'
5
+
6
+ module Cuprum::Collections::RSpec::Contracts
7
+ # Contracts for asserting on Association objects.
8
+ module AssociationContracts
9
+ # Contract validating the behavior of an Association.
10
+ module ShouldBeAnAssociationContract
11
+ extend RSpec::SleepingKingStudios::Contract
12
+
13
+ # @!method apply(example_group)
14
+ # Adds the contract to the example group.
15
+ #
16
+ # @param example_group [RSpec::Core::ExampleGroup] the example group to
17
+ # which the contract is applied.
18
+ contract do
19
+ include Cuprum::Collections::RSpec::Contracts::RelationContracts
20
+
21
+ example_class 'Author'
22
+ example_class 'Chapter'
23
+ example_class 'Writer'
24
+
25
+ include_contract 'should be a relation'
26
+
27
+ include_contract 'should disambiguate parameter',
28
+ :entity_class,
29
+ as: %i[association_class resource_class],
30
+ value: Grimoire
31
+
32
+ include_contract 'should disambiguate parameter',
33
+ :name,
34
+ as: %i[association_name resource_name]
35
+
36
+ include_contract 'should disambiguate parameter',
37
+ :singular_name,
38
+ as: :singular_resource_name
39
+
40
+ include_contract 'should define primary keys'
41
+
42
+ describe '#build_entities_query' do
43
+ it 'should define the method' do
44
+ expect(association)
45
+ .to respond_to(:build_entities_query)
46
+ .with_unlimited_arguments
47
+ .and_keywords(:allow_nil, :deduplicate)
48
+ end
49
+ end
50
+
51
+ describe '#build_keys_query' do
52
+ it 'should define the method' do
53
+ expect(association)
54
+ .to respond_to(:build_keys_query)
55
+ .with_unlimited_arguments
56
+ .and_keywords(:allow_nil, :deduplicate)
57
+ end
58
+ end
59
+
60
+ describe '#foreign_key_name' do
61
+ include_examples 'should define reader', :foreign_key_name
62
+ end
63
+
64
+ describe '#inverse' do
65
+ include_examples 'should define reader', :inverse, nil
66
+
67
+ context 'when initialized with inverse: value' do
68
+ let(:inverse) { described_class.new(name: 'authors') }
69
+ let(:constructor_options) do
70
+ super().merge(inverse: inverse)
71
+ end
72
+
73
+ it { expect(subject.inverse).to be == inverse }
74
+ end
75
+
76
+ context 'with a copy with assigned inverse' do
77
+ subject { super().with_inverse(new_inverse) }
78
+
79
+ let(:new_inverse) { described_class.new(name: 'chapters') }
80
+
81
+ it { expect(subject.inverse).to be == new_inverse }
82
+
83
+ context 'when initialized with inverse: value' do
84
+ let(:inverse) { described_class.new(name: 'authors') }
85
+ let(:constructor_options) do
86
+ super().merge(inverse: inverse)
87
+ end
88
+
89
+ it { expect(subject.inverse).to be == new_inverse }
90
+ end
91
+ end
92
+ end
93
+
94
+ describe '#inverse_class' do
95
+ include_examples 'should define reader', :inverse_class, nil
96
+
97
+ context 'when initialized with inverse: value' do
98
+ let(:inverse) { described_class.new(name: 'authors') }
99
+ let(:constructor_options) do
100
+ super().merge(inverse: inverse)
101
+ end
102
+
103
+ it { expect(subject.inverse_class).to be == Author }
104
+
105
+ context 'when initialized with inverse_class: a Class' do
106
+ let(:constructor_options) do
107
+ super().merge(inverse_class: Writer)
108
+ end
109
+
110
+ it { expect(subject.inverse_class).to be == Writer }
111
+ end
112
+
113
+ context 'when initialized with inverse_class: a String' do
114
+ let(:constructor_options) do
115
+ super().merge(inverse_class: 'Writer')
116
+ end
117
+
118
+ it { expect(subject.inverse_class).to be == Writer }
119
+ end
120
+ end
121
+
122
+ context 'when initialized with inverse_class: a Class' do
123
+ let(:constructor_options) do
124
+ super().merge(inverse_class: Writer)
125
+ end
126
+
127
+ it { expect(subject.inverse_class).to be == Writer }
128
+ end
129
+
130
+ context 'when initialized with inverse_class: a String' do
131
+ let(:constructor_options) do
132
+ super().merge(inverse_class: 'Writer')
133
+ end
134
+
135
+ it { expect(subject.inverse_class).to be == Writer }
136
+ end
137
+
138
+ context 'with a copy with assigned inverse' do
139
+ subject do
140
+ super().tap(&:inverse_class).with_inverse(new_inverse)
141
+ end
142
+
143
+ let(:new_inverse) { described_class.new(name: 'chapters') }
144
+
145
+ it { expect(subject.inverse_class).to be == Chapter }
146
+
147
+ context 'when initialized with inverse_class: a Class' do
148
+ let(:constructor_options) do
149
+ super().merge(inverse_class: Writer)
150
+ end
151
+
152
+ it { expect(subject.inverse_class).to be == Writer }
153
+ end
154
+
155
+ context 'when initialized with inverse_class: a String' do
156
+ let(:constructor_options) do
157
+ super().merge(inverse_class: 'Writer')
158
+ end
159
+
160
+ it { expect(subject.inverse_class).to be == Writer }
161
+ end
162
+
163
+ context 'when initialized with inverse: value' do
164
+ let(:inverse) { described_class.new(name: 'authors') }
165
+ let(:constructor_options) do
166
+ super().merge(inverse: inverse)
167
+ end
168
+
169
+ it { expect(subject.inverse_class).to be == Chapter }
170
+ end
171
+ end
172
+ end
173
+
174
+ describe '#inverse_key_name' do
175
+ include_examples 'should define reader', :inverse_key_name
176
+ end
177
+
178
+ describe '#inverse_name' do
179
+ include_examples 'should define reader', :inverse_name, nil
180
+
181
+ context 'when initialized with inverse: value' do
182
+ let(:inverse) { described_class.new(name: 'authors') }
183
+ let(:constructor_options) do
184
+ super().merge(inverse: inverse)
185
+ end
186
+
187
+ it { expect(subject.inverse_name).to be == 'authors' }
188
+
189
+ context 'when initialized with inverse_name: a String' do
190
+ let(:constructor_options) do
191
+ super().merge(inverse_name: 'writers')
192
+ end
193
+
194
+ it { expect(subject.inverse_name).to be == 'writers' }
195
+ end
196
+
197
+ context 'when initialized with inverse_name: a Symbol' do
198
+ let(:constructor_options) do
199
+ super().merge(inverse_name: :writers)
200
+ end
201
+
202
+ it { expect(subject.inverse_name).to be == 'writers' }
203
+ end
204
+ end
205
+
206
+ context 'when initialized with inverse_name: a String' do
207
+ let(:constructor_options) do
208
+ super().merge(inverse_name: 'writers')
209
+ end
210
+
211
+ it { expect(subject.inverse_name).to be == 'writers' }
212
+ end
213
+
214
+ context 'when initialized with inverse_name: a Symbol' do
215
+ let(:constructor_options) do
216
+ super().merge(inverse_name: :writers)
217
+ end
218
+
219
+ it { expect(subject.inverse_name).to be == 'writers' }
220
+ end
221
+
222
+ context 'with a copy with assigned inverse' do
223
+ subject do
224
+ super().tap(&:inverse_name).with_inverse(new_inverse)
225
+ end
226
+
227
+ let(:new_inverse) { described_class.new(name: 'chapters') }
228
+
229
+ it { expect(subject.inverse_name).to be == 'chapters' }
230
+
231
+ context 'when initialized with inverse: value' do
232
+ let(:inverse) { described_class.new(name: 'authors') }
233
+ let(:constructor_options) do
234
+ super().merge(inverse: inverse)
235
+ end
236
+
237
+ it { expect(subject.inverse_name).to be == 'chapters' }
238
+ end
239
+
240
+ context 'when initialized with inverse_name: a String' do
241
+ let(:constructor_options) do
242
+ super().merge(inverse_name: 'writers')
243
+ end
244
+
245
+ it { expect(subject.inverse_name).to be == 'writers' }
246
+ end
247
+
248
+ context 'when initialized with inverse_name: a Symbol' do
249
+ let(:constructor_options) do
250
+ super().merge(inverse_name: :writers)
251
+ end
252
+
253
+ it { expect(subject.inverse_name).to be == 'writers' }
254
+ end
255
+ end
256
+ end
257
+
258
+ describe '#map_entities_to_keys' do
259
+ it 'should define the method' do
260
+ expect(subject)
261
+ .to respond_to(:map_entities_to_keys)
262
+ .with_unlimited_arguments
263
+ .and_keywords(:allow_nil, :deduplicate, :strict)
264
+ end
265
+ end
266
+
267
+ describe '#options' do
268
+ context 'when initialized with inverse: value' do
269
+ let(:inverse) { described_class.new(name: 'authors') }
270
+ let(:constructor_options) do
271
+ super().merge(inverse: inverse)
272
+ end
273
+
274
+ it { expect(subject.options).to be == {} }
275
+ end
276
+ end
277
+
278
+ describe '#plural?' do
279
+ include_examples 'should define predicate', :plural?
280
+ end
281
+
282
+ describe '#primary_key_query?' do
283
+ include_examples 'should define predicate', :primary_key_query?
284
+ end
285
+
286
+ describe '#query_key_name' do
287
+ include_examples 'should define reader', :query_key_name
288
+ end
289
+
290
+ describe '#singular_inverse_name' do
291
+ include_examples 'should define reader', :singular_inverse_name, nil
292
+
293
+ context 'when initialized with inverse: value' do
294
+ let(:inverse) { described_class.new(name: 'authors') }
295
+ let(:constructor_options) do
296
+ super().merge(inverse: inverse)
297
+ end
298
+
299
+ it { expect(subject.singular_inverse_name).to be == 'author' }
300
+
301
+ context 'when initialized with singular_inverse_name: a String' do
302
+ let(:singular_inverse_name) { 'writer' }
303
+ let(:constructor_options) do
304
+ super().merge(singular_inverse_name: singular_inverse_name)
305
+ end
306
+
307
+ it { expect(subject.singular_inverse_name).to be == 'writer' }
308
+ end
309
+
310
+ context 'when initialized with singular_inverse_name: a Symbol' do
311
+ let(:singular_inverse_name) { :writer }
312
+ let(:constructor_options) do
313
+ super().merge(singular_inverse_name: singular_inverse_name)
314
+ end
315
+
316
+ it { expect(subject.singular_inverse_name).to be == 'writer' }
317
+ end
318
+ end
319
+
320
+ context 'when initialized with inverse_name: value' do
321
+ let(:inverse_name) { 'authors' }
322
+ let(:constructor_options) do
323
+ super().merge(inverse_name: inverse_name)
324
+ end
325
+
326
+ it { expect(subject.singular_inverse_name).to be == 'author' }
327
+
328
+ context 'when initialized with singular_inverse_name: a String' do
329
+ let(:singular_inverse_name) { 'writer' }
330
+ let(:constructor_options) do
331
+ super().merge(singular_inverse_name: singular_inverse_name)
332
+ end
333
+
334
+ it { expect(subject.singular_inverse_name).to be == 'writer' }
335
+ end
336
+
337
+ context 'when initialized with singular_inverse_name: a Symbol' do
338
+ let(:singular_inverse_name) { :writer }
339
+ let(:constructor_options) do
340
+ super().merge(singular_inverse_name: singular_inverse_name)
341
+ end
342
+
343
+ it { expect(subject.singular_inverse_name).to be == 'writer' }
344
+ end
345
+ end
346
+
347
+ context 'when initialized with singular_inverse_name: a String' do
348
+ let(:singular_inverse_name) { 'writer' }
349
+ let(:constructor_options) do
350
+ super().merge(singular_inverse_name: singular_inverse_name)
351
+ end
352
+
353
+ it { expect(subject.singular_inverse_name).to be == 'writer' }
354
+ end
355
+
356
+ context 'when initialized with singular_inverse_name: a Symbol' do
357
+ let(:singular_inverse_name) { :writer }
358
+ let(:constructor_options) do
359
+ super().merge(singular_inverse_name: singular_inverse_name)
360
+ end
361
+
362
+ it { expect(subject.singular_inverse_name).to be == 'writer' }
363
+ end
364
+
365
+ context 'with a copy with assigned inverse' do
366
+ subject do
367
+ super().tap(&:singular_inverse_name).with_inverse(new_inverse)
368
+ end
369
+
370
+ let(:new_inverse) { described_class.new(name: 'chapters') }
371
+
372
+ it { expect(subject.singular_inverse_name).to be == 'chapter' }
373
+
374
+ context 'when initialized with inverse: value' do
375
+ let(:inverse) { described_class.new(name: 'authors') }
376
+ let(:constructor_options) do
377
+ super().merge(inverse: inverse)
378
+ end
379
+
380
+ it { expect(subject.singular_inverse_name).to be == 'chapter' }
381
+ end
382
+
383
+ context 'when initialized with inverse_name: value' do
384
+ let(:inverse_name) { 'authors' }
385
+ let(:constructor_options) do
386
+ super().merge(inverse_name: inverse_name)
387
+ end
388
+
389
+ it { expect(subject.singular_inverse_name).to be == 'chapter' }
390
+
391
+ context 'when initialized with singular_inverse_name: a String' do
392
+ let(:singular_inverse_name) { 'writer' }
393
+ let(:constructor_options) do
394
+ super().merge(singular_inverse_name: singular_inverse_name)
395
+ end
396
+
397
+ it { expect(subject.singular_inverse_name).to be == 'writer' }
398
+ end
399
+
400
+ context 'when initialized with singular_inverse_name: a Symbol' do
401
+ let(:singular_inverse_name) { :writer }
402
+ let(:constructor_options) do
403
+ super().merge(singular_inverse_name: singular_inverse_name)
404
+ end
405
+
406
+ it { expect(subject.singular_inverse_name).to be == 'writer' }
407
+ end
408
+ end
409
+
410
+ context 'when initialized with singular_inverse_name: a String' do
411
+ let(:singular_inverse_name) { 'writer' }
412
+ let(:constructor_options) do
413
+ super().merge(singular_inverse_name: singular_inverse_name)
414
+ end
415
+
416
+ it { expect(subject.singular_inverse_name).to be == 'writer' }
417
+ end
418
+
419
+ context 'when initialized with singular_inverse_name: a Symbol' do
420
+ let(:singular_inverse_name) { :writer }
421
+ let(:constructor_options) do
422
+ super().merge(singular_inverse_name: singular_inverse_name)
423
+ end
424
+
425
+ it { expect(subject.singular_inverse_name).to be == 'writer' }
426
+ end
427
+ end
428
+ end
429
+
430
+ describe '#singular?' do
431
+ include_examples 'should define predicate', :singular?
432
+ end
433
+
434
+ describe '#with_inverse' do
435
+ it 'should define the method' do
436
+ expect(association).to respond_to(:with_inverse).with(1).argument
437
+ end
438
+
439
+ context 'with a copy with assigned inverse' do
440
+ let(:new_inverse) { described_class.new(name: 'chapters') }
441
+ let(:copy) { subject.with_inverse(new_inverse) }
442
+
443
+ it { expect(copy).to be_a described_class }
444
+
445
+ it { expect(copy.inverse).to be == new_inverse }
446
+
447
+ it { expect(subject.inverse).to be nil }
448
+ end
449
+ end
450
+ end
451
+ end
452
+
453
+ # Contract validating the behavior of a BelongsToAssociation.
454
+ module ShouldBeABelongsToAssociationContract
455
+ extend RSpec::SleepingKingStudios::Contract
456
+
457
+ # @!method apply(example_group)
458
+ # Adds the contract to the example group.
459
+ #
460
+ # @param example_group [RSpec::Core::ExampleGroup] the example group to
461
+ # which the contract is applied.
462
+ contract do
463
+ include Cuprum::Collections::RSpec::Contracts::RelationContracts
464
+
465
+ include_contract 'should be an association'
466
+
467
+ describe '#build_entities_query' do
468
+ let(:key) { subject.foreign_key_name }
469
+ let(:entities) { [] }
470
+ let(:options) { {} }
471
+ let(:query) do
472
+ association.build_entities_query(*entities, **options)
473
+ end
474
+ let(:evaluated) do
475
+ Spec::QueryBuilder.new.instance_exec(&query)
476
+ end
477
+
478
+ example_class 'Spec::Entity' do |klass|
479
+ klass.define_method(:initialize) do |**attributes|
480
+ attributes.each do |key, value|
481
+ instance_variable_set(:"@#{key}", value)
482
+ end
483
+ end
484
+
485
+ klass.attr_reader :book_id
486
+ end
487
+
488
+ example_class 'Spec::QueryBuilder' do |klass|
489
+ klass.define_method(:one_of) { |values| { 'one_of' => values } }
490
+ end
491
+
492
+ describe 'with no entities' do
493
+ let(:entities) { [] }
494
+
495
+ it { expect(query).to be_a Proc }
496
+
497
+ it { expect(evaluated).to be == {} }
498
+ end
499
+
500
+ describe 'with one nil entity' do
501
+ let(:entities) { [nil] }
502
+
503
+ it { expect(evaluated).to be == {} }
504
+ end
505
+
506
+ describe 'with one invalid entity' do
507
+ let(:entities) { [Object.new.freeze] }
508
+ let(:error_message) do
509
+ "undefined method :[] or :#{key} for #{entities.first.inspect}"
510
+ end
511
+
512
+ it 'should raise an exception' do
513
+ expect { association.build_entities_query(*entities) }
514
+ .to raise_error ArgumentError, error_message
515
+ end
516
+ end
517
+
518
+ describe 'with one entity that responds to #[] and key: nil' do
519
+ let(:entities) { [{ key => nil }] }
520
+
521
+ it { expect(evaluated).to be == {} }
522
+
523
+ describe 'with allow_nil: true' do
524
+ let(:options) { super().merge(allow_nil: true) }
525
+
526
+ it { expect(evaluated).to be == { 'id' => nil } }
527
+ end
528
+ end
529
+
530
+ describe 'with one entity that responds to #[] and key: value' do
531
+ let(:entities) { [{ key => 0 }] }
532
+
533
+ it { expect(evaluated).to be == { 'id' => 0 } }
534
+ end
535
+
536
+ describe 'with one entity that responds to #id and key: nil' do
537
+ let(:entities) { [Spec::Entity.new(key => nil)] }
538
+
539
+ it { expect(evaluated).to be == {} }
540
+
541
+ describe 'with allow_nil: true' do
542
+ let(:options) { super().merge(allow_nil: true) }
543
+
544
+ it { expect(evaluated).to be == { 'id' => nil } }
545
+ end
546
+ end
547
+
548
+ describe 'with one entity that responds to #id and key: value' do
549
+ let(:entities) { [Spec::Entity.new(key => 0)] }
550
+
551
+ it { expect(evaluated).to be == { 'id' => 0 } }
552
+ end
553
+
554
+ describe 'with multiple entities' do
555
+ let(:entities) do
556
+ [
557
+ Spec::Entity.new(key => 0),
558
+ Spec::Entity.new(key => 1),
559
+ Spec::Entity.new(key => 2)
560
+ ]
561
+ end
562
+ let(:expected) do
563
+ { 'id' => { 'one_of' => [0, 1, 2] } }
564
+ end
565
+
566
+ it { expect(evaluated).to be == expected }
567
+ end
568
+
569
+ describe 'with multiple entities including nil' do
570
+ let(:entities) do
571
+ [
572
+ Spec::Entity.new(key => 0),
573
+ nil,
574
+ Spec::Entity.new(key => 1),
575
+ nil,
576
+ Spec::Entity.new(key => 2)
577
+ ]
578
+ end
579
+ let(:expected) do
580
+ { 'id' => { 'one_of' => [0, 1, 2] } }
581
+ end
582
+
583
+ it { expect(evaluated).to be == expected }
584
+ end
585
+
586
+ describe 'with multiple entities including nil ids' do
587
+ let(:entities) do
588
+ [
589
+ Spec::Entity.new(key => 0),
590
+ Spec::Entity.new(key => nil),
591
+ Spec::Entity.new(key => 1),
592
+ Spec::Entity.new(key => nil),
593
+ Spec::Entity.new(key => 2)
594
+ ]
595
+ end
596
+ let(:expected) do
597
+ { 'id' => { 'one_of' => [0, 1, 2] } }
598
+ end
599
+
600
+ it { expect(evaluated).to be == expected }
601
+
602
+ describe 'with allow_nil: true' do
603
+ let(:options) { super().merge(allow_nil: true) }
604
+ let(:expected) do
605
+ { 'id' => { 'one_of' => [0, nil, 1, 2] } }
606
+ end
607
+
608
+ it { expect(evaluated).to be == expected }
609
+ end
610
+ end
611
+
612
+ describe 'with multiple entities including duplicate ids' do
613
+ let(:entities) do
614
+ [
615
+ Spec::Entity.new(key => 0),
616
+ Spec::Entity.new(key => 1),
617
+ Spec::Entity.new(key => 0),
618
+ Spec::Entity.new(key => 1),
619
+ Spec::Entity.new(key => 2)
620
+ ]
621
+ end
622
+ let(:expected) do
623
+ { 'id' => { 'one_of' => [0, 1, 2] } }
624
+ end
625
+
626
+ it { expect(evaluated).to be == expected }
627
+
628
+ describe 'with deduplicate: false' do
629
+ let(:options) { super().merge(deduplicate: false) }
630
+ let(:expected) do
631
+ { 'id' => { 'one_of' => [0, 1, 0, 1, 2] } }
632
+ end
633
+
634
+ it { expect(evaluated).to be == expected }
635
+ end
636
+ end
637
+ end
638
+
639
+ describe '#build_keys_query' do
640
+ let(:keys) { [] }
641
+ let(:options) { {} }
642
+ let(:query) do
643
+ association.build_keys_query(*keys, **options)
644
+ end
645
+ let(:evaluated) do
646
+ Spec::QueryBuilder.new.instance_exec(&query)
647
+ end
648
+
649
+ example_class 'Spec::QueryBuilder' do |klass|
650
+ klass.define_method(:one_of) { |values| { 'one_of' => values } }
651
+ end
652
+
653
+ describe 'with no keys' do
654
+ let(:keys) { [] }
655
+
656
+ it { expect(query).to be_a Proc }
657
+
658
+ it { expect(evaluated).to be == {} }
659
+ end
660
+
661
+ describe 'with one nil key' do
662
+ let(:keys) { [nil] }
663
+
664
+ it { expect(evaluated).to be == {} }
665
+
666
+ describe 'with allow_nil: true' do
667
+ let(:options) { { allow_nil: true } }
668
+
669
+ it { expect(evaluated).to be == { 'id' => nil } }
670
+ end
671
+ end
672
+
673
+ describe 'with one non-nil key' do
674
+ let(:keys) { [0] }
675
+
676
+ it { expect(evaluated).to be == { 'id' => 0 } }
677
+ end
678
+
679
+ describe 'with many keys' do
680
+ let(:keys) { [0, 1, 2] }
681
+ let(:expected) { { 'id' => { 'one_of' => keys } } }
682
+
683
+ it { expect(evaluated).to be == expected }
684
+ end
685
+
686
+ describe 'with many keys including nil' do
687
+ let(:keys) { [0, nil, 2] }
688
+ let(:expected) { { 'id' => { 'one_of' => [0, 2] } } }
689
+
690
+ it { expect(evaluated).to be == expected }
691
+
692
+ describe 'with allow_nil: true' do
693
+ let(:options) { { allow_nil: true } }
694
+ let(:expected) do
695
+ { 'id' => { 'one_of' => [0, nil, 2] } }
696
+ end
697
+
698
+ it { expect(evaluated).to be == expected }
699
+ end
700
+ end
701
+
702
+ describe 'with many non-unique keys' do
703
+ let(:keys) { [0, 1, 2, 1, 2] }
704
+ let(:expected) { { 'id' => { 'one_of' => keys.uniq } } }
705
+
706
+ it { expect(evaluated).to be == expected }
707
+
708
+ describe 'with deduplicate: false' do
709
+ let(:options) { super().merge(deduplicate: false) }
710
+ let(:expected) do
711
+ { 'id' => { 'one_of' => [0, 1, 2, 1, 2] } }
712
+ end
713
+
714
+ it { expect(evaluated).to be == expected }
715
+ end
716
+ end
717
+ end
718
+
719
+ describe '#foreign_key_name' do
720
+ let(:expected) { "#{tools.str.singularize(name)}_id" }
721
+
722
+ def tools
723
+ SleepingKingStudios::Tools::Toolbelt.instance
724
+ end
725
+
726
+ it { expect(subject.foreign_key_name).to be == expected }
727
+
728
+ context 'when initialized with foreign_key_name: a String' do
729
+ let(:foreign_key_name) { 'writer_id' }
730
+ let(:constructor_options) do
731
+ super().merge(foreign_key_name: foreign_key_name)
732
+ end
733
+
734
+ it { expect(subject.foreign_key_name).to be == 'writer_id' }
735
+ end
736
+
737
+ context 'when initialized with foreign_key_name: a String' do
738
+ let(:foreign_key_name) { :writer_id }
739
+ let(:constructor_options) do
740
+ super().merge(foreign_key_name: foreign_key_name)
741
+ end
742
+
743
+ it { expect(subject.foreign_key_name).to be == 'writer_id' }
744
+ end
745
+
746
+ context 'when initialized with singular_name: value' do
747
+ let(:singular_name) { 'author' }
748
+ let(:constructor_options) do
749
+ super().merge(singular_name: singular_name)
750
+ end
751
+
752
+ it { expect(subject.foreign_key_name).to be == 'author_id' }
753
+
754
+ context 'when initialized with foreign_key_name: a String' do
755
+ let(:foreign_key_name) { 'writer_id' }
756
+ let(:constructor_options) do
757
+ super().merge(foreign_key_name: foreign_key_name)
758
+ end
759
+
760
+ it { expect(subject.foreign_key_name).to be == 'writer_id' }
761
+ end
762
+
763
+ context 'when initialized with foreign_key_name: a String' do
764
+ let(:foreign_key_name) { :writer_id }
765
+ let(:constructor_options) do
766
+ super().merge(foreign_key_name: foreign_key_name)
767
+ end
768
+
769
+ it { expect(subject.foreign_key_name).to be == 'writer_id' }
770
+ end
771
+ end
772
+ end
773
+
774
+ describe '#inverse_key_name' do
775
+ let(:expected) { "#{tools.str.singularize(name)}_id" }
776
+
777
+ def tools
778
+ SleepingKingStudios::Tools::Toolbelt.instance
779
+ end
780
+
781
+ it { expect(subject.inverse_key_name).to be == expected }
782
+
783
+ context 'when initialized with foreign_key_name: a String' do
784
+ let(:foreign_key_name) { 'writer_id' }
785
+ let(:constructor_options) do
786
+ super().merge(foreign_key_name: foreign_key_name)
787
+ end
788
+
789
+ it { expect(subject.inverse_key_name).to be == 'writer_id' }
790
+ end
791
+
792
+ context 'when initialized with foreign_key_name: a String' do
793
+ let(:foreign_key_name) { :writer_id }
794
+ let(:constructor_options) do
795
+ super().merge(foreign_key_name: foreign_key_name)
796
+ end
797
+
798
+ it { expect(subject.inverse_key_name).to be == 'writer_id' }
799
+ end
800
+
801
+ context 'when initialized with singular_name: value' do
802
+ let(:singular_name) { 'author' }
803
+ let(:constructor_options) do
804
+ super().merge(singular_name: singular_name)
805
+ end
806
+
807
+ it { expect(subject.inverse_key_name).to be == 'author_id' }
808
+
809
+ context 'when initialized with foreign_key_name: a String' do
810
+ let(:foreign_key_name) { 'writer_id' }
811
+ let(:constructor_options) do
812
+ super().merge(foreign_key_name: foreign_key_name)
813
+ end
814
+
815
+ it { expect(subject.inverse_key_name).to be == 'writer_id' }
816
+ end
817
+
818
+ context 'when initialized with foreign_key_name: a String' do
819
+ let(:foreign_key_name) { :writer_id }
820
+ let(:constructor_options) do
821
+ super().merge(foreign_key_name: foreign_key_name)
822
+ end
823
+
824
+ it { expect(subject.inverse_key_name).to be == 'writer_id' }
825
+ end
826
+ end
827
+ end
828
+
829
+ describe '#map_entities_to_keys' do
830
+ let(:key) { subject.foreign_key_name }
831
+ let(:entities) { [] }
832
+ let(:options) { {} }
833
+ let(:keys) do
834
+ association.map_entities_to_keys(*entities, **options)
835
+ end
836
+
837
+ example_class 'Spec::Entity' do |klass|
838
+ klass.define_method(:initialize) do |**attributes|
839
+ attributes.each do |key, value|
840
+ instance_variable_set(:"@#{key}", value)
841
+ end
842
+ end
843
+
844
+ klass.attr_reader :book_id
845
+ end
846
+
847
+ describe 'with no entities' do
848
+ let(:entities) { [] }
849
+
850
+ it { expect(keys).to be == [] }
851
+ end
852
+
853
+ describe 'with one nil entity' do
854
+ let(:entities) { [nil] }
855
+
856
+ it { expect(keys).to be == [] }
857
+ end
858
+
859
+ describe 'with one invalid entity' do
860
+ let(:entities) { [Object.new.freeze] }
861
+ let(:error_message) do
862
+ "undefined method :[] or :#{key} for #{entities.first.inspect}"
863
+ end
864
+
865
+ it 'should raise an exception' do
866
+ expect { association.map_entities_to_keys(*entities) }
867
+ .to raise_error ArgumentError, error_message
868
+ end
869
+
870
+ describe 'with strict: false' do
871
+ it 'should raise an exception' do
872
+ expect do
873
+ association.map_entities_to_keys(*entities, strict: false)
874
+ end
875
+ .to raise_error ArgumentError, error_message
876
+ end
877
+ end
878
+ end
879
+
880
+ describe 'with one Integer' do
881
+ let(:entities) { [0] }
882
+ let(:error_message) do
883
+ "undefined method :[] or :#{key} for #{entities.first.inspect}"
884
+ end
885
+
886
+ it 'should raise an exception' do
887
+ expect { association.map_entities_to_keys(*entities) }
888
+ .to raise_error ArgumentError, error_message
889
+ end
890
+
891
+ describe 'with strict: false' do
892
+ it 'should raise an exception' do
893
+ expect(
894
+ association.map_entities_to_keys(*entities, strict: false)
895
+ )
896
+ .to be == entities
897
+ end
898
+ end
899
+
900
+ context 'when initialized with primary_key_type: String' do
901
+ let(:constructor_options) do
902
+ super().merge(primary_key_type: String)
903
+ end
904
+
905
+ describe 'with strict: false' do
906
+ it 'should raise an exception' do
907
+ expect do
908
+ association.map_entities_to_keys(*entities, strict: false)
909
+ end
910
+ .to raise_error ArgumentError, error_message
911
+ end
912
+ end
913
+ end
914
+ end
915
+
916
+ describe 'with one String' do
917
+ let(:entities) { %w[0] }
918
+ let(:error_message) do
919
+ "undefined method :[] or :#{key} for #{entities.first.inspect}"
920
+ end
921
+
922
+ it 'should raise an exception' do
923
+ expect { association.map_entities_to_keys(*entities) }
924
+ .to raise_error ArgumentError, error_message
925
+ end
926
+
927
+ describe 'with strict: false' do
928
+ it 'should raise an exception' do
929
+ expect do
930
+ association.map_entities_to_keys(*entities, strict: false)
931
+ end
932
+ .to raise_error ArgumentError, error_message
933
+ end
934
+ end
935
+
936
+ context 'when initialized with primary_key_type: String' do
937
+ let(:constructor_options) do
938
+ super().merge(primary_key_type: String)
939
+ end
940
+
941
+ describe 'with strict: false' do
942
+ it 'should raise an exception' do
943
+ expect(
944
+ association.map_entities_to_keys(*entities, strict: false)
945
+ )
946
+ .to be == entities
947
+ end
948
+ end
949
+ end
950
+ end
951
+
952
+ describe 'with one entity that responds to #[] and key: nil' do
953
+ let(:entities) { [{ key => nil }] }
954
+
955
+ it { expect(keys).to be == [] }
956
+
957
+ describe 'with allow_nil: true' do
958
+ let(:options) { super().merge(allow_nil: true) }
959
+
960
+ it { expect(keys).to be == [nil] }
961
+ end
962
+ end
963
+
964
+ describe 'with one entity that responds to #[] and key: value' do
965
+ let(:entities) { [{ key => 0 }] }
966
+
967
+ it { expect(keys).to be == [0] }
968
+ end
969
+
970
+ describe 'with one entity that responds to #id and key: nil' do
971
+ let(:entities) { [Spec::Entity.new(key => nil)] }
972
+
973
+ it { expect(keys).to be == [] }
974
+
975
+ describe 'with allow_nil: true' do
976
+ let(:options) { super().merge(allow_nil: true) }
977
+
978
+ it { expect(keys).to be == [nil] }
979
+ end
980
+ end
981
+
982
+ describe 'with one entity that responds to #id and key: value' do
983
+ let(:entities) { [Spec::Entity.new(key => 0)] }
984
+
985
+ it { expect(keys).to be == [0] }
986
+ end
987
+
988
+ describe 'with multiple entities' do
989
+ let(:entities) do
990
+ [
991
+ Spec::Entity.new(key => 0),
992
+ Spec::Entity.new(key => 1),
993
+ Spec::Entity.new(key => 2)
994
+ ]
995
+ end
996
+
997
+ it { expect(keys).to be == [0, 1, 2] }
998
+ end
999
+
1000
+ describe 'with multiple entities including nil' do
1001
+ let(:entities) do
1002
+ [
1003
+ Spec::Entity.new(key => 0),
1004
+ nil,
1005
+ Spec::Entity.new(key => 1),
1006
+ nil,
1007
+ Spec::Entity.new(key => 2)
1008
+ ]
1009
+ end
1010
+
1011
+ it { expect(keys).to be == [0, 1, 2] }
1012
+ end
1013
+
1014
+ describe 'with multiple entities including nil ids' do
1015
+ let(:entities) do
1016
+ [
1017
+ Spec::Entity.new(key => 0),
1018
+ Spec::Entity.new(key => nil),
1019
+ Spec::Entity.new(key => 1),
1020
+ Spec::Entity.new(key => nil),
1021
+ Spec::Entity.new(key => 2)
1022
+ ]
1023
+ end
1024
+
1025
+ it { expect(keys).to be == [0, 1, 2] }
1026
+
1027
+ describe 'with allow_nil: true' do
1028
+ let(:options) { super().merge(allow_nil: true) }
1029
+
1030
+ it { expect(keys).to be == [0, nil, 1, 2] }
1031
+ end
1032
+ end
1033
+
1034
+ describe 'with multiple entities including duplicate ids' do
1035
+ let(:entities) do
1036
+ [
1037
+ Spec::Entity.new(key => 0),
1038
+ Spec::Entity.new(key => 1),
1039
+ Spec::Entity.new(key => 0),
1040
+ Spec::Entity.new(key => 1),
1041
+ Spec::Entity.new(key => 2)
1042
+ ]
1043
+ end
1044
+
1045
+ it { expect(keys).to be == [0, 1, 2] }
1046
+
1047
+ describe 'with deduplicate: false' do
1048
+ let(:options) { super().merge(deduplicate: false) }
1049
+
1050
+ it { expect(keys).to be == [0, 1, 0, 1, 2] }
1051
+ end
1052
+ end
1053
+
1054
+ describe 'with multiple Integers' do
1055
+ let(:entities) { [0, 1, 2] }
1056
+ let(:error_message) do
1057
+ "undefined method :[] or :#{key} for #{entities.first.inspect}"
1058
+ end
1059
+
1060
+ it 'should raise an exception' do
1061
+ expect { association.map_entities_to_keys(*entities) }
1062
+ .to raise_error ArgumentError, error_message
1063
+ end
1064
+
1065
+ describe 'with strict: false' do
1066
+ it 'should raise an exception' do
1067
+ expect(
1068
+ association.map_entities_to_keys(*entities, strict: false)
1069
+ )
1070
+ .to be == entities
1071
+ end
1072
+ end
1073
+
1074
+ context 'when initialized with primary_key_type: String' do
1075
+ let(:constructor_options) do
1076
+ super().merge(primary_key_type: String)
1077
+ end
1078
+
1079
+ describe 'with strict: false' do
1080
+ it 'should raise an exception' do
1081
+ expect do
1082
+ association.map_entities_to_keys(*entities, strict: false)
1083
+ end
1084
+ .to raise_error ArgumentError, error_message
1085
+ end
1086
+ end
1087
+ end
1088
+ end
1089
+
1090
+ describe 'with multiple Strings' do
1091
+ let(:entities) { %w[0 1 2] }
1092
+ let(:error_message) do
1093
+ "undefined method :[] or :#{key} for #{entities.first.inspect}"
1094
+ end
1095
+
1096
+ it 'should raise an exception' do
1097
+ expect { association.map_entities_to_keys(*entities) }
1098
+ .to raise_error ArgumentError, error_message
1099
+ end
1100
+
1101
+ describe 'with strict: false' do
1102
+ it 'should raise an exception' do
1103
+ expect do
1104
+ association.map_entities_to_keys(*entities, strict: false)
1105
+ end
1106
+ .to raise_error ArgumentError, error_message
1107
+ end
1108
+ end
1109
+
1110
+ context 'when initialized with primary_key_type: String' do
1111
+ let(:constructor_options) do
1112
+ super().merge(primary_key_type: String)
1113
+ end
1114
+
1115
+ describe 'with strict: false' do
1116
+ it 'should raise an exception' do
1117
+ expect(
1118
+ association.map_entities_to_keys(*entities, strict: false)
1119
+ )
1120
+ .to be == entities
1121
+ end
1122
+ end
1123
+ end
1124
+ end
1125
+ end
1126
+
1127
+ describe '#primary_key_query?' do
1128
+ it { expect(subject.primary_key_query?).to be true }
1129
+ end
1130
+
1131
+ describe '#query_key_name' do
1132
+ it { expect(subject.query_key_name).to be == 'id' }
1133
+
1134
+ context 'when initialized with primary_key_name: a String' do
1135
+ let(:primary_key_name) { 'uuid' }
1136
+ let(:constructor_options) do
1137
+ super().merge(primary_key_name: primary_key_name)
1138
+ end
1139
+
1140
+ it { expect(subject.query_key_name).to be == primary_key_name }
1141
+ end
1142
+
1143
+ context 'when initialized with primary_key_name: a Symbol' do
1144
+ let(:primary_key_name) { :uuid }
1145
+ let(:constructor_options) do
1146
+ super().merge(primary_key_name: primary_key_name)
1147
+ end
1148
+
1149
+ it 'should set the primary key name' do
1150
+ expect(subject.query_key_name).to be == primary_key_name.to_s
1151
+ end
1152
+ end
1153
+ end
1154
+ end
1155
+ end
1156
+
1157
+ # Contract validating the behavior of a HasAssociation.
1158
+ module ShouldBeAHasAssociationContract
1159
+ extend RSpec::SleepingKingStudios::Contract
1160
+
1161
+ # @!method apply(example_group)
1162
+ # Adds the contract to the example group.
1163
+ #
1164
+ # @param example_group [RSpec::Core::ExampleGroup] the example group to
1165
+ # which the contract is applied.
1166
+ contract do
1167
+ include Cuprum::Collections::RSpec::Contracts::RelationContracts
1168
+
1169
+ include_contract 'should be an association'
1170
+
1171
+ describe '#build_entities_query' do
1172
+ let(:key) { subject.primary_key_name }
1173
+ let(:entities) { [] }
1174
+ let(:options) { {} }
1175
+ let(:query) do
1176
+ association.build_entities_query(*entities, **options)
1177
+ end
1178
+ let(:evaluated) do
1179
+ Spec::QueryBuilder.new.instance_exec(&query)
1180
+ end
1181
+
1182
+ example_class 'Spec::Entity' do |klass|
1183
+ klass.define_method(:initialize) do |**attributes|
1184
+ attributes.each do |key, value|
1185
+ instance_variable_set(:"@#{key}", value)
1186
+ end
1187
+ end
1188
+
1189
+ klass.attr_reader :id
1190
+ end
1191
+
1192
+ example_class 'Spec::QueryBuilder' do |klass|
1193
+ klass.define_method(:one_of) { |values| { 'one_of' => values } }
1194
+ end
1195
+
1196
+ context 'when the foreign key name is blank' do
1197
+ let(:error_message) do
1198
+ "foreign key name can't be blank"
1199
+ end
1200
+
1201
+ it 'should raise an exception' do
1202
+ expect { association.build_entities_query(*entities) }
1203
+ .to raise_error ArgumentError, error_message
1204
+ end
1205
+ end
1206
+
1207
+ context 'when initialized with foreign_key_name: value' do
1208
+ let(:foreign_key_name) { 'author_id' }
1209
+ let(:constructor_options) do
1210
+ super().merge(foreign_key_name: foreign_key_name)
1211
+ end
1212
+
1213
+ describe 'with no entities' do
1214
+ let(:entities) { [] }
1215
+
1216
+ it { expect(query).to be_a Proc }
1217
+
1218
+ it { expect(evaluated).to be == {} }
1219
+ end
1220
+
1221
+ describe 'with one nil entity' do
1222
+ let(:entities) { [nil] }
1223
+
1224
+ it { expect(evaluated).to be == {} }
1225
+ end
1226
+
1227
+ describe 'with one invalid entity' do
1228
+ let(:entities) { [Object.new.freeze] }
1229
+ let(:error_message) do
1230
+ "undefined method :[] or :#{key} for #{entities.first.inspect}"
1231
+ end
1232
+
1233
+ it 'should raise an exception' do
1234
+ expect { association.build_entities_query(*entities) }
1235
+ .to raise_error ArgumentError, error_message
1236
+ end
1237
+ end
1238
+
1239
+ describe 'with one entity that responds to #[] and key: nil' do
1240
+ let(:entities) { [{ key => nil }] }
1241
+
1242
+ it { expect(evaluated).to be == {} }
1243
+
1244
+ describe 'with allow_nil: true' do
1245
+ let(:options) { super().merge(allow_nil: true) }
1246
+
1247
+ it { expect(evaluated).to be == { 'author_id' => nil } }
1248
+ end
1249
+ end
1250
+
1251
+ describe 'with one entity that responds to #[] and key: value' do
1252
+ let(:entities) { [{ key => 0 }] }
1253
+
1254
+ it { expect(evaluated).to be == { 'author_id' => 0 } }
1255
+ end
1256
+
1257
+ describe 'with one entity that responds to #id and key: nil' do
1258
+ let(:entities) { [Spec::Entity.new(key => nil)] }
1259
+
1260
+ it { expect(evaluated).to be == {} }
1261
+
1262
+ describe 'with allow_nil: true' do
1263
+ let(:options) { super().merge(allow_nil: true) }
1264
+
1265
+ it { expect(evaluated).to be == { 'author_id' => nil } }
1266
+ end
1267
+ end
1268
+
1269
+ describe 'with one entity that responds to #id and key: value' do
1270
+ let(:entities) { [Spec::Entity.new(key => 0)] }
1271
+
1272
+ it { expect(evaluated).to be == { 'author_id' => 0 } }
1273
+ end
1274
+
1275
+ describe 'with multiple entities' do
1276
+ let(:entities) do
1277
+ [
1278
+ Spec::Entity.new(key => 0),
1279
+ Spec::Entity.new(key => 1),
1280
+ Spec::Entity.new(key => 2)
1281
+ ]
1282
+ end
1283
+ let(:expected) do
1284
+ { 'author_id' => { 'one_of' => [0, 1, 2] } }
1285
+ end
1286
+
1287
+ it { expect(evaluated).to be == expected }
1288
+ end
1289
+
1290
+ describe 'with multiple entities including nil' do
1291
+ let(:entities) do
1292
+ [
1293
+ Spec::Entity.new(key => 0),
1294
+ nil,
1295
+ Spec::Entity.new(key => 1),
1296
+ nil,
1297
+ Spec::Entity.new(key => 2)
1298
+ ]
1299
+ end
1300
+ let(:expected) do
1301
+ { 'author_id' => { 'one_of' => [0, 1, 2] } }
1302
+ end
1303
+
1304
+ it { expect(evaluated).to be == expected }
1305
+ end
1306
+
1307
+ describe 'with multiple entities including nil ids' do
1308
+ let(:entities) do
1309
+ [
1310
+ Spec::Entity.new(key => 0),
1311
+ Spec::Entity.new(key => nil),
1312
+ Spec::Entity.new(key => 1),
1313
+ Spec::Entity.new(key => nil),
1314
+ Spec::Entity.new(key => 2)
1315
+ ]
1316
+ end
1317
+ let(:expected) do
1318
+ { 'author_id' => { 'one_of' => [0, 1, 2] } }
1319
+ end
1320
+
1321
+ it { expect(evaluated).to be == expected }
1322
+
1323
+ describe 'with allow_nil: true' do
1324
+ let(:options) { super().merge(allow_nil: true) }
1325
+ let(:expected) do
1326
+ { 'author_id' => { 'one_of' => [0, nil, 1, 2] } }
1327
+ end
1328
+
1329
+ it { expect(evaluated).to be == expected }
1330
+ end
1331
+ end
1332
+
1333
+ describe 'with multiple entities including duplicate ids' do
1334
+ let(:entities) do
1335
+ [
1336
+ Spec::Entity.new(key => 0),
1337
+ Spec::Entity.new(key => 1),
1338
+ Spec::Entity.new(key => 0),
1339
+ Spec::Entity.new(key => 1),
1340
+ Spec::Entity.new(key => 2)
1341
+ ]
1342
+ end
1343
+ let(:expected) do
1344
+ { 'author_id' => { 'one_of' => [0, 1, 2] } }
1345
+ end
1346
+
1347
+ it { expect(evaluated).to be == expected }
1348
+
1349
+ describe 'with deduplicate: false' do
1350
+ let(:options) { super().merge(deduplicate: false) }
1351
+ let(:expected) do
1352
+ { 'author_id' => { 'one_of' => [0, 1, 0, 1, 2] } }
1353
+ end
1354
+
1355
+ it { expect(evaluated).to be == expected }
1356
+ end
1357
+ end
1358
+ end
1359
+ end
1360
+
1361
+ describe '#build_keys_query' do
1362
+ let(:keys) { [] }
1363
+ let(:options) { {} }
1364
+ let(:query) do
1365
+ association.build_keys_query(*keys, **options)
1366
+ end
1367
+ let(:evaluated) do
1368
+ Spec::QueryBuilder.new.instance_exec(&query)
1369
+ end
1370
+
1371
+ example_class 'Spec::QueryBuilder' do |klass|
1372
+ klass.define_method(:one_of) { |values| { 'one_of' => values } }
1373
+ end
1374
+
1375
+ context 'when the foreign key name is blank' do
1376
+ let(:error_message) do
1377
+ "foreign key name can't be blank"
1378
+ end
1379
+
1380
+ it 'should raise an exception' do
1381
+ expect { association.build_keys_query(*keys) }
1382
+ .to raise_error ArgumentError, error_message
1383
+ end
1384
+ end
1385
+
1386
+ context 'when initialized with foreign_key_name: value' do
1387
+ let(:foreign_key_name) { 'author_id' }
1388
+ let(:constructor_options) do
1389
+ super().merge(foreign_key_name: foreign_key_name)
1390
+ end
1391
+
1392
+ describe 'with no keys' do
1393
+ let(:keys) { [] }
1394
+
1395
+ it { expect(query).to be_a Proc }
1396
+
1397
+ it { expect(evaluated).to be == {} }
1398
+ end
1399
+
1400
+ describe 'with one nil key' do
1401
+ let(:keys) { [nil] }
1402
+
1403
+ it { expect(evaluated).to be == {} }
1404
+
1405
+ describe 'with allow_nil: true' do
1406
+ let(:options) { { allow_nil: true } }
1407
+
1408
+ it { expect(evaluated).to be == { 'author_id' => nil } }
1409
+ end
1410
+ end
1411
+
1412
+ describe 'with one non-nil key' do
1413
+ let(:keys) { [0] }
1414
+
1415
+ it { expect(evaluated).to be == { 'author_id' => 0 } }
1416
+ end
1417
+
1418
+ describe 'with many keys' do
1419
+ let(:keys) { [0, 1, 2] }
1420
+ let(:expected) { { 'author_id' => { 'one_of' => keys } } }
1421
+
1422
+ it { expect(evaluated).to be == expected }
1423
+ end
1424
+
1425
+ describe 'with many keys including nil' do
1426
+ let(:keys) { [0, nil, 2] }
1427
+ let(:expected) { { 'author_id' => { 'one_of' => [0, 2] } } }
1428
+
1429
+ it { expect(evaluated).to be == expected }
1430
+
1431
+ describe 'with allow_nil: true' do
1432
+ let(:options) { { allow_nil: true } }
1433
+ let(:expected) do
1434
+ { 'author_id' => { 'one_of' => [0, nil, 2] } }
1435
+ end
1436
+
1437
+ it { expect(evaluated).to be == expected }
1438
+ end
1439
+ end
1440
+
1441
+ describe 'with many non-unique keys' do
1442
+ let(:keys) { [0, 1, 2, 1, 2] }
1443
+ let(:expected) { { 'author_id' => { 'one_of' => keys.uniq } } }
1444
+
1445
+ it { expect(evaluated).to be == expected }
1446
+
1447
+ describe 'with deduplicate: false' do
1448
+ let(:options) { super().merge(deduplicate: false) }
1449
+ let(:expected) do
1450
+ { 'author_id' => { 'one_of' => [0, 1, 2, 1, 2] } }
1451
+ end
1452
+
1453
+ it { expect(evaluated).to be == expected }
1454
+ end
1455
+ end
1456
+ end
1457
+ end
1458
+
1459
+ describe '#foreign_key_name' do
1460
+ it { expect(subject.foreign_key_name).to be nil }
1461
+
1462
+ context 'when initialized with foreign_key_name: a String' do
1463
+ let(:foreign_key_name) { 'writer_id' }
1464
+ let(:constructor_options) do
1465
+ super().merge(foreign_key_name: foreign_key_name)
1466
+ end
1467
+
1468
+ it { expect(subject.foreign_key_name).to be == 'writer_id' }
1469
+ end
1470
+
1471
+ context 'when initialized with foreign_key_name: a String' do
1472
+ let(:foreign_key_name) { :writer_id }
1473
+ let(:constructor_options) do
1474
+ super().merge(foreign_key_name: foreign_key_name)
1475
+ end
1476
+
1477
+ it { expect(subject.foreign_key_name).to be == 'writer_id' }
1478
+ end
1479
+
1480
+ context 'when initialized with inverse: value' do
1481
+ let(:inverse) { described_class.new(name: 'authors') }
1482
+ let(:constructor_options) do
1483
+ super().merge(inverse: inverse)
1484
+ end
1485
+
1486
+ it { expect(subject.foreign_key_name).to be == 'author_id' }
1487
+
1488
+ context 'when initialized with foreign_key_name: a String' do
1489
+ let(:foreign_key_name) { 'writer_id' }
1490
+ let(:constructor_options) do
1491
+ super().merge(foreign_key_name: foreign_key_name)
1492
+ end
1493
+
1494
+ it { expect(subject.foreign_key_name).to be == 'writer_id' }
1495
+ end
1496
+
1497
+ context 'when initialized with foreign_key_name: a String' do
1498
+ let(:foreign_key_name) { :writer_id }
1499
+ let(:constructor_options) do
1500
+ super().merge(foreign_key_name: foreign_key_name)
1501
+ end
1502
+
1503
+ it { expect(subject.foreign_key_name).to be == 'writer_id' }
1504
+ end
1505
+
1506
+ context 'when initialized with inverse_name: value' do
1507
+ let(:inverse_name) { 'writers' }
1508
+ let(:constructor_options) do
1509
+ super().merge(inverse_name: inverse_name)
1510
+ end
1511
+
1512
+ it { expect(subject.foreign_key_name).to be == 'author_id' }
1513
+ end
1514
+
1515
+ context 'when initialized with singular_inverse_name: value' do
1516
+ let(:singular_inverse_name) { 'writer' }
1517
+ let(:constructor_options) do
1518
+ super().merge(singular_inverse_name: singular_inverse_name)
1519
+ end
1520
+
1521
+ it { expect(subject.foreign_key_name).to be == 'writer_id' }
1522
+ end
1523
+ end
1524
+
1525
+ context 'when initialized with inverse_name: value' do
1526
+ let(:inverse_name) { 'authors' }
1527
+ let(:constructor_options) do
1528
+ super().merge(inverse_name: inverse_name)
1529
+ end
1530
+
1531
+ it { expect(subject.foreign_key_name).to be == 'author_id' }
1532
+
1533
+ context 'when initialized with foreign_key_name: a String' do
1534
+ let(:foreign_key_name) { 'writer_id' }
1535
+ let(:constructor_options) do
1536
+ super().merge(foreign_key_name: foreign_key_name)
1537
+ end
1538
+
1539
+ it { expect(subject.foreign_key_name).to be == 'writer_id' }
1540
+ end
1541
+
1542
+ context 'when initialized with foreign_key_name: a String' do
1543
+ let(:foreign_key_name) { :writer_id }
1544
+ let(:constructor_options) do
1545
+ super().merge(foreign_key_name: foreign_key_name)
1546
+ end
1547
+
1548
+ it { expect(subject.foreign_key_name).to be == 'writer_id' }
1549
+ end
1550
+ end
1551
+
1552
+ context 'when initialized with singular_inverse_name: value' do
1553
+ let(:singular_inverse_name) { 'author' }
1554
+ let(:constructor_options) do
1555
+ super().merge(singular_inverse_name: singular_inverse_name)
1556
+ end
1557
+
1558
+ it { expect(subject.foreign_key_name).to be == 'author_id' }
1559
+
1560
+ context 'when initialized with foreign_key_name: a String' do
1561
+ let(:foreign_key_name) { 'writer_id' }
1562
+ let(:constructor_options) do
1563
+ super().merge(foreign_key_name: foreign_key_name)
1564
+ end
1565
+
1566
+ it { expect(subject.foreign_key_name).to be == 'writer_id' }
1567
+ end
1568
+
1569
+ context 'when initialized with foreign_key_name: a String' do
1570
+ let(:foreign_key_name) { :writer_id }
1571
+ let(:constructor_options) do
1572
+ super().merge(foreign_key_name: foreign_key_name)
1573
+ end
1574
+
1575
+ it { expect(subject.foreign_key_name).to be == 'writer_id' }
1576
+ end
1577
+ end
1578
+
1579
+ context 'with a copy with assigned inverse' do
1580
+ subject do
1581
+ super().tap(&:foreign_key_name).with_inverse(new_inverse)
1582
+ end
1583
+
1584
+ let(:new_inverse) { described_class.new(name: 'chapters') }
1585
+
1586
+ it { expect(subject.foreign_key_name).to be == 'chapter_id' }
1587
+
1588
+ context 'when initialized with foreign_key_name: a String' do
1589
+ let(:foreign_key_name) { 'writer_id' }
1590
+ let(:constructor_options) do
1591
+ super().merge(foreign_key_name: foreign_key_name)
1592
+ end
1593
+
1594
+ it { expect(subject.foreign_key_name).to be == 'writer_id' }
1595
+ end
1596
+
1597
+ context 'when initialized with foreign_key_name: a String' do
1598
+ let(:foreign_key_name) { :writer_id }
1599
+ let(:constructor_options) do
1600
+ super().merge(foreign_key_name: foreign_key_name)
1601
+ end
1602
+
1603
+ it { expect(subject.foreign_key_name).to be == 'writer_id' }
1604
+ end
1605
+
1606
+ context 'when initialized with inverse: value' do
1607
+ let(:inverse) { described_class.new(name: 'authors') }
1608
+ let(:constructor_options) do
1609
+ super().merge(inverse: inverse)
1610
+ end
1611
+
1612
+ it { expect(subject.foreign_key_name).to be == 'chapter_id' }
1613
+ end
1614
+
1615
+ context 'when initialized with inverse_name: value' do
1616
+ let(:inverse_name) { 'authors' }
1617
+ let(:constructor_options) do
1618
+ super().merge(inverse_name: inverse_name)
1619
+ end
1620
+
1621
+ it { expect(subject.foreign_key_name).to be == 'chapter_id' }
1622
+ end
1623
+
1624
+ context 'when initialized with singular_inverse_name: value' do
1625
+ let(:singular_inverse_name) { 'author' }
1626
+ let(:constructor_options) do
1627
+ super().merge(singular_inverse_name: singular_inverse_name)
1628
+ end
1629
+
1630
+ it { expect(subject.foreign_key_name).to be == 'author_id' }
1631
+ end
1632
+ end
1633
+ end
1634
+
1635
+ describe '#inverse_key_name' do
1636
+ it { expect(subject.inverse_key_name).to be == 'id' }
1637
+
1638
+ context 'when initialized with primary_key_name: a String' do
1639
+ let(:primary_key_name) { 'uuid' }
1640
+ let(:constructor_options) do
1641
+ super().merge(primary_key_name: primary_key_name)
1642
+ end
1643
+
1644
+ it { expect(subject.inverse_key_name).to be == primary_key_name }
1645
+ end
1646
+
1647
+ context 'when initialized with primary_key_name: a Symbol' do
1648
+ let(:primary_key_name) { :uuid }
1649
+ let(:constructor_options) do
1650
+ super().merge(primary_key_name: primary_key_name)
1651
+ end
1652
+
1653
+ it 'should set the primary key name' do
1654
+ expect(subject.inverse_key_name).to be == primary_key_name.to_s
1655
+ end
1656
+ end
1657
+ end
1658
+
1659
+ describe '#map_entities_to_keys' do
1660
+ let(:key) { subject.primary_key_name }
1661
+ let(:entities) { [] }
1662
+ let(:options) { {} }
1663
+ let(:keys) { subject.map_entities_to_keys(*entities, **options) }
1664
+
1665
+ example_class 'Spec::Entity' do |klass|
1666
+ klass.define_method(:initialize) do |**attributes|
1667
+ attributes.each do |key, value|
1668
+ instance_variable_set(:"@#{key}", value)
1669
+ end
1670
+ end
1671
+
1672
+ klass.attr_reader :id
1673
+ end
1674
+
1675
+ describe 'with no entities' do
1676
+ let(:entities) { [] }
1677
+
1678
+ it { expect(keys).to be == [] }
1679
+ end
1680
+
1681
+ describe 'with one nil entity' do
1682
+ let(:entities) { [nil] }
1683
+
1684
+ it { expect(keys).to be == [] }
1685
+ end
1686
+
1687
+ describe 'with one invalid entity' do
1688
+ let(:entities) { [Object.new.freeze] }
1689
+ let(:error_message) do
1690
+ "undefined method :[] or :#{key} for #{entities.first.inspect}"
1691
+ end
1692
+
1693
+ it 'should raise an exception' do
1694
+ expect { association.map_entities_to_keys(*entities) }
1695
+ .to raise_error ArgumentError, error_message
1696
+ end
1697
+ end
1698
+
1699
+ describe 'with one Integer' do
1700
+ let(:entities) { [0] }
1701
+ let(:error_message) do
1702
+ "undefined method :[] or :#{key} for #{entities.first.inspect}"
1703
+ end
1704
+
1705
+ it 'should raise an exception' do
1706
+ expect { association.map_entities_to_keys(*entities) }
1707
+ .to raise_error ArgumentError, error_message
1708
+ end
1709
+
1710
+ describe 'with strict: false' do
1711
+ it 'should raise an exception' do
1712
+ expect(
1713
+ association.map_entities_to_keys(*entities, strict: false)
1714
+ )
1715
+ .to be == entities
1716
+ end
1717
+ end
1718
+
1719
+ context 'when initialized with primary_key_type: String' do
1720
+ let(:constructor_options) do
1721
+ super().merge(primary_key_type: String)
1722
+ end
1723
+
1724
+ describe 'with strict: false' do
1725
+ it 'should raise an exception' do
1726
+ expect do
1727
+ association.map_entities_to_keys(*entities, strict: false)
1728
+ end
1729
+ .to raise_error ArgumentError, error_message
1730
+ end
1731
+ end
1732
+ end
1733
+ end
1734
+
1735
+ describe 'with one String' do
1736
+ let(:entities) { %w[0] }
1737
+ let(:error_message) do
1738
+ "undefined method :[] or :#{key} for #{entities.first.inspect}"
1739
+ end
1740
+
1741
+ it 'should raise an exception' do
1742
+ expect { association.map_entities_to_keys(*entities) }
1743
+ .to raise_error ArgumentError, error_message
1744
+ end
1745
+
1746
+ describe 'with strict: false' do
1747
+ it 'should raise an exception' do
1748
+ expect do
1749
+ association.map_entities_to_keys(*entities, strict: false)
1750
+ end
1751
+ .to raise_error ArgumentError, error_message
1752
+ end
1753
+ end
1754
+
1755
+ context 'when initialized with primary_key_type: String' do
1756
+ let(:constructor_options) do
1757
+ super().merge(primary_key_type: String)
1758
+ end
1759
+
1760
+ describe 'with strict: false' do
1761
+ it 'should raise an exception' do
1762
+ expect(
1763
+ association.map_entities_to_keys(*entities, strict: false)
1764
+ )
1765
+ .to be == entities
1766
+ end
1767
+ end
1768
+ end
1769
+ end
1770
+
1771
+ describe 'with one entity that responds to #[] and key: nil' do
1772
+ let(:entities) { [{ key => nil }] }
1773
+
1774
+ it { expect(keys).to be == [] }
1775
+
1776
+ describe 'with allow_nil: true' do
1777
+ let(:options) { super().merge(allow_nil: true) }
1778
+
1779
+ it { expect(keys).to be == [nil] }
1780
+ end
1781
+ end
1782
+
1783
+ describe 'with one entity that responds to #[] and key: value' do
1784
+ let(:entities) { [{ key => 0 }] }
1785
+
1786
+ it { expect(keys).to be == [0] }
1787
+ end
1788
+
1789
+ describe 'with one entity that responds to #id and key: nil' do
1790
+ let(:entities) { [Spec::Entity.new(key => nil)] }
1791
+
1792
+ it { expect(keys).to be == [] }
1793
+
1794
+ describe 'with allow_nil: true' do
1795
+ let(:options) { super().merge(allow_nil: true) }
1796
+
1797
+ it { expect(keys).to be == [nil] }
1798
+ end
1799
+ end
1800
+
1801
+ describe 'with one entity that responds to #id and key: value' do
1802
+ let(:entities) { [Spec::Entity.new(key => 0)] }
1803
+
1804
+ it { expect(keys).to be == [0] }
1805
+ end
1806
+
1807
+ describe 'with multiple entities' do
1808
+ let(:entities) do
1809
+ [
1810
+ Spec::Entity.new(key => 0),
1811
+ Spec::Entity.new(key => 1),
1812
+ Spec::Entity.new(key => 2)
1813
+ ]
1814
+ end
1815
+
1816
+ it { expect(keys).to be == [0, 1, 2] }
1817
+ end
1818
+
1819
+ describe 'with multiple entities including nil' do
1820
+ let(:entities) do
1821
+ [
1822
+ Spec::Entity.new(key => 0),
1823
+ nil,
1824
+ Spec::Entity.new(key => 1),
1825
+ nil,
1826
+ Spec::Entity.new(key => 2)
1827
+ ]
1828
+ end
1829
+
1830
+ it { expect(keys).to be == [0, 1, 2] }
1831
+ end
1832
+
1833
+ describe 'with multiple entities including nil ids' do
1834
+ let(:entities) do
1835
+ [
1836
+ Spec::Entity.new(key => 0),
1837
+ Spec::Entity.new(key => nil),
1838
+ Spec::Entity.new(key => 1),
1839
+ Spec::Entity.new(key => nil),
1840
+ Spec::Entity.new(key => 2)
1841
+ ]
1842
+ end
1843
+
1844
+ it { expect(keys).to be == [0, 1, 2] }
1845
+
1846
+ describe 'with allow_nil: true' do
1847
+ let(:options) { super().merge(allow_nil: true) }
1848
+
1849
+ it { expect(keys).to be == [0, nil, 1, 2] }
1850
+ end
1851
+ end
1852
+
1853
+ describe 'with multiple entities including duplicate ids' do
1854
+ let(:entities) do
1855
+ [
1856
+ Spec::Entity.new(key => 0),
1857
+ Spec::Entity.new(key => 1),
1858
+ Spec::Entity.new(key => 0),
1859
+ Spec::Entity.new(key => 1),
1860
+ Spec::Entity.new(key => 2)
1861
+ ]
1862
+ end
1863
+
1864
+ it { expect(keys).to be == [0, 1, 2] }
1865
+
1866
+ describe 'with deduplicate: false' do
1867
+ let(:options) { super().merge(deduplicate: false) }
1868
+
1869
+ it { expect(keys).to be == [0, 1, 0, 1, 2] }
1870
+ end
1871
+ end
1872
+
1873
+ describe 'with multiple Integers' do
1874
+ let(:entities) { [0, 1, 2] }
1875
+ let(:error_message) do
1876
+ "undefined method :[] or :#{key} for #{entities.first.inspect}"
1877
+ end
1878
+
1879
+ it 'should raise an exception' do
1880
+ expect { association.map_entities_to_keys(*entities) }
1881
+ .to raise_error ArgumentError, error_message
1882
+ end
1883
+
1884
+ describe 'with strict: false' do
1885
+ it 'should raise an exception' do
1886
+ expect(
1887
+ association.map_entities_to_keys(*entities, strict: false)
1888
+ )
1889
+ .to be == entities
1890
+ end
1891
+ end
1892
+
1893
+ context 'when initialized with primary_key_type: String' do
1894
+ let(:constructor_options) do
1895
+ super().merge(primary_key_type: String)
1896
+ end
1897
+
1898
+ describe 'with strict: false' do
1899
+ it 'should raise an exception' do
1900
+ expect do
1901
+ association.map_entities_to_keys(*entities, strict: false)
1902
+ end
1903
+ .to raise_error ArgumentError, error_message
1904
+ end
1905
+ end
1906
+ end
1907
+ end
1908
+
1909
+ describe 'with multiple Strings' do
1910
+ let(:entities) { %w[0 1 2] }
1911
+ let(:error_message) do
1912
+ "undefined method :[] or :#{key} for #{entities.first.inspect}"
1913
+ end
1914
+
1915
+ it 'should raise an exception' do
1916
+ expect { association.map_entities_to_keys(*entities) }
1917
+ .to raise_error ArgumentError, error_message
1918
+ end
1919
+
1920
+ describe 'with strict: false' do
1921
+ it 'should raise an exception' do
1922
+ expect do
1923
+ association.map_entities_to_keys(*entities, strict: false)
1924
+ end
1925
+ .to raise_error ArgumentError, error_message
1926
+ end
1927
+ end
1928
+
1929
+ context 'when initialized with primary_key_type: String' do
1930
+ let(:constructor_options) do
1931
+ super().merge(primary_key_type: String)
1932
+ end
1933
+
1934
+ describe 'with strict: false' do
1935
+ it 'should raise an exception' do
1936
+ expect(
1937
+ association.map_entities_to_keys(*entities, strict: false)
1938
+ )
1939
+ .to be == entities
1940
+ end
1941
+ end
1942
+ end
1943
+ end
1944
+ end
1945
+
1946
+ describe '#primary_key_query?' do
1947
+ it { expect(subject.primary_key_query?).to be false }
1948
+ end
1949
+
1950
+ describe '#query_key_name' do
1951
+ context 'when the foreign key name is blank' do
1952
+ let(:error_message) do
1953
+ "foreign key name can't be blank"
1954
+ end
1955
+
1956
+ it 'should raise an exception' do
1957
+ expect { association.query_key_name }
1958
+ .to raise_error ArgumentError, error_message
1959
+ end
1960
+ end
1961
+
1962
+ context 'when initialized with foreign_key_name: a String' do
1963
+ let(:foreign_key_name) { 'writer_id' }
1964
+ let(:constructor_options) do
1965
+ super().merge(foreign_key_name: foreign_key_name)
1966
+ end
1967
+
1968
+ it { expect(subject.query_key_name).to be == 'writer_id' }
1969
+ end
1970
+
1971
+ context 'when initialized with foreign_key_name: a String' do
1972
+ let(:foreign_key_name) { :writer_id }
1973
+ let(:constructor_options) do
1974
+ super().merge(foreign_key_name: foreign_key_name)
1975
+ end
1976
+
1977
+ it { expect(subject.query_key_name).to be == 'writer_id' }
1978
+ end
1979
+
1980
+ context 'when initialized with inverse: value' do
1981
+ let(:inverse) { described_class.new(name: 'authors') }
1982
+ let(:constructor_options) do
1983
+ super().merge(inverse: inverse)
1984
+ end
1985
+
1986
+ it { expect(subject.query_key_name).to be == 'author_id' }
1987
+
1988
+ context 'when initialized with foreign_key_name: a String' do
1989
+ let(:foreign_key_name) { 'writer_id' }
1990
+ let(:constructor_options) do
1991
+ super().merge(foreign_key_name: foreign_key_name)
1992
+ end
1993
+
1994
+ it { expect(subject.query_key_name).to be == 'writer_id' }
1995
+ end
1996
+
1997
+ context 'when initialized with foreign_key_name: a String' do
1998
+ let(:foreign_key_name) { :writer_id }
1999
+ let(:constructor_options) do
2000
+ super().merge(foreign_key_name: foreign_key_name)
2001
+ end
2002
+
2003
+ it { expect(subject.query_key_name).to be == 'writer_id' }
2004
+ end
2005
+
2006
+ context 'when initialized with inverse_name: value' do
2007
+ let(:inverse_name) { 'writers' }
2008
+ let(:constructor_options) do
2009
+ super().merge(inverse_name: inverse_name)
2010
+ end
2011
+
2012
+ it { expect(subject.query_key_name).to be == 'author_id' }
2013
+ end
2014
+
2015
+ context 'when initialized with singular_inverse_name: value' do
2016
+ let(:singular_inverse_name) { 'writer' }
2017
+ let(:constructor_options) do
2018
+ super().merge(singular_inverse_name: singular_inverse_name)
2019
+ end
2020
+
2021
+ it { expect(subject.query_key_name).to be == 'writer_id' }
2022
+ end
2023
+ end
2024
+
2025
+ context 'when initialized with inverse_name: value' do
2026
+ let(:inverse_name) { 'authors' }
2027
+ let(:constructor_options) do
2028
+ super().merge(inverse_name: inverse_name)
2029
+ end
2030
+
2031
+ it { expect(subject.query_key_name).to be == 'author_id' }
2032
+
2033
+ context 'when initialized with foreign_key_name: a String' do
2034
+ let(:foreign_key_name) { 'writer_id' }
2035
+ let(:constructor_options) do
2036
+ super().merge(foreign_key_name: foreign_key_name)
2037
+ end
2038
+
2039
+ it { expect(subject.query_key_name).to be == 'writer_id' }
2040
+ end
2041
+
2042
+ context 'when initialized with foreign_key_name: a String' do
2043
+ let(:foreign_key_name) { :writer_id }
2044
+ let(:constructor_options) do
2045
+ super().merge(foreign_key_name: foreign_key_name)
2046
+ end
2047
+
2048
+ it { expect(subject.query_key_name).to be == 'writer_id' }
2049
+ end
2050
+ end
2051
+
2052
+ context 'when initialized with singular_inverse_name: value' do
2053
+ let(:singular_inverse_name) { 'author' }
2054
+ let(:constructor_options) do
2055
+ super().merge(singular_inverse_name: singular_inverse_name)
2056
+ end
2057
+
2058
+ it { expect(subject.query_key_name).to be == 'author_id' }
2059
+
2060
+ context 'when initialized with foreign_key_name: a String' do
2061
+ let(:foreign_key_name) { 'writer_id' }
2062
+ let(:constructor_options) do
2063
+ super().merge(foreign_key_name: foreign_key_name)
2064
+ end
2065
+
2066
+ it { expect(subject.query_key_name).to be == 'writer_id' }
2067
+ end
2068
+
2069
+ context 'when initialized with foreign_key_name: a String' do
2070
+ let(:foreign_key_name) { :writer_id }
2071
+ let(:constructor_options) do
2072
+ super().merge(foreign_key_name: foreign_key_name)
2073
+ end
2074
+
2075
+ it { expect(subject.query_key_name).to be == 'writer_id' }
2076
+ end
2077
+ end
2078
+
2079
+ context 'with a copy with assigned inverse' do
2080
+ subject do
2081
+ super().tap(&:foreign_key_name).with_inverse(new_inverse)
2082
+ end
2083
+
2084
+ let(:new_inverse) { described_class.new(name: 'chapters') }
2085
+
2086
+ it { expect(subject.query_key_name).to be == 'chapter_id' }
2087
+
2088
+ context 'when initialized with foreign_key_name: a String' do
2089
+ let(:foreign_key_name) { 'writer_id' }
2090
+ let(:constructor_options) do
2091
+ super().merge(foreign_key_name: foreign_key_name)
2092
+ end
2093
+
2094
+ it { expect(subject.query_key_name).to be == 'writer_id' }
2095
+ end
2096
+
2097
+ context 'when initialized with foreign_key_name: a String' do
2098
+ let(:foreign_key_name) { :writer_id }
2099
+ let(:constructor_options) do
2100
+ super().merge(foreign_key_name: foreign_key_name)
2101
+ end
2102
+
2103
+ it { expect(subject.query_key_name).to be == 'writer_id' }
2104
+ end
2105
+
2106
+ context 'when initialized with inverse: value' do
2107
+ let(:inverse) { described_class.new(name: 'authors') }
2108
+ let(:constructor_options) do
2109
+ super().merge(inverse: inverse)
2110
+ end
2111
+
2112
+ it { expect(subject.query_key_name).to be == 'chapter_id' }
2113
+ end
2114
+
2115
+ context 'when initialized with inverse_name: value' do
2116
+ let(:inverse_name) { 'authors' }
2117
+ let(:constructor_options) do
2118
+ super().merge(inverse_name: inverse_name)
2119
+ end
2120
+
2121
+ it { expect(subject.query_key_name).to be == 'chapter_id' }
2122
+ end
2123
+
2124
+ context 'when initialized with singular_inverse_name: value' do
2125
+ let(:singular_inverse_name) { 'author' }
2126
+ let(:constructor_options) do
2127
+ super().merge(singular_inverse_name: singular_inverse_name)
2128
+ end
2129
+
2130
+ it { expect(subject.query_key_name).to be == 'author_id' }
2131
+ end
2132
+ end
2133
+ end
2134
+ end
2135
+ end
2136
+ end
2137
+ end