cuprum-rails 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +145 -0
  3. data/DEVELOPMENT.md +20 -0
  4. data/README.md +356 -63
  5. data/lib/cuprum/rails/action.rb +32 -16
  6. data/lib/cuprum/rails/actions/create.rb +62 -15
  7. data/lib/cuprum/rails/actions/destroy.rb +23 -7
  8. data/lib/cuprum/rails/actions/edit.rb +23 -7
  9. data/lib/cuprum/rails/actions/index.rb +30 -10
  10. data/lib/cuprum/rails/actions/middleware/associations/cache.rb +112 -0
  11. data/lib/cuprum/rails/actions/middleware/associations/find.rb +23 -0
  12. data/lib/cuprum/rails/actions/middleware/associations/parent.rb +70 -0
  13. data/lib/cuprum/rails/actions/middleware/associations/query.rb +140 -0
  14. data/lib/cuprum/rails/actions/middleware/associations.rb +12 -0
  15. data/lib/cuprum/rails/actions/middleware/log_request.rb +126 -0
  16. data/lib/cuprum/rails/actions/middleware/log_result.rb +51 -0
  17. data/lib/cuprum/rails/actions/middleware/resources/find.rb +44 -0
  18. data/lib/cuprum/rails/actions/middleware/resources/query.rb +91 -0
  19. data/lib/cuprum/rails/actions/middleware/resources.rb +11 -0
  20. data/lib/cuprum/rails/actions/middleware.rb +13 -0
  21. data/lib/cuprum/rails/actions/new.rb +16 -4
  22. data/lib/cuprum/rails/actions/parameter_validation.rb +60 -0
  23. data/lib/cuprum/rails/actions/resource_action.rb +119 -42
  24. data/lib/cuprum/rails/actions/show.rb +23 -7
  25. data/lib/cuprum/rails/actions/update.rb +70 -22
  26. data/lib/cuprum/rails/actions.rb +11 -7
  27. data/lib/cuprum/rails/collection.rb +27 -47
  28. data/lib/cuprum/rails/command.rb +3 -1
  29. data/lib/cuprum/rails/commands/destroy_one.rb +10 -6
  30. data/lib/cuprum/rails/commands/find_many.rb +8 -1
  31. data/lib/cuprum/rails/commands/find_matching.rb +1 -1
  32. data/lib/cuprum/rails/commands/find_one.rb +8 -0
  33. data/lib/cuprum/rails/commands/insert_one.rb +17 -6
  34. data/lib/cuprum/rails/commands/update_one.rb +16 -5
  35. data/lib/cuprum/rails/constraints/parameters_contract.rb +14 -0
  36. data/lib/cuprum/rails/constraints.rb +10 -0
  37. data/lib/cuprum/rails/controller.rb +12 -2
  38. data/lib/cuprum/rails/controllers/action.rb +100 -0
  39. data/lib/cuprum/rails/controllers/class_methods/actions.rb +33 -7
  40. data/lib/cuprum/rails/controllers/class_methods/configuration.rb +36 -0
  41. data/lib/cuprum/rails/controllers/class_methods/middleware.rb +88 -0
  42. data/lib/cuprum/rails/controllers/class_methods/validations.rb +2 -2
  43. data/lib/cuprum/rails/controllers/configuration.rb +41 -1
  44. data/lib/cuprum/rails/controllers/middleware.rb +59 -0
  45. data/lib/cuprum/rails/controllers.rb +2 -0
  46. data/lib/cuprum/rails/errors/invalid_parameters.rb +55 -0
  47. data/lib/cuprum/rails/errors/invalid_statement.rb +11 -0
  48. data/lib/cuprum/rails/errors/missing_parameter.rb +42 -0
  49. data/lib/cuprum/rails/errors/resource_error.rb +46 -0
  50. data/lib/cuprum/rails/errors.rb +6 -1
  51. data/lib/cuprum/rails/map_errors.rb +29 -1
  52. data/lib/cuprum/rails/query.rb +1 -1
  53. data/lib/cuprum/rails/repository.rb +12 -25
  54. data/lib/cuprum/rails/request.rb +149 -60
  55. data/lib/cuprum/rails/resource.rb +119 -85
  56. data/lib/cuprum/rails/responders/base_responder.rb +78 -0
  57. data/lib/cuprum/rails/responders/html/plural_resource.rb +9 -39
  58. data/lib/cuprum/rails/responders/html/rendering.rb +81 -0
  59. data/lib/cuprum/rails/responders/html/resource.rb +107 -0
  60. data/lib/cuprum/rails/responders/html/singular_resource.rb +9 -38
  61. data/lib/cuprum/rails/responders/html.rb +2 -0
  62. data/lib/cuprum/rails/responders/html_responder.rb +8 -52
  63. data/lib/cuprum/rails/responders/json/resource.rb +3 -3
  64. data/lib/cuprum/rails/responders/json_responder.rb +31 -16
  65. data/lib/cuprum/rails/responders/matching.rb +29 -27
  66. data/lib/cuprum/rails/responders/serialization.rb +11 -9
  67. data/lib/cuprum/rails/responders.rb +1 -0
  68. data/lib/cuprum/rails/responses/head_response.rb +24 -0
  69. data/lib/cuprum/rails/responses/html/redirect_back_response.rb +55 -0
  70. data/lib/cuprum/rails/responses/html/redirect_response.rb +19 -4
  71. data/lib/cuprum/rails/responses/html/render_response.rb +17 -5
  72. data/lib/cuprum/rails/responses/html.rb +6 -2
  73. data/lib/cuprum/rails/responses.rb +1 -0
  74. data/lib/cuprum/rails/result.rb +36 -0
  75. data/lib/cuprum/rails/routes.rb +36 -23
  76. data/lib/cuprum/rails/rspec/contract_helpers.rb +57 -0
  77. data/lib/cuprum/rails/rspec/contracts/action_contracts.rb +754 -0
  78. data/lib/cuprum/rails/rspec/contracts/actions/create_contracts.rb +289 -0
  79. data/lib/cuprum/rails/rspec/contracts/actions/destroy_contracts.rb +164 -0
  80. data/lib/cuprum/rails/rspec/contracts/actions/edit_contracts.rb +73 -0
  81. data/lib/cuprum/rails/rspec/contracts/actions/index_contracts.rb +108 -0
  82. data/lib/cuprum/rails/rspec/contracts/actions/new_contracts.rb +111 -0
  83. data/lib/cuprum/rails/rspec/contracts/actions/show_contracts.rb +72 -0
  84. data/lib/cuprum/rails/rspec/contracts/actions/update_contracts.rb +263 -0
  85. data/lib/cuprum/rails/rspec/contracts/actions.rb +8 -0
  86. data/lib/cuprum/rails/rspec/contracts/command_contracts.rb +479 -0
  87. data/lib/cuprum/rails/rspec/contracts/responder_contracts.rb +232 -0
  88. data/lib/cuprum/rails/rspec/contracts/routes_contracts.rb +363 -0
  89. data/lib/cuprum/rails/rspec/contracts/serializers_contracts.rb +70 -0
  90. data/lib/cuprum/rails/rspec/contracts.rb +8 -0
  91. data/lib/cuprum/rails/rspec/matchers/be_a_result_matcher.rb +64 -0
  92. data/lib/cuprum/rails/rspec/matchers.rb +41 -0
  93. data/lib/cuprum/rails/serializers/base_serializer.rb +60 -0
  94. data/lib/cuprum/rails/serializers/context.rb +84 -0
  95. data/lib/cuprum/rails/serializers/json/active_record_serializer.rb +2 -2
  96. data/lib/cuprum/rails/serializers/json/array_serializer.rb +9 -8
  97. data/lib/cuprum/rails/serializers/json/attributes_serializer.rb +95 -172
  98. data/lib/cuprum/rails/serializers/json/error_serializer.rb +2 -2
  99. data/lib/cuprum/rails/serializers/json/hash_serializer.rb +9 -8
  100. data/lib/cuprum/rails/serializers/json/identity_serializer.rb +3 -3
  101. data/lib/cuprum/rails/serializers/json/properties_serializer.rb +252 -0
  102. data/lib/cuprum/rails/serializers/json.rb +2 -1
  103. data/lib/cuprum/rails/serializers.rb +3 -1
  104. data/lib/cuprum/rails/version.rb +1 -1
  105. data/lib/cuprum/rails.rb +19 -16
  106. metadata +73 -131
  107. data/lib/cuprum/rails/controller_action.rb +0 -121
  108. data/lib/cuprum/rails/errors/missing_parameters.rb +0 -33
  109. data/lib/cuprum/rails/errors/missing_primary_key.rb +0 -46
  110. data/lib/cuprum/rails/errors/undefined_permitted_attributes.rb +0 -34
  111. data/lib/cuprum/rails/rspec/command_contract.rb +0 -460
  112. data/lib/cuprum/rails/rspec/define_route_contract.rb +0 -84
  113. data/lib/cuprum/rails/serializers/json/serializer.rb +0 -66
@@ -0,0 +1,479 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/sleeping_king_studios/contract'
4
+
5
+ require 'cuprum/rails/rspec/contracts'
6
+
7
+ require 'support/book'
8
+ require 'support/tome'
9
+
10
+ module Cuprum::Rails::RSpec::Contracts
11
+ # Namespace for RSpec command contracts.
12
+ module CommandContracts
13
+ # Contract validating the behavior of a Rails command implementation.
14
+ module ShouldBeARailsCommandContract
15
+ extend RSpec::SleepingKingStudios::Contract
16
+
17
+ # @!method apply(example_group)
18
+ # Adds the contract to the example group.
19
+ #
20
+ # @param example_group [RSpec::Core::ExampleGroup] the example group to
21
+ # which the contract is applied.
22
+ contract do
23
+ describe '.subclass' do
24
+ let(:subclass) { described_class.subclass }
25
+ let(:constructor_options) do
26
+ {
27
+ record_class: Book,
28
+ optional_key: 'optional value'
29
+ }
30
+ end
31
+
32
+ it 'should define the class method' do
33
+ expect(described_class)
34
+ .to respond_to(:subclass)
35
+ .with(0).arguments
36
+ .and_any_keywords
37
+ end
38
+
39
+ it { expect(subclass).to be_a Class }
40
+
41
+ it { expect(subclass).to be < described_class }
42
+
43
+ it 'should define the constructor' do
44
+ expect(subclass)
45
+ .to respond_to(:new)
46
+ .with(0).arguments
47
+ .and_any_keywords
48
+ end
49
+
50
+ it 'should return the record class' do
51
+ expect(subclass.new(**constructor_options).record_class)
52
+ .to be record_class
53
+ end
54
+
55
+ it 'should return the options' do
56
+ expect(subclass.new(**constructor_options).options)
57
+ .to be == { optional_key: 'optional value' }
58
+ end
59
+
60
+ describe 'with options' do
61
+ let(:default_options) do
62
+ {
63
+ record_class: Book,
64
+ custom_key: 'custom value'
65
+ }
66
+ end
67
+ let(:constructor_options) do
68
+ {
69
+ optional_key: 'optional value'
70
+ }
71
+ end
72
+ let(:subclass) { described_class.subclass(**default_options) }
73
+
74
+ it { expect(subclass).to be_a Class }
75
+
76
+ it { expect(subclass).to be < described_class }
77
+
78
+ it 'should define the constructor' do
79
+ expect(subclass)
80
+ .to respond_to(:new)
81
+ .with(0).arguments
82
+ .and_any_keywords
83
+ end
84
+
85
+ it 'should return the record class' do
86
+ expect(subclass.new(**constructor_options).record_class)
87
+ .to be record_class
88
+ end
89
+
90
+ it 'should return the options' do
91
+ expect(subclass.new(**constructor_options).options)
92
+ .to be == {
93
+ custom_key: 'custom value',
94
+ optional_key: 'optional value'
95
+ }
96
+ end
97
+ end
98
+ end
99
+
100
+ describe '#collection_name' do
101
+ let(:expected) { record_class.name.underscore.pluralize }
102
+
103
+ include_examples 'should define reader',
104
+ :collection_name,
105
+ -> { be == expected }
106
+
107
+ context 'when initialized with collection_name: string' do
108
+ let(:collection_name) { 'books' }
109
+ let(:constructor_options) do
110
+ super().merge(collection_name: collection_name)
111
+ end
112
+
113
+ it { expect(command.collection_name).to be == collection_name }
114
+ end
115
+
116
+ context 'when initialized with collection_name: symbol' do
117
+ let(:collection_name) { :books }
118
+ let(:constructor_options) do
119
+ super().merge(collection_name: collection_name)
120
+ end
121
+
122
+ it { expect(command.collection_name).to be == collection_name.to_s }
123
+ end
124
+ end
125
+
126
+ describe '#member_name' do
127
+ def tools
128
+ SleepingKingStudios::Tools::Toolbelt.instance
129
+ end
130
+
131
+ include_examples 'should have reader',
132
+ :member_name,
133
+ -> { record_class.name.underscore }
134
+
135
+ context 'when initialized with collection_name: value' do
136
+ let(:collection_name) { :books }
137
+
138
+ it 'should return the singular collection name' do
139
+ expect(command.member_name)
140
+ .to be == tools.str.singularize(collection_name.to_s)
141
+ end
142
+ end
143
+
144
+ context 'when initialized with member_name: string' do
145
+ let(:member_name) { 'tome' }
146
+ let(:constructor_options) do
147
+ super().merge(member_name: member_name)
148
+ end
149
+
150
+ it 'should return the singular collection name' do
151
+ expect(command.member_name).to be member_name
152
+ end
153
+ end
154
+
155
+ context 'when initialized with member_name: symbol' do
156
+ let(:member_name) { :tome }
157
+ let(:constructor_options) do
158
+ super().merge(member_name: member_name)
159
+ end
160
+
161
+ it 'should return the singular collection name' do
162
+ expect(command.member_name).to be == member_name.to_s
163
+ end
164
+ end
165
+ end
166
+
167
+ describe '#options' do
168
+ let(:expected_options) do
169
+ defined?(super()) ? super() : constructor_options
170
+ end
171
+
172
+ include_examples 'should define reader',
173
+ :options,
174
+ -> { be == expected_options }
175
+
176
+ context 'when initialized with options' do
177
+ let(:constructor_options) { super().merge({ key: 'value' }) }
178
+ let(:expected_options) { super().merge({ key: 'value' }) }
179
+
180
+ it { expect(command.options).to be == expected_options }
181
+ end
182
+ end
183
+
184
+ describe '#primary_key_name' do
185
+ include_examples 'should define reader', :primary_key_name, 'id'
186
+
187
+ context 'with a record class with custom primary key' do
188
+ let(:record_class) { Tome }
189
+
190
+ include_examples 'should define reader', :primary_key_name, 'uuid'
191
+ end
192
+ end
193
+
194
+ describe '#primary_key_type' do
195
+ include_examples 'should define reader', :primary_key_type, Integer
196
+
197
+ context 'with a record class with custom primary key' do
198
+ let(:record_class) { Tome }
199
+
200
+ include_examples 'should define reader', :primary_key_type, String
201
+ end
202
+ end
203
+
204
+ describe '#record_class' do
205
+ include_examples 'should define reader',
206
+ :record_class,
207
+ -> { record_class }
208
+ end
209
+
210
+ describe '#validate_entity' do
211
+ let(:expected_error) do
212
+ type = record_class
213
+ contract = Stannum::Contracts::ParametersContract.new do
214
+ keyword :entity, type
215
+ end
216
+ errors = contract.errors_for(
217
+ {
218
+ arguments: [],
219
+ block: nil,
220
+ keywords: { entity: nil }
221
+ }
222
+ )
223
+
224
+ Cuprum::Collections::Errors::InvalidParameters.new(
225
+ command: command,
226
+ errors: errors
227
+ )
228
+ end
229
+
230
+ it 'should define the private method' do
231
+ expect(command)
232
+ .to respond_to(:validate_entity, true)
233
+ .with(1).argument
234
+ end
235
+
236
+ describe 'with nil' do
237
+ it 'should return a failing result' do
238
+ expect(command.send(:validate_entity, nil))
239
+ .to be_a_failing_result
240
+ .with_error(expected_error)
241
+ end
242
+ end
243
+
244
+ describe 'with an Object' do
245
+ it 'should return a failing result' do
246
+ expect(command.send(:validate_entity, Object.new.freeze))
247
+ .to be_a_failing_result
248
+ .with_error(expected_error)
249
+ end
250
+ end
251
+
252
+ describe 'with an invalid record instance' do
253
+ it 'should return a failing result' do
254
+ expect(command.send(:validate_entity, Tome.new))
255
+ .to be_a_failing_result
256
+ .with_error(expected_error)
257
+ end
258
+ end
259
+
260
+ describe 'with a valid record instance' do
261
+ it 'should not return a result' do
262
+ expect(command.send(:validate_entity, Book.new))
263
+ .not_to be_a_result
264
+ end
265
+ end
266
+ end
267
+
268
+ describe '#validate_primary_key' do
269
+ let(:primary_key_type) { Integer }
270
+ let(:expected_error) do
271
+ type = primary_key_type
272
+ contract = Stannum::Contracts::ParametersContract.new do
273
+ keyword :primary_key, type
274
+ end
275
+ errors = contract.errors_for(
276
+ {
277
+ arguments: [],
278
+ block: nil,
279
+ keywords: { primary_key: nil }
280
+ }
281
+ )
282
+
283
+ Cuprum::Collections::Errors::InvalidParameters.new(
284
+ command: command,
285
+ errors: errors
286
+ )
287
+ end
288
+
289
+ it 'should define the private method' do
290
+ expect(command)
291
+ .to respond_to(:validate_primary_key, true)
292
+ .with(1).argument
293
+ end
294
+
295
+ describe 'with nil' do
296
+ it 'should return a failing result' do
297
+ expect(command.send(:validate_primary_key, nil))
298
+ .to be_a_failing_result
299
+ .with_error(expected_error)
300
+ end
301
+ end
302
+
303
+ describe 'with an Object' do
304
+ it 'should return a failing result' do
305
+ expect(command.send(:validate_primary_key, Object.new.freeze))
306
+ .to be_a_failing_result
307
+ .with_error(expected_error)
308
+ end
309
+ end
310
+
311
+ describe 'with a String' do
312
+ it 'should return a failing result' do
313
+ expect(command.send(:validate_primary_key, '12345'))
314
+ .to be_a_failing_result
315
+ .with_error(expected_error)
316
+ end
317
+ end
318
+
319
+ describe 'with an Integer' do
320
+ it 'should not return a result' do
321
+ expect(command.send(:validate_primary_key, 12_345))
322
+ .not_to be_a_result
323
+ end
324
+ end
325
+
326
+ context 'with a record class with custom primary key' do
327
+ let(:record_class) { Tome }
328
+ let(:primary_key_type) { String }
329
+
330
+ describe 'with an Integer' do
331
+ it 'should return a failing result' do
332
+ expect(command.send(:validate_primary_key, 12_345))
333
+ .to be_a_failing_result
334
+ .with_error(expected_error)
335
+ end
336
+ end
337
+
338
+ describe 'with a String' do
339
+ it 'should not return a result' do
340
+ expect(command.send(:validate_primary_key, '12345'))
341
+ .not_to be_a_result
342
+ end
343
+ end
344
+ end
345
+ end
346
+
347
+ describe '#validate_primary_keys' do
348
+ let(:primary_keys) { nil }
349
+ let(:primary_key_type) { Integer }
350
+ let(:expected_error) do
351
+ type = primary_key_type
352
+ contract = Stannum::Contracts::ParametersContract.new do
353
+ keyword :primary_keys,
354
+ Stannum::Constraints::Types::ArrayType.new(item_type: type)
355
+ end
356
+ errors = contract.errors_for(
357
+ {
358
+ arguments: [],
359
+ block: nil,
360
+ keywords: { primary_keys: primary_keys }
361
+ }
362
+ )
363
+
364
+ Cuprum::Collections::Errors::InvalidParameters.new(
365
+ command: command,
366
+ errors: errors
367
+ )
368
+ end
369
+
370
+ it 'should define the private method' do
371
+ expect(command)
372
+ .to respond_to(:validate_primary_keys, true)
373
+ .with(1).argument
374
+ end
375
+
376
+ describe 'with nil' do
377
+ it 'should return a failing result' do
378
+ expect(command.send(:validate_primary_keys, nil))
379
+ .to be_a_failing_result
380
+ .with_error(expected_error)
381
+ end
382
+ end
383
+
384
+ describe 'with an Object' do
385
+ it 'should return a failing result' do
386
+ expect(command.send(:validate_primary_keys, Object.new.freeze))
387
+ .to be_a_failing_result
388
+ .with_error(expected_error)
389
+ end
390
+ end
391
+
392
+ describe 'with a String' do
393
+ it 'should return a failing result' do
394
+ expect(command.send(:validate_primary_keys, '12345'))
395
+ .to be_a_failing_result
396
+ .with_error(expected_error)
397
+ end
398
+ end
399
+
400
+ describe 'with an Integer' do
401
+ it 'should return a failing result' do
402
+ expect(command.send(:validate_primary_keys, 12_345))
403
+ .to be_a_failing_result
404
+ .with_error(expected_error)
405
+ end
406
+ end
407
+
408
+ describe 'with an empty Array' do
409
+ it 'should not return a result' do
410
+ expect(command.send(:validate_primary_keys, []))
411
+ .not_to be_a_result
412
+ end
413
+ end
414
+
415
+ describe 'with an Array with nil values' do
416
+ let(:primary_keys) { Array.new(3, nil) }
417
+
418
+ it 'should return a failing result' do
419
+ expect(command.send(:validate_primary_keys, primary_keys))
420
+ .to be_a_failing_result
421
+ .with_error(expected_error)
422
+ end
423
+ end
424
+
425
+ describe 'with an Array with Object values' do
426
+ let(:primary_keys) { Array.new(3) { Object.new.freeze } }
427
+
428
+ it 'should return a failing result' do
429
+ expect(command.send(:validate_primary_keys, primary_keys))
430
+ .to be_a_failing_result
431
+ .with_error(expected_error)
432
+ end
433
+ end
434
+
435
+ describe 'with an Array with String values' do
436
+ let(:primary_keys) { %w[ichi ni san] }
437
+
438
+ it 'should return a failing result' do
439
+ expect(command.send(:validate_primary_keys, primary_keys))
440
+ .to be_a_failing_result
441
+ .with_error(expected_error)
442
+ end
443
+ end
444
+
445
+ describe 'with an Array with Integer values' do
446
+ it 'should not return a result' do
447
+ expect(command.send(:validate_primary_keys, [0, 1, 2]))
448
+ .not_to be_a_result
449
+ end
450
+ end
451
+
452
+ context 'with a record class with custom primary key' do
453
+ let(:record_class) { Tome }
454
+ let(:primary_key_type) { String }
455
+
456
+ describe 'with an Array with String values' do
457
+ let(:primary_keys) { %w[ichi ni san] }
458
+
459
+ it 'should not return a result' do
460
+ expect(command.send(:validate_primary_keys, primary_keys))
461
+ .not_to be_a_result
462
+ end
463
+ end
464
+
465
+ describe 'with an Array with Integer values' do
466
+ let(:primary_keys) { [0, 1, 2] }
467
+
468
+ it 'should return a failing result' do
469
+ expect(command.send(:validate_primary_keys, primary_keys))
470
+ .to be_a_failing_result
471
+ .with_error(expected_error)
472
+ end
473
+ end
474
+ end
475
+ end
476
+ end
477
+ end
478
+ end
479
+ end
@@ -0,0 +1,232 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/sleeping_king_studios/contract'
4
+
5
+ require 'cuprum/rails/rspec/contracts'
6
+
7
+ module Cuprum::Rails::RSpec::Contracts
8
+ # Namespace for RSpec responder contracts.
9
+ module ResponderContracts
10
+ # Contract validating the interface for a responder.
11
+ module ShouldImplementTheResponderMethodsContract
12
+ extend RSpec::SleepingKingStudios::Contract
13
+
14
+ # @!method apply(example_group, constructor_keywords: [])
15
+ # Adds the contract to the example group.
16
+ #
17
+ # @param example_group [RSpec::Core::ExampleGroup] the example group to
18
+ # which the contract is applied.
19
+ # @param constructor_keywords [Array<Symbol>] additional keywords that
20
+ # are required by the constructor.
21
+ # @param controller_name [String] the name of the test controller.
22
+ contract do |
23
+ constructor_keywords: [],
24
+ controller_name: 'Spec::CustomController'
25
+ |
26
+ def self.let?(method_name, &block)
27
+ let(method_name, &block) unless method_defined?(method_name)
28
+ end
29
+
30
+ let?(:resource_options) { { name: 'books' } }
31
+ let?(:resource) do
32
+ Cuprum::Rails::Resource.new(**resource_options)
33
+ end
34
+
35
+ example_class(controller_name) do |klass|
36
+ configured_resource = resource
37
+
38
+ klass.define_singleton_method(:resource) do
39
+ @resource ||= configured_resource
40
+ end
41
+ end
42
+
43
+ describe '.new' do
44
+ let(:expected_keywords) do
45
+ %i[
46
+ action_name
47
+ controller
48
+ member_action
49
+ request
50
+ resource
51
+ ]
52
+ end
53
+
54
+ it 'should define the constructor' do
55
+ expect(described_class)
56
+ .to respond_to(:new)
57
+ .with(0).arguments
58
+ .and_keywords(*expected_keywords, *constructor_keywords)
59
+ .and_any_keywords
60
+ end
61
+ end
62
+
63
+ describe '#action_name' do
64
+ include_examples 'should define reader',
65
+ :action_name,
66
+ -> { action_name }
67
+ end
68
+
69
+ describe '#call' do
70
+ let(:result) { Cuprum::Result.new(value: :ok) }
71
+
72
+ def ignore_exceptions
73
+ yield
74
+ rescue StandardError
75
+ # Do nothing
76
+ end
77
+
78
+ it { expect(responder).to respond_to(:call).with(1).argument }
79
+
80
+ it 'should set the result' do
81
+ expect { ignore_exceptions { responder.call(result) } }
82
+ .to change(responder, :result)
83
+ .to be == result
84
+ end
85
+ end
86
+
87
+ describe '#controller' do
88
+ include_examples 'should define reader',
89
+ :controller,
90
+ -> { controller }
91
+ end
92
+
93
+ describe '#controller_name' do
94
+ include_examples 'should define reader',
95
+ :controller_name,
96
+ controller_name
97
+ end
98
+
99
+ describe '#member_action?' do
100
+ include_examples 'should define predicate',
101
+ :member_action?,
102
+ -> { !!constructor_options[:member_action] } # rubocop:disable Style/DoubleNegation
103
+
104
+ context 'when initialized with member_action: false' do
105
+ let(:constructor_options) { super().merge(member_action: false) }
106
+
107
+ it { expect(responder.member_action?).to be false }
108
+ end
109
+
110
+ context 'when initialized with member_action: true' do
111
+ let(:constructor_options) { super().merge(member_action: true) }
112
+
113
+ it { expect(responder.member_action?).to be true }
114
+ end
115
+ end
116
+
117
+ describe '#request' do
118
+ include_examples 'should define reader', :request, -> { request }
119
+ end
120
+
121
+ describe '#resource' do
122
+ include_examples 'should define reader',
123
+ :resource,
124
+ -> { controller.class.resource }
125
+ end
126
+
127
+ describe '#result' do
128
+ include_examples 'should define reader', :result, nil
129
+ end
130
+
131
+ describe '#routes' do
132
+ include_examples 'should define reader', :routes
133
+
134
+ let(:resource_options) { super().merge(name: 'books') }
135
+
136
+ it 'should return the resource routes' do
137
+ expect(responder.routes)
138
+ .to be_a Cuprum::Rails::Routing::PluralRoutes
139
+ end
140
+
141
+ it { expect(responder.routes.base_path).to be == '/books' }
142
+
143
+ it { expect(responder.routes.index_path).to be == '/books' }
144
+
145
+ it { expect(responder.routes.parent_path).to be == '/' }
146
+
147
+ it { expect(responder.routes.show_path(0)).to be == '/books/0' }
148
+
149
+ it { expect(responder.routes.wildcards).to be == {} }
150
+
151
+ context 'when the request has path params' do
152
+ let(:path_params) { { 'author_id' => 0 } }
153
+ let(:request) do
154
+ Cuprum::Rails::Request.new(path_params: path_params)
155
+ end
156
+
157
+ it { expect(responder.routes.wildcards).to be == path_params }
158
+ end
159
+
160
+ context 'when initialized with a singular resource' do
161
+ let(:resource_options) do
162
+ super().merge(name: 'book', singular: true)
163
+ end
164
+
165
+ it 'should return the resource routes' do
166
+ expect(responder.routes)
167
+ .to be_a Cuprum::Rails::Routing::SingularRoutes
168
+ end
169
+
170
+ it { expect(responder.routes.base_path).to be == '/book' }
171
+
172
+ it { expect(responder.routes.show_path).to be == '/book' }
173
+ end
174
+
175
+ context 'when initialized with a resource with custom routes' do
176
+ let(:resource_options) do
177
+ super().merge(
178
+ routes: Cuprum::Rails::Routes.new(base_path: '/path/to/books')
179
+ )
180
+ end
181
+
182
+ it { expect(responder.routes.base_path).to be == '/path/to/books' }
183
+ end
184
+
185
+ context 'when initialized with a resource with ancestors' do
186
+ let(:base_path) { '/authors/:author_id/series/:series_id' }
187
+ let(:authors_resource) do
188
+ Cuprum::Rails::Resource.new(name: 'authors')
189
+ end
190
+ let(:series_resource) do
191
+ Cuprum::Rails::Resource.new(
192
+ name: 'series',
193
+ singular_name: 'series',
194
+ parent: authors_resource
195
+ )
196
+ end
197
+ let(:resource_options) { super().merge(parent: series_resource) }
198
+
199
+ it 'should set the scoped base path' do
200
+ expect(responder.routes.base_path).to be == "#{base_path}/books"
201
+ end
202
+
203
+ context 'when the request has path params' do
204
+ let(:path_params) { { 'author_id' => 0, 'series_id' => 1 } }
205
+ let(:parent_path) { '/authors/0/series/1' }
206
+ let(:request) do
207
+ Cuprum::Rails::Request.new(path_params: path_params)
208
+ end
209
+
210
+ it { expect(responder.routes.wildcards).to be == path_params }
211
+
212
+ it 'should set the scoped index path' do
213
+ expect(responder.routes.index_path)
214
+ .to be == "#{parent_path}/books"
215
+ end
216
+
217
+ it 'should set the scoped parent path' do
218
+ expect(responder.routes.parent_path)
219
+ .to be == parent_path
220
+ end
221
+
222
+ it 'should set the scoped show path' do
223
+ expect(responder.routes.show_path(2))
224
+ .to be == "#{parent_path}/books/2"
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end
230
+ end
231
+ end
232
+ end