cuprum-rails 0.1.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 (81) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +98 -0
  3. data/CODE_OF_CONDUCT.md +132 -0
  4. data/DEVELOPMENT.md +28 -0
  5. data/LICENSE +22 -0
  6. data/README.md +1045 -0
  7. data/lib/cuprum/rails/action.rb +45 -0
  8. data/lib/cuprum/rails/actions/create.rb +49 -0
  9. data/lib/cuprum/rails/actions/destroy.rb +22 -0
  10. data/lib/cuprum/rails/actions/edit.rb +22 -0
  11. data/lib/cuprum/rails/actions/index.rb +55 -0
  12. data/lib/cuprum/rails/actions/new.rb +19 -0
  13. data/lib/cuprum/rails/actions/resource_action.rb +75 -0
  14. data/lib/cuprum/rails/actions/show.rb +22 -0
  15. data/lib/cuprum/rails/actions/update.rb +59 -0
  16. data/lib/cuprum/rails/actions.rb +16 -0
  17. data/lib/cuprum/rails/collection.rb +115 -0
  18. data/lib/cuprum/rails/command.rb +137 -0
  19. data/lib/cuprum/rails/commands/assign_one.rb +66 -0
  20. data/lib/cuprum/rails/commands/build_one.rb +55 -0
  21. data/lib/cuprum/rails/commands/destroy_one.rb +43 -0
  22. data/lib/cuprum/rails/commands/find_many.rb +60 -0
  23. data/lib/cuprum/rails/commands/find_matching.rb +121 -0
  24. data/lib/cuprum/rails/commands/find_one.rb +50 -0
  25. data/lib/cuprum/rails/commands/insert_one.rb +41 -0
  26. data/lib/cuprum/rails/commands/update_one.rb +49 -0
  27. data/lib/cuprum/rails/commands/validate_one.rb +68 -0
  28. data/lib/cuprum/rails/commands.rb +18 -0
  29. data/lib/cuprum/rails/controller.rb +50 -0
  30. data/lib/cuprum/rails/controller_action.rb +121 -0
  31. data/lib/cuprum/rails/controllers/class_methods/actions.rb +57 -0
  32. data/lib/cuprum/rails/controllers/class_methods/configuration.rb +64 -0
  33. data/lib/cuprum/rails/controllers/class_methods/validations.rb +30 -0
  34. data/lib/cuprum/rails/controllers/class_methods.rb +15 -0
  35. data/lib/cuprum/rails/controllers/configuration.rb +53 -0
  36. data/lib/cuprum/rails/controllers.rb +10 -0
  37. data/lib/cuprum/rails/errors/missing_parameters.rb +33 -0
  38. data/lib/cuprum/rails/errors/missing_primary_key.rb +46 -0
  39. data/lib/cuprum/rails/errors/undefined_permitted_attributes.rb +34 -0
  40. data/lib/cuprum/rails/errors.rb +8 -0
  41. data/lib/cuprum/rails/map_errors.rb +44 -0
  42. data/lib/cuprum/rails/query.rb +77 -0
  43. data/lib/cuprum/rails/query_builder.rb +78 -0
  44. data/lib/cuprum/rails/repository.rb +44 -0
  45. data/lib/cuprum/rails/request.rb +105 -0
  46. data/lib/cuprum/rails/resource.rb +145 -0
  47. data/lib/cuprum/rails/responders/actions.rb +73 -0
  48. data/lib/cuprum/rails/responders/html/plural_resource.rb +62 -0
  49. data/lib/cuprum/rails/responders/html/singular_resource.rb +59 -0
  50. data/lib/cuprum/rails/responders/html.rb +11 -0
  51. data/lib/cuprum/rails/responders/html_responder.rb +129 -0
  52. data/lib/cuprum/rails/responders/json/resource.rb +60 -0
  53. data/lib/cuprum/rails/responders/json.rb +10 -0
  54. data/lib/cuprum/rails/responders/json_responder.rb +122 -0
  55. data/lib/cuprum/rails/responders/matching.rb +145 -0
  56. data/lib/cuprum/rails/responders/serialization.rb +36 -0
  57. data/lib/cuprum/rails/responders.rb +15 -0
  58. data/lib/cuprum/rails/responses/html/redirect_response.rb +29 -0
  59. data/lib/cuprum/rails/responses/html/render_response.rb +52 -0
  60. data/lib/cuprum/rails/responses/html.rb +11 -0
  61. data/lib/cuprum/rails/responses/json_response.rb +29 -0
  62. data/lib/cuprum/rails/responses.rb +11 -0
  63. data/lib/cuprum/rails/routes.rb +166 -0
  64. data/lib/cuprum/rails/routing/plural_routes.rb +26 -0
  65. data/lib/cuprum/rails/routing/singular_routes.rb +24 -0
  66. data/lib/cuprum/rails/routing.rb +11 -0
  67. data/lib/cuprum/rails/rspec/command_contract.rb +460 -0
  68. data/lib/cuprum/rails/rspec/define_route_contract.rb +84 -0
  69. data/lib/cuprum/rails/rspec.rb +8 -0
  70. data/lib/cuprum/rails/serializers/json/active_record_serializer.rb +24 -0
  71. data/lib/cuprum/rails/serializers/json/array_serializer.rb +40 -0
  72. data/lib/cuprum/rails/serializers/json/attributes_serializer.rb +217 -0
  73. data/lib/cuprum/rails/serializers/json/error_serializer.rb +24 -0
  74. data/lib/cuprum/rails/serializers/json/hash_serializer.rb +44 -0
  75. data/lib/cuprum/rails/serializers/json/identity_serializer.rb +21 -0
  76. data/lib/cuprum/rails/serializers/json/serializer.rb +66 -0
  77. data/lib/cuprum/rails/serializers/json.rb +40 -0
  78. data/lib/cuprum/rails/serializers.rb +10 -0
  79. data/lib/cuprum/rails/version.rb +59 -0
  80. data/lib/cuprum/rails.rb +31 -0
  81. metadata +286 -0
@@ -0,0 +1,460 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/rails/rspec'
4
+
5
+ require 'support/book'
6
+ require 'support/tome'
7
+
8
+ module Cuprum::Rails::RSpec
9
+ # Contract validating the behavior of a Rails command implementation.
10
+ COMMAND_CONTRACT = lambda do
11
+ describe '.subclass' do
12
+ let(:subclass) { described_class.subclass }
13
+ let(:constructor_options) do
14
+ {
15
+ record_class: Book,
16
+ optional_key: 'optional value'
17
+ }
18
+ end
19
+
20
+ it 'should define the class method' do
21
+ expect(described_class)
22
+ .to respond_to(:subclass)
23
+ .with(0).arguments
24
+ .and_any_keywords
25
+ end
26
+
27
+ it { expect(subclass).to be_a Class }
28
+
29
+ it { expect(subclass).to be < described_class }
30
+
31
+ it 'should define the constructor' do
32
+ expect(subclass)
33
+ .to respond_to(:new)
34
+ .with(0).arguments
35
+ .and_any_keywords
36
+ end
37
+
38
+ it 'should return the record class' do
39
+ expect(subclass.new(**constructor_options).record_class)
40
+ .to be record_class
41
+ end
42
+
43
+ it 'should return the options' do
44
+ expect(subclass.new(**constructor_options).options)
45
+ .to be == { optional_key: 'optional value' }
46
+ end
47
+
48
+ describe 'with options' do
49
+ let(:default_options) do
50
+ {
51
+ record_class: Book,
52
+ custom_key: 'custom value'
53
+ }
54
+ end
55
+ let(:constructor_options) do
56
+ {
57
+ optional_key: 'optional value'
58
+ }
59
+ end
60
+ let(:subclass) { described_class.subclass(**default_options) }
61
+
62
+ it { expect(subclass).to be_a Class }
63
+
64
+ it { expect(subclass).to be < described_class }
65
+
66
+ it 'should define the constructor' do
67
+ expect(subclass)
68
+ .to respond_to(:new)
69
+ .with(0).arguments
70
+ .and_any_keywords
71
+ end
72
+
73
+ it 'should return the record class' do
74
+ expect(subclass.new(**constructor_options).record_class)
75
+ .to be record_class
76
+ end
77
+
78
+ it 'should return the options' do
79
+ expect(subclass.new(**constructor_options).options)
80
+ .to be == {
81
+ custom_key: 'custom value',
82
+ optional_key: 'optional value'
83
+ }
84
+ end
85
+ end
86
+ end
87
+
88
+ describe '#collection_name' do
89
+ let(:expected) { record_class.name.underscore.pluralize }
90
+
91
+ include_examples 'should define reader',
92
+ :collection_name,
93
+ -> { be == expected }
94
+
95
+ context 'when initialized with collection_name: string' do
96
+ let(:collection_name) { 'books' }
97
+ let(:constructor_options) do
98
+ super().merge(collection_name: collection_name)
99
+ end
100
+
101
+ it { expect(command.collection_name).to be == collection_name }
102
+ end
103
+
104
+ context 'when initialized with collection_name: symbol' do
105
+ let(:collection_name) { :books }
106
+ let(:constructor_options) do
107
+ super().merge(collection_name: collection_name)
108
+ end
109
+
110
+ it { expect(command.collection_name).to be == collection_name.to_s }
111
+ end
112
+ end
113
+
114
+ describe '#member_name' do
115
+ def tools
116
+ SleepingKingStudios::Tools::Toolbelt.instance
117
+ end
118
+
119
+ include_examples 'should have reader',
120
+ :member_name,
121
+ -> { record_class.name.underscore }
122
+
123
+ context 'when initialized with collection_name: value' do
124
+ let(:collection_name) { :books }
125
+
126
+ it 'should return the singular collection name' do
127
+ expect(command.member_name)
128
+ .to be == tools.str.singularize(collection_name.to_s)
129
+ end
130
+ end
131
+
132
+ context 'when initialized with member_name: string' do
133
+ let(:member_name) { 'tome' }
134
+ let(:constructor_options) { super().merge(member_name: member_name) }
135
+
136
+ it 'should return the singular collection name' do
137
+ expect(command.member_name).to be member_name
138
+ end
139
+ end
140
+
141
+ context 'when initialized with member_name: symbol' do
142
+ let(:member_name) { :tome }
143
+ let(:constructor_options) { super().merge(member_name: member_name) }
144
+
145
+ it 'should return the singular collection name' do
146
+ expect(command.member_name).to be == member_name.to_s
147
+ end
148
+ end
149
+ end
150
+
151
+ describe '#options' do
152
+ let(:expected_options) do
153
+ defined?(super()) ? super() : constructor_options
154
+ end
155
+
156
+ include_examples 'should define reader',
157
+ :options,
158
+ -> { be == expected_options }
159
+
160
+ context 'when initialized with options' do
161
+ let(:constructor_options) { super().merge({ key: 'value' }) }
162
+ let(:expected_options) { super().merge({ key: 'value' }) }
163
+
164
+ it { expect(command.options).to be == expected_options }
165
+ end
166
+ end
167
+
168
+ describe '#primary_key_name' do
169
+ include_examples 'should define reader', :primary_key_name, :id
170
+
171
+ context 'with a record class with custom primary key' do
172
+ let(:record_class) { Tome }
173
+
174
+ include_examples 'should define reader', :primary_key_name, :uuid
175
+ end
176
+ end
177
+
178
+ describe '#primary_key_type' do
179
+ include_examples 'should define reader', :primary_key_type, Integer
180
+
181
+ context 'with a record class with custom primary key' do
182
+ let(:record_class) { Tome }
183
+
184
+ include_examples 'should define reader', :primary_key_type, String
185
+ end
186
+ end
187
+
188
+ describe '#record_class' do
189
+ include_examples 'should define reader',
190
+ :record_class,
191
+ -> { record_class }
192
+ end
193
+
194
+ describe '#validate_entity' do
195
+ let(:expected_error) do
196
+ type = record_class
197
+ contract = Stannum::Contracts::ParametersContract.new do
198
+ keyword :entity, type
199
+ end
200
+ errors = contract.errors_for(
201
+ {
202
+ arguments: [],
203
+ block: nil,
204
+ keywords: { entity: nil }
205
+ }
206
+ )
207
+
208
+ Cuprum::Collections::Errors::InvalidParameters.new(
209
+ command: command,
210
+ errors: errors
211
+ )
212
+ end
213
+
214
+ it 'should define the private method' do
215
+ expect(command)
216
+ .to respond_to(:validate_entity, true)
217
+ .with(1).argument
218
+ end
219
+
220
+ describe 'with nil' do
221
+ it 'should return a failing result' do
222
+ expect(command.send(:validate_entity, nil))
223
+ .to be_a_failing_result
224
+ .with_error(expected_error)
225
+ end
226
+ end
227
+
228
+ describe 'with an Object' do
229
+ it 'should return a failing result' do
230
+ expect(command.send(:validate_entity, Object.new.freeze))
231
+ .to be_a_failing_result
232
+ .with_error(expected_error)
233
+ end
234
+ end
235
+
236
+ describe 'with an invalid record instance' do
237
+ it 'should return a failing result' do
238
+ expect(command.send(:validate_entity, Tome.new))
239
+ .to be_a_failing_result
240
+ .with_error(expected_error)
241
+ end
242
+ end
243
+
244
+ describe 'with a valid record instance' do
245
+ it 'should not return a result' do
246
+ expect(command.send(:validate_entity, Book.new))
247
+ .not_to be_a_result
248
+ end
249
+ end
250
+ end
251
+
252
+ describe '#validate_primary_key' do
253
+ let(:primary_key_type) { Integer }
254
+ let(:expected_error) do
255
+ type = primary_key_type
256
+ contract = Stannum::Contracts::ParametersContract.new do
257
+ keyword :primary_key, type
258
+ end
259
+ errors = contract.errors_for(
260
+ {
261
+ arguments: [],
262
+ block: nil,
263
+ keywords: { primary_key: nil }
264
+ }
265
+ )
266
+
267
+ Cuprum::Collections::Errors::InvalidParameters.new(
268
+ command: command,
269
+ errors: errors
270
+ )
271
+ end
272
+
273
+ it 'should define the private method' do
274
+ expect(command)
275
+ .to respond_to(:validate_primary_key, true)
276
+ .with(1).argument
277
+ end
278
+
279
+ describe 'with nil' do
280
+ it 'should return a failing result' do
281
+ expect(command.send(:validate_primary_key, nil))
282
+ .to be_a_failing_result
283
+ .with_error(expected_error)
284
+ end
285
+ end
286
+
287
+ describe 'with an Object' do
288
+ it 'should return a failing result' do
289
+ expect(command.send(:validate_primary_key, Object.new.freeze))
290
+ .to be_a_failing_result
291
+ .with_error(expected_error)
292
+ end
293
+ end
294
+
295
+ describe 'with a String' do
296
+ it 'should return a failing result' do
297
+ expect(command.send(:validate_primary_key, '12345'))
298
+ .to be_a_failing_result
299
+ .with_error(expected_error)
300
+ end
301
+ end
302
+
303
+ describe 'with an Integer' do
304
+ it 'should not return a result' do
305
+ expect(command.send(:validate_primary_key, 12_345)).not_to be_a_result
306
+ end
307
+ end
308
+
309
+ context 'with a record class with custom primary key' do
310
+ let(:record_class) { Tome }
311
+ let(:primary_key_type) { String }
312
+
313
+ describe 'with an Integer' do
314
+ it 'should return a failing result' do
315
+ expect(command.send(:validate_primary_key, 12_345))
316
+ .to be_a_failing_result
317
+ .with_error(expected_error)
318
+ end
319
+ end
320
+
321
+ describe 'with a String' do
322
+ it 'should not return a result' do
323
+ expect(command.send(:validate_primary_key, '12345'))
324
+ .not_to be_a_result
325
+ end
326
+ end
327
+ end
328
+ end
329
+
330
+ describe '#validate_primary_keys' do
331
+ let(:primary_keys) { nil }
332
+ let(:primary_key_type) { Integer }
333
+ let(:expected_error) do
334
+ type = primary_key_type
335
+ contract = Stannum::Contracts::ParametersContract.new do
336
+ keyword :primary_keys,
337
+ Stannum::Constraints::Types::ArrayType.new(item_type: type)
338
+ end
339
+ errors = contract.errors_for(
340
+ {
341
+ arguments: [],
342
+ block: nil,
343
+ keywords: { primary_keys: primary_keys }
344
+ }
345
+ )
346
+
347
+ Cuprum::Collections::Errors::InvalidParameters.new(
348
+ command: command,
349
+ errors: errors
350
+ )
351
+ end
352
+
353
+ it 'should define the private method' do
354
+ expect(command)
355
+ .to respond_to(:validate_primary_keys, true)
356
+ .with(1).argument
357
+ end
358
+
359
+ describe 'with nil' do
360
+ it 'should return a failing result' do
361
+ expect(command.send(:validate_primary_keys, nil))
362
+ .to be_a_failing_result
363
+ .with_error(expected_error)
364
+ end
365
+ end
366
+
367
+ describe 'with an Object' do
368
+ it 'should return a failing result' do
369
+ expect(command.send(:validate_primary_keys, Object.new.freeze))
370
+ .to be_a_failing_result
371
+ .with_error(expected_error)
372
+ end
373
+ end
374
+
375
+ describe 'with a String' do
376
+ it 'should return a failing result' do
377
+ expect(command.send(:validate_primary_keys, '12345'))
378
+ .to be_a_failing_result
379
+ .with_error(expected_error)
380
+ end
381
+ end
382
+
383
+ describe 'with an Integer' do
384
+ it 'should return a failing result' do
385
+ expect(command.send(:validate_primary_keys, 12_345))
386
+ .to be_a_failing_result
387
+ .with_error(expected_error)
388
+ end
389
+ end
390
+
391
+ describe 'with an empty Array' do
392
+ it 'should not return a result' do
393
+ expect(command.send(:validate_primary_keys, []))
394
+ .not_to be_a_result
395
+ end
396
+ end
397
+
398
+ describe 'with an Array with nil values' do
399
+ let(:primary_keys) { Array.new(3, nil) }
400
+
401
+ it 'should return a failing result' do
402
+ expect(command.send(:validate_primary_keys, primary_keys))
403
+ .to be_a_failing_result
404
+ .with_error(expected_error)
405
+ end
406
+ end
407
+
408
+ describe 'with an Array with Object values' do
409
+ let(:primary_keys) { Array.new(3) { Object.new.freeze } }
410
+
411
+ it 'should return a failing result' do
412
+ expect(command.send(:validate_primary_keys, primary_keys))
413
+ .to be_a_failing_result
414
+ .with_error(expected_error)
415
+ end
416
+ end
417
+
418
+ describe 'with an Array with String values' do
419
+ let(:primary_keys) { %w[ichi ni san] }
420
+
421
+ it 'should return a failing result' do
422
+ expect(command.send(:validate_primary_keys, primary_keys))
423
+ .to be_a_failing_result
424
+ .with_error(expected_error)
425
+ end
426
+ end
427
+
428
+ describe 'with an Array with Integer values' do
429
+ it 'should not return a result' do
430
+ expect(command.send(:validate_primary_keys, [0, 1, 2]))
431
+ .not_to be_a_result
432
+ end
433
+ end
434
+
435
+ context 'with a record class with custom primary key' do
436
+ let(:record_class) { Tome }
437
+ let(:primary_key_type) { String }
438
+
439
+ describe 'with an Array with String values' do
440
+ let(:primary_keys) { %w[ichi ni san] }
441
+
442
+ it 'should not return a result' do
443
+ expect(command.send(:validate_primary_keys, primary_keys))
444
+ .not_to be_a_result
445
+ end
446
+ end
447
+
448
+ describe 'with an Array with Integer values' do
449
+ let(:primary_keys) { [0, 1, 2] }
450
+
451
+ it 'should return a failing result' do
452
+ expect(command.send(:validate_primary_keys, primary_keys))
453
+ .to be_a_failing_result
454
+ .with_error(expected_error)
455
+ end
456
+ end
457
+ end
458
+ end
459
+ end
460
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/rails/rspec'
4
+
5
+ module Cuprum::Rails::RSpec
6
+ # Contract asserting that a Routes object defines the given route.
7
+ DEFINE_ROUTE_CONTRACT = lambda \
8
+ do |action_name:, path:, member_action: false, wildcards: {}|
9
+ describe "##{action_name}_path" do
10
+ let(:method_name) { "#{action_name}_path" }
11
+ let(:expected) do
12
+ next path unless member_action
13
+
14
+ primary_key_name = entity.class.primary_key
15
+ primary_key_value = entity[primary_key_name]
16
+
17
+ path.sub(':id', primary_key_value.to_s)
18
+ end
19
+
20
+ define_method(:route_helper) do
21
+ scoped = wildcards.empty? ? routes : routes.with_wildcards(wildcards)
22
+
23
+ if member_action
24
+ scoped.send(method_name, entity)
25
+ else
26
+ scoped.send(method_name)
27
+ end
28
+ end
29
+
30
+ it 'should define the helper method' do
31
+ if member_action
32
+ expect(routes).to respond_to(method_name).with(1).argument
33
+ else
34
+ expect(routes).to respond_to(method_name).with(0).arguments
35
+ end
36
+ end
37
+
38
+ it 'should return the route path' do
39
+ expect(route_helper).to be == expected
40
+ end
41
+
42
+ if member_action
43
+ describe 'when the entity is nil' do
44
+ let(:entity) { nil }
45
+ let(:error_message) { 'missing wildcard :id' }
46
+
47
+ it 'should raise an exception' do
48
+ expect { route_helper }
49
+ .to raise_error(
50
+ described_class::MissingWildcardError,
51
+ error_message
52
+ )
53
+ end
54
+ end
55
+ end
56
+
57
+ wildcards.each do |key, _|
58
+ wildcard = key.to_s.end_with?('_id') ? key.intern : :"#{key}_id"
59
+
60
+ describe "when the #{wildcard.inspect} wildcard is undefined" do
61
+ let(:error_message) { "missing wildcard #{wildcard.inspect}" }
62
+
63
+ define_method(:route_helper) do
64
+ scoped = routes.with_wildcards(wildcards.except(key))
65
+
66
+ if member_action
67
+ scoped.send(method_name, entity)
68
+ else
69
+ scoped.send(method_name)
70
+ end
71
+ end
72
+
73
+ it 'should raise an exception' do
74
+ expect { route_helper }
75
+ .to raise_error(
76
+ described_class::MissingWildcardError,
77
+ error_message
78
+ )
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/rails'
4
+
5
+ module Cuprum::Rails
6
+ # Namespace for RSpec contracts, which validate collection implementations.
7
+ module RSpec; end
8
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/rails/serializers/json'
4
+ require 'cuprum/rails/serializers/json/serializer'
5
+
6
+ module Cuprum::Rails::Serializers::Json
7
+ # Converts ActiveRecord record to JSON using the #as_json method.
8
+ class ActiveRecordSerializer < Cuprum::Rails::Serializers::Json::Serializer
9
+ # Converts the ActiveRecord record to JSON.
10
+ #
11
+ # Calls and returns the #as_json method of the record.
12
+ #
13
+ # @param record [ActiveRecord::Base] The record to convert to JSON.
14
+ #
15
+ # @return [Hash] a JSON-compatible representation of the record.
16
+ def call(record, **_)
17
+ unless record.is_a?(ActiveRecord::Base)
18
+ raise ArgumentError, 'object must be an ActiveRecord record'
19
+ end
20
+
21
+ record.as_json
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/rails/serializers/json'
4
+ require 'cuprum/rails/serializers/json/serializer'
5
+
6
+ module Cuprum::Rails::Serializers::Json
7
+ # Converts Array data structures to JSON based on configured serializers.
8
+ class ArraySerializer < Cuprum::Rails::Serializers::Json::Serializer
9
+ # Converts the array to JSON using the given serializers.
10
+ #
11
+ # First, #call finds the best serializer from the :serializers Hash for each
12
+ # item in the Array. This is done by walking up the object class's ancestors
13
+ # to find the closest ancestor which is a key in the :serializers Hash. The
14
+ # corresponding value is then called with the object, and the results are
15
+ # combined into a new Array and returned.
16
+ #
17
+ # @param array [Array] The array to convert to JSON.
18
+ # @param serializers [Hash<Class, #call>] The serializers for different
19
+ # object types.
20
+ #
21
+ # @return [Array] a JSON-compatible representation of the array.
22
+ #
23
+ # @raise UndefinedSerializerError if there is no matching serializer for
24
+ # any of the items in the array.
25
+ def call(array, serializers:)
26
+ raise ArgumentError, 'object must be an Array' unless array.is_a?(Array)
27
+
28
+ array.map { |item| super(item, serializers: serializers) }
29
+ end
30
+
31
+ private
32
+
33
+ def handle_recursion!(_object)
34
+ # Call serializes the items, not the array. Because the context changes,
35
+ # we don't need to check for recursion (unless the Array contains itself,
36
+ # in which case here there be dragons).
37
+ yield
38
+ end
39
+ end
40
+ end