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
@@ -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