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
@@ -13,24 +13,29 @@ module Cuprum::Rails
13
13
  ].freeze
14
14
  private_constant :FILTERED_HEADER_PREFIXES
15
15
 
16
- FILTERED_PARAMS = %w[controller action].freeze
17
- private_constant :FILTERED_PARAMS
18
-
19
16
  # Generates a Request from a native Rails request.
20
17
  #
21
- # @param request [] The native request to build.
18
+ # @param request [ActionDispatch::Request] The native request to build.
22
19
  #
23
20
  # @return [Cuprum::Rails::Request] the generated request.
24
- def build(request:)
21
+ def build(request:, **options) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
22
+ body_params = request.request_parameters
23
+ query_params = request.query_parameters
24
+ path_params = filter_path_parameters(request.path_parameters)
25
+
25
26
  new(
26
- authorization: request.authorization,
27
- body_params: request.request_parameters,
28
- format: request.format.symbol,
29
- headers: filter_headers(request.headers),
30
- method: request.request_method_symbol,
31
- params: filter_params(request.params),
32
- path: request.fullpath,
33
- query_params: request.query_parameters
27
+ action_name: request.params['action']&.intern,
28
+ authorization: request.authorization,
29
+ body_params: body_params,
30
+ controller_name: request.params['controller'],
31
+ format: request.format.symbol,
32
+ headers: filter_headers(request.headers),
33
+ http_method: request.request_method_symbol,
34
+ params: body_params.merge(query_params).merge(path_params),
35
+ path: request.fullpath,
36
+ path_params: path_params,
37
+ query_params: query_params,
38
+ **options
34
39
  )
35
40
  end
36
41
 
@@ -42,64 +47,148 @@ module Cuprum::Rails
42
47
  end
43
48
  end
44
49
 
45
- def filter_params(params)
46
- params.reject { |key, _| FILTERED_PARAMS.include?(key) }
50
+ def filter_path_parameters(path_parameters)
51
+ path_parameters.except('action', 'controller')
52
+ end
53
+
54
+ def property(property_name)
55
+ define_method(property_name) do
56
+ @properties[property_name]
57
+ end
58
+
59
+ define_method(:"#{property_name}=") do |value|
60
+ @properties[property_name] = value
61
+ end
47
62
  end
48
63
  end
49
64
 
50
- # @param authorization [String, nil] The authorization header, if any.
51
- # @param body_params [Hash<String, Object>] The parameters from the request
52
- # body.
53
- # @param format [Symbol] The request format, e.g. :html or :json.
54
- # @param headers [Hash<String, String>] The request headers.
55
- # @param method [Symbol] The HTTP method used for the request.
56
- # @param params [Hash<String, Object>] The merged GET and POST parameters.
57
- # @param query_params [Hash<String, Object>] The query parameters.
58
- def initialize( # rubocop:disable Metrics/ParameterLists
59
- body_params:,
60
- format:,
61
- headers:,
62
- method:,
63
- params:,
64
- path:,
65
- query_params:,
66
- authorization: nil
67
- )
68
- @authorization = authorization
69
- @body_params = body_params
70
- @format = format
71
- @headers = headers
72
- @method = method
73
- @path = path
74
- @params = params
75
- @query_params = query_params
65
+ # @param context [Object] the controller or request context.
66
+ #
67
+ # @option properties [Symbol] :action_name the name of the called action.
68
+ # @option properties [String, nil] :authorization the authorization header,
69
+ # if any.
70
+ # @option properties [Hash<String, Object>] :body_params the parameters from
71
+ # the request body.
72
+ # @option properties [String] :controller_name the name of the controller.
73
+ # @option properties [Symbol] :format the request format, e.g. :html or
74
+ # :json.
75
+ # @option properties [Hash<String, String>] :headers the request headers.
76
+ # @option properties [Boolean] :member_action true if the request is for a
77
+ # resource member action; otherwise false.
78
+ # @option properties [Symbol] :method the HTTP method used for the request.
79
+ # @option properties [Hash<String, Object>] :params the merged GET and POST
80
+ # parameters.
81
+ # @option properties [Hash<String, Object>] :query_params the query
82
+ # parameters.
83
+ def initialize(context: nil, **properties)
84
+ @context = context
85
+ @properties = properties
86
+ end
87
+
88
+ # @return [Object] the controller or request context.
89
+ attr_reader :context
90
+
91
+ # @return [Hash<Symbol, Object>] the properties of the request.
92
+ attr_reader :properties
93
+
94
+ # @!attribute action_name
95
+ # @return [Symbol] the name of the called action.
96
+ property :action_name
97
+
98
+ # @!attribute authorization
99
+ # @return [String, nil] the authorization header, if any.
100
+ property :authorization
101
+
102
+ # @!attribute body_params
103
+ # @return [Hash<String, Object>] the parameters from the request body.
104
+ property :body_params
105
+ alias body_parameters body_params
106
+ alias body_parameters= body_params=
107
+
108
+ # @!attribute controller_name
109
+ # @return [String] the name of the controller.
110
+ property :controller_name
111
+
112
+ # @!attribute format [Symbol]
113
+ # @return the request format, e.g. :html or :json.
114
+ property :format
115
+
116
+ # @!attribute headers
117
+ # @return [Hash<String, String>] the request headers.
118
+ property :headers
119
+
120
+ # @!attribute method
121
+ # @return [Symbol] the HTTP method used for the request.
122
+ property :http_method
123
+
124
+ # @!attribute params
125
+ # @return [Hash<String, Object>] The merged GET and POST parameters.
126
+ property :params
127
+ alias parameters params
128
+ alias parameters= params=
129
+
130
+ # @!attribute path
131
+ # @return [String] the relative path of the request, including params.
132
+ property :path
133
+
134
+ # @!attribute path_params
135
+ # @return [Hash<String, Object>] the path parameters.
136
+ property :path_params
137
+ alias path_parameters path_params
138
+ alias path_parameters= path_params=
139
+
140
+ # @!attribute query_params
141
+ # @return [Hash<String, Object>] the query parameters.
142
+ property :query_params
143
+ alias query_parameters query_params
144
+ alias query_parameters= query_params=
145
+
146
+ # @param property_name [String, Symbol] the name of the property.
147
+ #
148
+ # @return [Object] the value of the property
149
+ def [](property_name)
150
+ validate_property_name!(property_name)
151
+
152
+ @properties[property_name.intern]
76
153
  end
77
154
 
78
- # @return [String, nil] the authorization header, if any.
79
- attr_reader :authorization
155
+ # @param property_name [String, Symbol] the name of the property.
156
+ # @param value [Object] the value to assign to the property.
157
+ def []=(property_name, value)
158
+ validate_property_name!(property_name)
159
+
160
+ @properties[property_name.intern] = value
161
+ end
80
162
 
81
- # @return [Hash<String, Object>] The parameters from the request body.
82
- attr_reader :body_params
83
- alias body_parameters body_params
163
+ # @return [Boolean] true if the request is for a resource member action;
164
+ # otherwise false.
165
+ def member_action?
166
+ !!@properties[:member_action]
167
+ end
84
168
 
85
- # @return [Symbol] the request format, e.g. :html or :json.
86
- attr_reader :format
169
+ # @return [ActionDispatch::Request::Session] the native session object.
170
+ def native_session
171
+ context&.session
172
+ end
87
173
 
88
- # @return [Hash<String, String>] the request headers.
89
- attr_reader :headers
174
+ private
90
175
 
91
- # @return [Symbol] the HTTP method used for the request.
92
- attr_reader :method
176
+ def validate_property_name!(name) # rubocop:disable Metrics/MethodLength
177
+ if name.nil?
178
+ raise ArgumentError,
179
+ "property name can't be blank",
180
+ caller(1..-1)
181
+ end
93
182
 
94
- # @return [Hash<String, Object>] The merged GET and POST parameters.
95
- attr_reader :params
96
- alias parameters params
183
+ unless name.is_a?(String) || name.is_a?(Symbol)
184
+ raise ArgumentError,
185
+ 'property name must be a String or a Symbol',
186
+ caller(1..-1)
187
+ end
97
188
 
98
- # @return [String] the relative path of the request, including params.
99
- attr_reader :path
189
+ return unless name.empty?
100
190
 
101
- # @return [Hash<String, Object>] the query parameters.
102
- attr_reader :query_params
103
- alias query_parameters query_params
191
+ raise ArgumentError, "property name can't be blank", caller(1..-1)
192
+ end
104
193
  end
105
194
  end
@@ -1,67 +1,85 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'cuprum/collections/resource'
4
+
3
5
  require 'cuprum/rails'
6
+ require 'cuprum/rails/collection'
4
7
 
5
8
  module Cuprum::Rails
6
9
  # Value object representing a controller resource.
7
- class Resource
8
- # @param collection [Cuprum::Collections::Base] Collection representing the
9
- # resource data.
10
- # @param options [Hash] Additional options for the resource.
11
- # @param resource_class [Class] Class representing the resource items.
12
- # @param resource_name [String] The name of the resource.
13
- # @param routes [Cuprum::Rails::Routes] The routes defined for the resource.
14
- # @param singular [Boolean] Indicates that the resource is a singular
15
- # collection, and has only one member.
10
+ class Resource < Cuprum::Collections::Resource
11
+ # Default actions for a plural resource.
12
+ PLURAL_ACTIONS = %w[create destroy edit index new show update].freeze
13
+
14
+ # Default actions for a singular resource.
15
+ SINGULAR_ACTIONS = %w[create destroy edit new show update].freeze
16
+
17
+ STRING_COLUMN_TYPES = Set.new(%i[string uuid]).freeze
18
+ private_constant :STRING_COLUMN_TYPES
19
+
20
+ # @overload initialize(entity_class: nil, name: nil, qualified_name: nil, singular_name: nil, **options)
21
+ # @param entity_class [Class, String] the class of entity represented by
22
+ # the resource.
23
+ # @param name [String] the name of the resource.
24
+ # @param qualified_name [String] a scoped name for the resource.
25
+ # @param routes [Cuprum::Rails::Routes] the routes defined for the
26
+ # resource.
27
+ # @param singular_name [String] the name of an entity in the resource.
28
+ # @param options [Hash] additional options for the resource.
16
29
  #
17
- # @option options default_order [Hash] The default ordering for the resource
18
- # items.
19
- # @option options permitted_attributes [Array] List of attributes that can
20
- # be set or changed by resourceful actions.
21
- # @option options primary_key [String, Symbol] The name of the primary key
22
- # for the resource, if any.
23
- # @option options singular_resource_name [String] The singular form of the
24
- # resource name.
25
- def initialize( # rubocop:disable Metrics/ParameterLists
26
- collection: nil,
27
- resource_class: nil,
28
- resource_name: nil,
29
- routes: nil,
30
- singular: false,
31
- **options
32
- )
33
- unless resource_class || resource_name
34
- raise ArgumentError, 'missing keyword :resource_class or :resource_name'
35
- end
36
-
37
- validate_permitted_attributes(options[:permitted_attributes])
38
-
39
- @collection = collection
40
- @options = options
41
- @resource_class = resource_class
42
- @resource_name = resource_name.to_s unless resource_name.nil?
43
- @routes = routes
44
- @singular = !!singular
45
- end
46
-
47
- # @return [Cuprum::Collections::Base] collection representing the resource
48
- # data.
49
- attr_reader :collection
50
-
51
- # @return [Hash] additional options for the resource.
52
- attr_reader :options
53
-
54
- # @return [Class] class representing the resource items.
55
- attr_reader :resource_class
30
+ # @option options actions [Array, Set] the defined actions for the
31
+ # resource.
32
+ # @option options base_path [String] the base url for the resource.
33
+ # @option options default_order [Hash] the default ordering for the
34
+ # resource items.
35
+ # @option options permitted_attributes [Array] list of attributes that can
36
+ # be set or changed by resourceful actions.
37
+ # @option options primary_key_name [String] the name of the primary key
38
+ # attribute. Defaults to 'id'.
39
+ # @option primary_key_type [Class, Stannum::Constraint] the type of
40
+ # the primary key attribute. Defaults to Integer.
41
+ # @option options plural [Boolean] if true, the resource represents a
42
+ # plural resource. Defaults to true. Can also be specified as :singular.
43
+ # @option primary_key_type [Class, Stannum::Constraint] the type of
44
+ # the primary key attribute. Defaults to Integer.
45
+ def initialize(routes: nil, **params)
46
+ validate_permitted_attributes(params[:permitted_attributes])
47
+
48
+ super(**params)
49
+
50
+ @routes = routes
51
+ end
52
+
53
+ # @return [Set] the defined actions for the resource.
54
+ def actions
55
+ @actions ||= Set.new(options.fetch(:actions, default_actions).map(&:to_s))
56
+ end
57
+
58
+ # @return [Array<Resource>] the resource's ancestors, starting with the
59
+ # resource itself.
60
+ def ancestors
61
+ each_ancestor.to_a
62
+ end
56
63
 
57
64
  # @return [String] the base url for the resource.
58
65
  def base_path
59
66
  @base_path ||=
60
67
  options
61
- .fetch(:base_path) { "/#{resource_name.underscore}" }
68
+ .fetch(:base_path) { default_base_path }
62
69
  .to_s
63
70
  end
64
71
 
72
+ # Enumerates the resource's ancestors, starting with the resource itself.
73
+ #
74
+ # @yield_param [Resource] the current ancestor.
75
+ def each_ancestor(&block)
76
+ return enum_for(:each_ancestor) unless block_given?
77
+
78
+ parent&.each_ancestor(&block)
79
+
80
+ yield self
81
+ end
82
+
65
83
  # @return [Hash] the default ordering for the resource items.
66
84
  def default_order
67
85
  @default_order ||= options.fetch(:default_order, {})
@@ -73,27 +91,32 @@ module Cuprum::Rails
73
91
  @permitted_attributes ||= options.fetch(:permitted_attributes, nil)
74
92
  end
75
93
 
76
- # @return [Boolean] true if the collection is a plural collection, otherwise
77
- # false.
78
- def plural?
79
- !@singular
80
- end
81
-
82
- # @return [String] the name of the primary key for the resource, if any.
83
- def primary_key
84
- @primary_key ||=
94
+ # @return [String] the name of the primary key attribute. Defaults to 'id'.
95
+ def primary_key_name
96
+ @primary_key_name ||=
85
97
  options
86
- .fetch(:primary_key) { resource_class&.primary_key }
87
- .yield_self { |value| value.nil? ? nil : value.to_s }
98
+ .fetch(:primary_key_name) { entity_class.primary_key }
99
+ .to_s
88
100
  end
101
+ alias primary_key primary_key_name
89
102
 
90
- # @return [String] the name of the resource.
91
- def resource_name
92
- return @resource_name if @resource_name
103
+ # @return [Cuprum::Rails::Resource] the parent resource, if any.
104
+ def parent
105
+ @parent ||= @options.fetch(:parent, nil)
106
+ end
93
107
 
94
- name = resource_class.name.split('::').last.underscore
108
+ # @return [Class, Stannum::Constraint] the type of the primary key
109
+ # attribute. Defaults to Integer.
110
+ def primary_key_type
111
+ @primary_key_type =
112
+ options
113
+ .fetch(:primary_key_type) do
114
+ key = entity_class.primary_key
115
+ column = entity_class.columns.find { |col| col.name == key }
95
116
 
96
- @resource_name = plural? ? name.pluralize : name
117
+ STRING_COLUMN_TYPES.include?(column.type) ? String : Integer
118
+ end # rubocop:disable Style/MultilineBlockChain
119
+ .then { |value| value.is_a?(String) ? value.constantize : value }
97
120
  end
98
121
 
99
122
  # Generates the routes for the resource and injects the given wildcards.
@@ -105,41 +128,52 @@ module Cuprum::Rails
105
128
  routes_without_wildcards.with_wildcards(wildcards)
106
129
  end
107
130
 
108
- # @return [Boolean] true if the collection is a singular collection,
109
- # otherwise false.
110
- def singular?
111
- @singular
131
+ private
132
+
133
+ def default_actions
134
+ singular? ? SINGULAR_ACTIONS : PLURAL_ACTIONS
112
135
  end
113
136
 
114
- # @return [String] the singular form of the resource name.
115
- def singular_resource_name
116
- @singular_resource_name ||=
117
- options
118
- .fetch(:singular_resource_name) do
119
- resource_name.singularize
120
- end
121
- .to_s
137
+ def default_base_path
138
+ "#{parent_path}/#{(singular? ? name.singularize : name).underscore}"
122
139
  end
123
140
 
124
- def validate_permitted_attributes(attributes)
125
- return if attributes.nil? || attributes.is_a?(Array)
141
+ def default_parent_path
142
+ return unless parent
126
143
 
127
- raise ArgumentError,
128
- 'keyword :permitted_attributes must be an Array or nil',
129
- caller(1..-1)
144
+ return parent.base_path if parent.singular?
145
+
146
+ "#{parent.base_path}/:#{parent.singular_name}_id"
130
147
  end
131
148
 
132
- private
149
+ def parent_path
150
+ @parent_path || default_parent_path
151
+ end
152
+
153
+ def routes_options
154
+ {
155
+ base_path: base_path,
156
+ parent_path: parent_path
157
+ }
158
+ end
133
159
 
134
160
  def routes_without_wildcards
135
161
  return @routes if @routes
136
162
 
137
163
  @routes =
138
164
  if plural?
139
- Cuprum::Rails::Routing::PluralRoutes.new(base_path: base_path)
165
+ Cuprum::Rails::Routing::PluralRoutes.new(**routes_options)
140
166
  else
141
- Cuprum::Rails::Routing::SingularRoutes.new(base_path: base_path)
167
+ Cuprum::Rails::Routing::SingularRoutes.new(**routes_options)
142
168
  end
143
169
  end
170
+
171
+ def validate_permitted_attributes(attributes)
172
+ return if attributes.nil? || attributes.is_a?(Array)
173
+
174
+ raise ArgumentError,
175
+ 'keyword :permitted_attributes must be an Array or nil',
176
+ caller(1..-1)
177
+ end
144
178
  end
145
179
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/rails/responders'
4
+
5
+ module Cuprum::Rails::Responders
6
+ # Abstract base class for defining responders.
7
+ class BaseResponder
8
+ extend Forwardable
9
+
10
+ # @param action_name [String, Symbol] the name of the action to match.
11
+ # @param controller [Cuprum::Rails::Controller] the called controller.
12
+ # @param member_action [Boolean] true if the action acts on a collection
13
+ # item, not on the collection as a whole.
14
+ # @param request [Cuprum::Rails::Request] the request to the controller.
15
+ def initialize(
16
+ action_name:,
17
+ controller:,
18
+ request:,
19
+ member_action: false,
20
+ **_options
21
+ )
22
+ @action_name = action_name
23
+ @controller = controller
24
+ @controller_name = controller.class.name
25
+ @member_action = !!member_action # rubocop:disable Style/DoubleNegation
26
+ @request = request
27
+ @resource = controller.class.resource
28
+ end
29
+
30
+ # @return [String, Symbol] the name of the action to match.
31
+ attr_reader :action_name
32
+
33
+ # @return [Cuprum::Rails::Controller] the called controller.
34
+ attr_reader :controller
35
+
36
+ # @return [String] the name of the called controller.
37
+ attr_reader :controller_name
38
+
39
+ # @return [Cuprum::Rails::Request] the request to the controller.
40
+ attr_reader :request
41
+
42
+ # @return [Cuprum::Rails::Resource] the resource for the controller.
43
+ attr_reader :resource
44
+
45
+ # @return [Cuprum::Result] the result of calling the action.
46
+ attr_reader :result
47
+
48
+ # Generates the response object for the result.
49
+ #
50
+ # @param result [Cuprum::Result] the result of the action call.
51
+ #
52
+ # @return [#call] the response object from the matching response clause.
53
+ def call(result)
54
+ @result = result
55
+ end
56
+
57
+ # @return [true, false] true if the action is a member action, otherwise
58
+ # false.
59
+ def member_action?
60
+ @member_action
61
+ end
62
+
63
+ # Helper for accessing the configured routes for the resource.
64
+ #
65
+ # Any wildcards from the path params will be applied to the routes.
66
+ #
67
+ # @return [Cuprum::Rails::Routes] the configured routes.
68
+ def routes
69
+ resource.routes.with_wildcards(routes_wildcards)
70
+ end
71
+
72
+ private
73
+
74
+ def routes_wildcards
75
+ request.path_params || {}
76
+ end
77
+ end
78
+ end
@@ -3,6 +3,7 @@
3
3
  require 'cuprum/collections/errors/failed_validation'
4
4
 
5
5
  require 'cuprum/rails/responders/html'
6
+ require 'cuprum/rails/responders/html/resource'
6
7
 
7
8
  module Cuprum::Rails::Responders::Html
8
9
  # Defines default responses for a plural RESTful resource.
@@ -18,45 +19,14 @@ module Cuprum::Rails::Responders::Html
18
19
  # action name and passing the result value as assigned variables. For a
19
20
  # failing result, redirects to either the show page or the index page for the
20
21
  # resource, based on the resource's defined #routes.
21
- class PluralResource < Cuprum::Rails::Responders::HtmlResponder
22
- action :create do
23
- match :failure, error: Cuprum::Collections::Errors::FailedValidation do
24
- render :new,
25
- assigns: result.value.merge(errors: result.error.errors),
26
- status: 422 # rubocop:disable Rails/HttpStatus
27
- end
28
-
29
- match :success do
30
- entity = result.value[resource.singular_resource_name]
31
-
32
- redirect_to resource.routes.show_path(entity)
33
- end
34
- end
35
-
36
- action :destroy do
37
- match :success do
38
- redirect_to resource.routes.index_path
39
- end
40
- end
41
-
42
- action :index do
43
- match :failure do
44
- redirect_to resource.routes.root_path
45
- end
46
- end
47
-
48
- action :update do
49
- match :failure, error: Cuprum::Collections::Errors::FailedValidation do
50
- render :edit,
51
- assigns: result.value.merge(errors: result.error.errors),
52
- status: 422 # rubocop:disable Rails/HttpStatus
53
- end
54
-
55
- match :success do
56
- entity = result.value[resource.singular_resource_name]
57
-
58
- redirect_to resource.routes.show_path(entity)
59
- end
22
+ class PluralResource < Cuprum::Rails::Responders::Html::Resource
23
+ def initialize(**options)
24
+ super
25
+
26
+ SleepingKingStudios::Tools::CoreTools.deprecate(
27
+ 'Cuprum::Rails::Responders::Html::PluralResource',
28
+ message: 'use Cuprum::Rails::Responders::Html::Resource'
29
+ )
60
30
  end
61
31
  end
62
32
  end