cuprum-rails 0.1.0 → 0.2.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 (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