cuprum-collections 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +1 -1
  37. data/lib/cuprum/collections.rb +9 -4
  38. metadata +23 -19
  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