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