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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +145 -0
- data/DEVELOPMENT.md +20 -0
- data/README.md +356 -63
- data/lib/cuprum/rails/action.rb +32 -16
- data/lib/cuprum/rails/actions/create.rb +62 -15
- data/lib/cuprum/rails/actions/destroy.rb +23 -7
- data/lib/cuprum/rails/actions/edit.rb +23 -7
- data/lib/cuprum/rails/actions/index.rb +30 -10
- data/lib/cuprum/rails/actions/middleware/associations/cache.rb +112 -0
- data/lib/cuprum/rails/actions/middleware/associations/find.rb +23 -0
- data/lib/cuprum/rails/actions/middleware/associations/parent.rb +70 -0
- data/lib/cuprum/rails/actions/middleware/associations/query.rb +140 -0
- data/lib/cuprum/rails/actions/middleware/associations.rb +12 -0
- data/lib/cuprum/rails/actions/middleware/log_request.rb +126 -0
- data/lib/cuprum/rails/actions/middleware/log_result.rb +51 -0
- data/lib/cuprum/rails/actions/middleware/resources/find.rb +44 -0
- data/lib/cuprum/rails/actions/middleware/resources/query.rb +91 -0
- data/lib/cuprum/rails/actions/middleware/resources.rb +11 -0
- data/lib/cuprum/rails/actions/middleware.rb +13 -0
- data/lib/cuprum/rails/actions/new.rb +16 -4
- data/lib/cuprum/rails/actions/parameter_validation.rb +60 -0
- data/lib/cuprum/rails/actions/resource_action.rb +119 -42
- data/lib/cuprum/rails/actions/show.rb +23 -7
- data/lib/cuprum/rails/actions/update.rb +70 -22
- data/lib/cuprum/rails/actions.rb +11 -7
- data/lib/cuprum/rails/collection.rb +27 -47
- data/lib/cuprum/rails/command.rb +3 -1
- data/lib/cuprum/rails/commands/destroy_one.rb +10 -6
- data/lib/cuprum/rails/commands/find_many.rb +8 -1
- data/lib/cuprum/rails/commands/find_matching.rb +1 -1
- data/lib/cuprum/rails/commands/find_one.rb +8 -0
- data/lib/cuprum/rails/commands/insert_one.rb +17 -6
- data/lib/cuprum/rails/commands/update_one.rb +16 -5
- data/lib/cuprum/rails/constraints/parameters_contract.rb +14 -0
- data/lib/cuprum/rails/constraints.rb +10 -0
- data/lib/cuprum/rails/controller.rb +12 -2
- data/lib/cuprum/rails/controllers/action.rb +100 -0
- data/lib/cuprum/rails/controllers/class_methods/actions.rb +33 -7
- data/lib/cuprum/rails/controllers/class_methods/configuration.rb +36 -0
- data/lib/cuprum/rails/controllers/class_methods/middleware.rb +88 -0
- data/lib/cuprum/rails/controllers/class_methods/validations.rb +2 -2
- data/lib/cuprum/rails/controllers/configuration.rb +41 -1
- data/lib/cuprum/rails/controllers/middleware.rb +59 -0
- data/lib/cuprum/rails/controllers.rb +2 -0
- data/lib/cuprum/rails/errors/invalid_parameters.rb +55 -0
- data/lib/cuprum/rails/errors/invalid_statement.rb +11 -0
- data/lib/cuprum/rails/errors/missing_parameter.rb +42 -0
- data/lib/cuprum/rails/errors/resource_error.rb +46 -0
- data/lib/cuprum/rails/errors.rb +6 -1
- data/lib/cuprum/rails/map_errors.rb +29 -1
- data/lib/cuprum/rails/query.rb +1 -1
- data/lib/cuprum/rails/repository.rb +12 -25
- data/lib/cuprum/rails/request.rb +149 -60
- data/lib/cuprum/rails/resource.rb +119 -85
- data/lib/cuprum/rails/responders/base_responder.rb +78 -0
- data/lib/cuprum/rails/responders/html/plural_resource.rb +9 -39
- data/lib/cuprum/rails/responders/html/rendering.rb +81 -0
- data/lib/cuprum/rails/responders/html/resource.rb +107 -0
- data/lib/cuprum/rails/responders/html/singular_resource.rb +9 -38
- data/lib/cuprum/rails/responders/html.rb +2 -0
- data/lib/cuprum/rails/responders/html_responder.rb +8 -52
- data/lib/cuprum/rails/responders/json/resource.rb +3 -3
- data/lib/cuprum/rails/responders/json_responder.rb +31 -16
- data/lib/cuprum/rails/responders/matching.rb +29 -27
- data/lib/cuprum/rails/responders/serialization.rb +11 -9
- data/lib/cuprum/rails/responders.rb +1 -0
- data/lib/cuprum/rails/responses/head_response.rb +24 -0
- data/lib/cuprum/rails/responses/html/redirect_back_response.rb +55 -0
- data/lib/cuprum/rails/responses/html/redirect_response.rb +19 -4
- data/lib/cuprum/rails/responses/html/render_response.rb +17 -5
- data/lib/cuprum/rails/responses/html.rb +6 -2
- data/lib/cuprum/rails/responses.rb +1 -0
- data/lib/cuprum/rails/result.rb +36 -0
- data/lib/cuprum/rails/routes.rb +36 -23
- data/lib/cuprum/rails/rspec/contract_helpers.rb +57 -0
- data/lib/cuprum/rails/rspec/contracts/action_contracts.rb +754 -0
- data/lib/cuprum/rails/rspec/contracts/actions/create_contracts.rb +289 -0
- data/lib/cuprum/rails/rspec/contracts/actions/destroy_contracts.rb +164 -0
- data/lib/cuprum/rails/rspec/contracts/actions/edit_contracts.rb +73 -0
- data/lib/cuprum/rails/rspec/contracts/actions/index_contracts.rb +108 -0
- data/lib/cuprum/rails/rspec/contracts/actions/new_contracts.rb +111 -0
- data/lib/cuprum/rails/rspec/contracts/actions/show_contracts.rb +72 -0
- data/lib/cuprum/rails/rspec/contracts/actions/update_contracts.rb +263 -0
- data/lib/cuprum/rails/rspec/contracts/actions.rb +8 -0
- data/lib/cuprum/rails/rspec/contracts/command_contracts.rb +479 -0
- data/lib/cuprum/rails/rspec/contracts/responder_contracts.rb +232 -0
- data/lib/cuprum/rails/rspec/contracts/routes_contracts.rb +363 -0
- data/lib/cuprum/rails/rspec/contracts/serializers_contracts.rb +70 -0
- data/lib/cuprum/rails/rspec/contracts.rb +8 -0
- data/lib/cuprum/rails/rspec/matchers/be_a_result_matcher.rb +64 -0
- data/lib/cuprum/rails/rspec/matchers.rb +41 -0
- data/lib/cuprum/rails/serializers/base_serializer.rb +60 -0
- data/lib/cuprum/rails/serializers/context.rb +84 -0
- data/lib/cuprum/rails/serializers/json/active_record_serializer.rb +2 -2
- data/lib/cuprum/rails/serializers/json/array_serializer.rb +9 -8
- data/lib/cuprum/rails/serializers/json/attributes_serializer.rb +95 -172
- data/lib/cuprum/rails/serializers/json/error_serializer.rb +2 -2
- data/lib/cuprum/rails/serializers/json/hash_serializer.rb +9 -8
- data/lib/cuprum/rails/serializers/json/identity_serializer.rb +3 -3
- data/lib/cuprum/rails/serializers/json/properties_serializer.rb +252 -0
- data/lib/cuprum/rails/serializers/json.rb +2 -1
- data/lib/cuprum/rails/serializers.rb +3 -1
- data/lib/cuprum/rails/version.rb +1 -1
- data/lib/cuprum/rails.rb +19 -16
- metadata +73 -131
- data/lib/cuprum/rails/controller_action.rb +0 -121
- data/lib/cuprum/rails/errors/missing_parameters.rb +0 -33
- data/lib/cuprum/rails/errors/missing_primary_key.rb +0 -46
- data/lib/cuprum/rails/errors/undefined_permitted_attributes.rb +0 -34
- data/lib/cuprum/rails/rspec/command_contract.rb +0 -460
- data/lib/cuprum/rails/rspec/define_route_contract.rb +0 -84
- data/lib/cuprum/rails/serializers/json/serializer.rb +0 -66
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails/actions/middleware'
|
4
|
+
|
5
|
+
module Cuprum::Rails::Actions::Middleware
|
6
|
+
# Configurable middleware for logging controller requests.
|
7
|
+
class LogRequest < Cuprum::Command
|
8
|
+
include Cuprum::Middleware
|
9
|
+
|
10
|
+
DEFAULT_CONFIGURATION = {
|
11
|
+
authorization: false,
|
12
|
+
headers: false,
|
13
|
+
repository: false,
|
14
|
+
resource: false
|
15
|
+
}.freeze
|
16
|
+
private_constant :DEFAULT_CONFIGURATION
|
17
|
+
|
18
|
+
REQUEST_PROPERTIES = %i[
|
19
|
+
action_name
|
20
|
+
authorization
|
21
|
+
body_params
|
22
|
+
controller_name
|
23
|
+
format
|
24
|
+
headers
|
25
|
+
http_method
|
26
|
+
params
|
27
|
+
path
|
28
|
+
path_params
|
29
|
+
query_params
|
30
|
+
].freeze
|
31
|
+
private_constant :REQUEST_PROPERTIES
|
32
|
+
|
33
|
+
# @param config [Hash] request and environment properties to log. A value of
|
34
|
+
# false will disable logging for that property.
|
35
|
+
#
|
36
|
+
# @option config action_name [Boolean] if true, logs the name of the action.
|
37
|
+
# Defaults to true.
|
38
|
+
# @option config authorization [Boolean] if true, logs the value of the
|
39
|
+
# authorization header. Defaults to false.
|
40
|
+
# @option config body_params [Boolean] if true, logs the parameters from the
|
41
|
+
# request body. Defaults to true.
|
42
|
+
# @option config controller_name [Boolean] if true, logs the name of the
|
43
|
+
# controller. Defaults to true.
|
44
|
+
# @option config format [Boolean] if true, logs the request format. Defaults
|
45
|
+
# to true.
|
46
|
+
# @option config headers [Boolean] if true, logs the headers. Defaults to
|
47
|
+
# false.
|
48
|
+
# @option config http_methopd [Boolean] if true, logs the request HTTP
|
49
|
+
# method. Defaults to true.
|
50
|
+
# @option config params [Boolean] if true, logs the request parameters.
|
51
|
+
# Defaults to true.
|
52
|
+
# @option config path [Boolean] if true, logs the request path. Defaults to
|
53
|
+
# true.
|
54
|
+
# @option config path_params [Boolean] if true, logs the parameters from the
|
55
|
+
# request path. Defaults to true.
|
56
|
+
# @option config query_params [Boolean] if true, logs the parameters from
|
57
|
+
# the url query. Defaults to true.
|
58
|
+
# @option config repository [Boolean] if true, logs the repository used in
|
59
|
+
# the request. Defaults to false.
|
60
|
+
# @option config resource [Boolean] if true, logs the resource used in the
|
61
|
+
# request. Defaults to false.
|
62
|
+
def initialize(**config)
|
63
|
+
super()
|
64
|
+
|
65
|
+
@config = DEFAULT_CONFIGURATION.merge(config)
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [Hash] request and environment properties to log.
|
69
|
+
attr_reader :config
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def format_log(request:, **options)
|
74
|
+
msg = +" #{self.class.name}#process"
|
75
|
+
|
76
|
+
logged_properties(request: request, **options).each do |label, formatted|
|
77
|
+
msg << "\n #{label}\n"
|
78
|
+
msg << tools.string_tools.indent(formatted, 6)
|
79
|
+
end
|
80
|
+
|
81
|
+
msg
|
82
|
+
end
|
83
|
+
|
84
|
+
def log_property?(name)
|
85
|
+
config.fetch(name.intern, true)
|
86
|
+
end
|
87
|
+
|
88
|
+
def logged_properties(request:, repository: nil, resource: nil, **options)
|
89
|
+
hsh = {}
|
90
|
+
|
91
|
+
if log_property?(:command_options) && options.present?
|
92
|
+
hsh['Command Options'] = options.pretty_inspect
|
93
|
+
end
|
94
|
+
|
95
|
+
if log_property?(:repository)
|
96
|
+
hsh['Repository'] = repository.pretty_inspect
|
97
|
+
end
|
98
|
+
|
99
|
+
hsh['Resource'] = resource.pretty_inspect if log_property?(:resource)
|
100
|
+
|
101
|
+
hsh.merge(request_properties(request: request))
|
102
|
+
end
|
103
|
+
|
104
|
+
def process(next_command, request:, **options)
|
105
|
+
Rails.logger.info format_log(request: request, **options)
|
106
|
+
|
107
|
+
next_command.call(request: request, **options)
|
108
|
+
end
|
109
|
+
|
110
|
+
def request_properties(request:)
|
111
|
+
hsh = {}
|
112
|
+
|
113
|
+
REQUEST_PROPERTIES.each do |key|
|
114
|
+
next unless log_property?(key)
|
115
|
+
|
116
|
+
hsh[key.to_s.titleize] = request.send(key).pretty_inspect
|
117
|
+
end
|
118
|
+
|
119
|
+
hsh
|
120
|
+
end
|
121
|
+
|
122
|
+
def tools
|
123
|
+
SleepingKingStudios::Tools::Toolbelt.instance
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails/actions/middleware'
|
4
|
+
|
5
|
+
module Cuprum::Rails::Actions::Middleware
|
6
|
+
# Middleware for logging controller action results.
|
7
|
+
class LogResult < Cuprum::Command
|
8
|
+
include Cuprum::Middleware
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def format_log(result:)
|
13
|
+
msg = +" #{self.class.name}#process"
|
14
|
+
|
15
|
+
logged_properties(result: result).each do |label, formatted|
|
16
|
+
msg << "\n #{label}\n"
|
17
|
+
msg << tools.string_tools.indent(formatted, 6)
|
18
|
+
end
|
19
|
+
|
20
|
+
msg
|
21
|
+
end
|
22
|
+
|
23
|
+
def logged_properties(result:)
|
24
|
+
hsh = {
|
25
|
+
status: result.status,
|
26
|
+
value: result.value,
|
27
|
+
error: result.error
|
28
|
+
}
|
29
|
+
|
30
|
+
hsh
|
31
|
+
.merge(result.properties.except(:status, :value, :error))
|
32
|
+
.to_h { |key, value| [key.to_s.titleize, value.pretty_inspect] }
|
33
|
+
end
|
34
|
+
|
35
|
+
def process(next_command, **options)
|
36
|
+
result = next_command.call(**options)
|
37
|
+
|
38
|
+
if result.success?
|
39
|
+
Rails.logger.info format_log(result: result)
|
40
|
+
else
|
41
|
+
Rails.logger.error format_log(result: result)
|
42
|
+
end
|
43
|
+
|
44
|
+
result
|
45
|
+
end
|
46
|
+
|
47
|
+
def tools
|
48
|
+
SleepingKingStudios::Tools::Toolbelt.instance
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails/actions/middleware/resources'
|
4
|
+
require 'cuprum/rails/actions/middleware/resources/query'
|
5
|
+
|
6
|
+
module Cuprum::Rails::Actions::Middleware::Resources
|
7
|
+
# Middleware for querying a resource.
|
8
|
+
class Find < Cuprum::Rails::Actions::Middleware::Resources::Query
|
9
|
+
# @param only_form_actions [Boolean] if true, does not query the resource
|
10
|
+
# for non-GET success results. Defaults to false.
|
11
|
+
# @param resource_params [Hash] parameters to pass to the resource.
|
12
|
+
def initialize(only_form_actions: false, **resource_params)
|
13
|
+
super(**resource_params)
|
14
|
+
|
15
|
+
@only_form_actions = !!only_form_actions
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [Boolean] if true, does not query the resource for non-GET success
|
19
|
+
# results.
|
20
|
+
def only_form_actions?
|
21
|
+
@only_form_actions
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def process(next_command, **)
|
27
|
+
super
|
28
|
+
|
29
|
+
return result if skip_query?
|
30
|
+
|
31
|
+
values = step { perform_query }
|
32
|
+
|
33
|
+
merge_result(result: result, values: values)
|
34
|
+
end
|
35
|
+
|
36
|
+
def skip_query?
|
37
|
+
return false unless only_form_actions?
|
38
|
+
|
39
|
+
return false if request.http_method.to_s.downcase == 'get'
|
40
|
+
|
41
|
+
result.success?
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/commands/associations/find_many'
|
4
|
+
|
5
|
+
require 'cuprum/rails/actions/middleware/resources'
|
6
|
+
require 'cuprum/rails/result'
|
7
|
+
|
8
|
+
module Cuprum::Rails::Actions::Middleware::Resources
|
9
|
+
# Abstract middleware for performing a resource query.
|
10
|
+
class Query < Cuprum::Rails::Action
|
11
|
+
include Cuprum::Middleware
|
12
|
+
|
13
|
+
# @param resource_params [Hash] parameters to pass to the resource.
|
14
|
+
def initialize(**resource_params)
|
15
|
+
super()
|
16
|
+
|
17
|
+
@resource = build_resource(**resource_params)
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Cuprum::Collections::Resource] the resource.
|
21
|
+
attr_reader :resource
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_reader :repository
|
26
|
+
|
27
|
+
attr_reader :request
|
28
|
+
|
29
|
+
attr_reader :result
|
30
|
+
|
31
|
+
def build_resource(**params)
|
32
|
+
Cuprum::Collections::Resource.new(**params)
|
33
|
+
end
|
34
|
+
|
35
|
+
def collection
|
36
|
+
repository.find_or_create(
|
37
|
+
name: resource.name,
|
38
|
+
qualified_name: resource.qualified_name
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
def merge_result(result:, values:)
|
43
|
+
return result unless result.value.is_a?(Hash)
|
44
|
+
|
45
|
+
Cuprum::Rails::Result.new(
|
46
|
+
**result.properties,
|
47
|
+
value: merge_values(
|
48
|
+
value: result.value,
|
49
|
+
values: values
|
50
|
+
)
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
def merge_values(value:, values:)
|
55
|
+
key = pluralize_name(resource: resource, values: values)
|
56
|
+
|
57
|
+
value.merge(key => values)
|
58
|
+
end
|
59
|
+
|
60
|
+
def pluralize_name(resource:, values:)
|
61
|
+
values.is_a?(Array) ? resource.plural_name : resource.singular_name
|
62
|
+
end
|
63
|
+
|
64
|
+
def process(next_command, repository:, request:, **rest)
|
65
|
+
@repository = repository
|
66
|
+
@request = request
|
67
|
+
@result = next_command.call(
|
68
|
+
repository: repository,
|
69
|
+
request: request,
|
70
|
+
resource: resource,
|
71
|
+
**rest
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
def perform_query
|
76
|
+
if resource.singular?
|
77
|
+
values = step { query_command.call(limit: 1) }
|
78
|
+
|
79
|
+
success(values.first)
|
80
|
+
else
|
81
|
+
values = step { query_command.call }
|
82
|
+
|
83
|
+
success(values.to_a)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def query_command
|
88
|
+
collection.find_matching
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails/actions/middleware'
|
4
|
+
|
5
|
+
module Cuprum::Rails::Actions::Middleware
|
6
|
+
# Namespace for resource middleware.
|
7
|
+
module Resources
|
8
|
+
autoload :Find, 'cuprum/rails/actions/middleware/resources/find'
|
9
|
+
autoload :Query, 'cuprum/rails/actions/middleware/resources/query'
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails/actions'
|
4
|
+
|
5
|
+
module Cuprum::Rails::Actions
|
6
|
+
# Namespace for action middleware, which wraps controller actions.
|
7
|
+
module Middleware
|
8
|
+
autoload :Associations, 'cuprum/rails/actions/middleware/associations'
|
9
|
+
autoload :LogRequest, 'cuprum/rails/actions/middleware/log_request'
|
10
|
+
autoload :LogResult, 'cuprum/rails/actions/middleware/log_result'
|
11
|
+
autoload :Resources, 'cuprum/rails/actions/middleware/resources'
|
12
|
+
end
|
13
|
+
end
|
@@ -8,12 +8,24 @@ module Cuprum::Rails::Actions
|
|
8
8
|
class New < Cuprum::Rails::Actions::ResourceAction
|
9
9
|
private
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
attr_reader :entity
|
12
|
+
|
13
|
+
def build_entity
|
14
|
+
collection.build_one.call(attributes: {})
|
15
|
+
end
|
16
|
+
|
17
|
+
def build_response
|
18
|
+
{ resource.singular_name => entity }
|
19
|
+
end
|
20
|
+
|
21
|
+
def perform_action
|
22
|
+
@entity = step { build_entity }
|
23
|
+
end
|
13
24
|
|
14
|
-
|
25
|
+
def process(**)
|
26
|
+
@entity = nil
|
15
27
|
|
16
|
-
|
28
|
+
super
|
17
29
|
end
|
18
30
|
end
|
19
31
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sleeping_king_studios/tools/toolbox/mixin'
|
4
|
+
|
5
|
+
require 'cuprum/rails/actions'
|
6
|
+
require 'cuprum/rails/constraints/parameters_contract'
|
7
|
+
|
8
|
+
module Cuprum::Rails::Actions
|
9
|
+
# Mixin for adding parameter validation to an Action.
|
10
|
+
module ParameterValidation
|
11
|
+
extend SleepingKingStudios::Tools::Toolbox::Mixin
|
12
|
+
|
13
|
+
# Class methods to extend when including the mixin.
|
14
|
+
module ClassMethods
|
15
|
+
# @overload validate_parameters(contract)
|
16
|
+
# Sets the contract to automatically validate the request parameters.
|
17
|
+
#
|
18
|
+
# @param contract [Stannum::Contract] the contract used to validate the
|
19
|
+
# request parameters.
|
20
|
+
#
|
21
|
+
# @overload validate_parameters(&block)
|
22
|
+
# Defines a contract to automatically validate the request parameters.
|
23
|
+
#
|
24
|
+
# @yield Used to create an indifferent hash contract to validate the
|
25
|
+
# request parameters.
|
26
|
+
def validate_parameters(contract = nil, &block)
|
27
|
+
contract ||= Cuprum::Rails::Constraints::ParametersContract.new(&block)
|
28
|
+
|
29
|
+
define_method(:parameters_contract) { contract }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def parameters_contract
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def process(request:, **)
|
40
|
+
super
|
41
|
+
|
42
|
+
return unless validate_parameters?
|
43
|
+
|
44
|
+
step { validate_parameters(parameters_contract) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def validate_parameters(contract)
|
48
|
+
match, errors = contract.match(params)
|
49
|
+
|
50
|
+
return success(nil) if match
|
51
|
+
|
52
|
+
error = Cuprum::Rails::Errors::InvalidParameters.new(errors: errors)
|
53
|
+
failure(error)
|
54
|
+
end
|
55
|
+
|
56
|
+
def validate_parameters?
|
57
|
+
!parameters_contract.nil?
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -1,75 +1,152 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'cuprum/errors/uncaught_exception'
|
4
4
|
|
5
|
-
require 'cuprum/rails/
|
6
|
-
require 'cuprum/rails/
|
7
|
-
require 'cuprum/rails/errors/
|
8
|
-
require 'cuprum/rails/errors/undefined_permitted_attributes'
|
5
|
+
require 'cuprum/rails/action'
|
6
|
+
require 'cuprum/rails/actions/parameter_validation'
|
7
|
+
require 'cuprum/rails/errors/resource_error'
|
9
8
|
|
10
9
|
module Cuprum::Rails::Actions
|
11
10
|
# Abstract base class for resourceful actions.
|
11
|
+
#
|
12
|
+
# Each ResourceAction defines a series of steps used to validate and process
|
13
|
+
# the action. Each step is performed in order:
|
14
|
+
#
|
15
|
+
# - The #find_required_entities step locates any dependent entities and
|
16
|
+
# ensures that the entities exist. For example, it may find the requested
|
17
|
+
# entity for a show action, or the parent entity for a create action on a
|
18
|
+
# nested resource.
|
19
|
+
# - The #perform_action step contains the core logic of the action. For
|
20
|
+
# example, in a destroy action it would take the entity (located in the
|
21
|
+
# previous step) and remove it from the collection.
|
22
|
+
# - The #build_response step generates the result returned by a successful
|
23
|
+
# request. For example, for a show action on a nested resource, it might
|
24
|
+
# build a passing result with both the requested and parent resources.
|
25
|
+
#
|
26
|
+
# If any of the steps fail, either by returning a failing result or by raising
|
27
|
+
# an exception, the action will immediately stop execution and return the
|
28
|
+
# result, or wrap the exception in a failing result with a
|
29
|
+
# Cuprum::Errors::UncaughtException error.
|
12
30
|
class ResourceAction < Cuprum::Rails::Action
|
13
|
-
|
14
|
-
|
15
|
-
#
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
31
|
+
include Cuprum::Rails::Actions::ParameterValidation
|
32
|
+
|
33
|
+
# @!method call(request:, resource:, repository: nil, **options)
|
34
|
+
# Performs the controller action.
|
35
|
+
#
|
36
|
+
# Subclasses should implement a #process method with the :request keyword,
|
37
|
+
# which accepts an ActionDispatch::Request instance.
|
38
|
+
#
|
39
|
+
# @param request [ActionDispatch::Request] the Rails request.
|
40
|
+
# @param resource [Cuprum::Rails::Resource] the controller resource.
|
41
|
+
# @param repository [Cuprum::Collections::Repository] the repository
|
42
|
+
# containing the data collections for the application or scope.
|
43
|
+
# @param options [Hash<Symbol, Object>] additional options for the action.
|
44
|
+
#
|
45
|
+
# @return [Cuprum::Result] the result of the action.
|
46
|
+
|
47
|
+
# @return [Cuprum::Rails::Collection] the collection for the resource class.
|
48
|
+
def collection
|
49
|
+
@collection ||= repository.find_or_create(
|
50
|
+
qualified_name: resource.qualified_name
|
51
|
+
)
|
22
52
|
end
|
23
53
|
|
24
|
-
|
25
|
-
|
26
|
-
:resource_name,
|
27
|
-
:singular_resource_name
|
54
|
+
# @return [Cuprum::Rails::Resource] the controller resource.
|
55
|
+
attr_reader :resource
|
28
56
|
|
29
57
|
# @return [Object] the primary key for the resource.
|
30
58
|
def resource_id
|
31
|
-
|
32
|
-
|
33
|
-
failure(missing_primary_key_error)
|
59
|
+
@resource_id ||= params['id']
|
34
60
|
end
|
35
61
|
|
36
62
|
# @return [Hash] the permitted params for the resource.
|
37
63
|
def resource_params
|
38
|
-
return
|
64
|
+
return @resource_params if @resource_params
|
65
|
+
|
66
|
+
resource_params = params.fetch(resource.singular_name, {})
|
39
67
|
|
40
|
-
|
41
|
-
|
42
|
-
|
68
|
+
return resource_params unless resource_params.is_a?(Hash)
|
69
|
+
|
70
|
+
@resource_params =
|
71
|
+
resource_params
|
72
|
+
.select { |key, _| permitted_attributes.include?(key) }
|
73
|
+
.to_h
|
43
74
|
end
|
44
75
|
|
45
76
|
private
|
46
77
|
|
47
|
-
def
|
48
|
-
Cuprum::
|
49
|
-
.new(resource_name: singular_resource_name)
|
78
|
+
def build_response
|
79
|
+
Cuprum::Result.new(status: :success)
|
50
80
|
end
|
51
81
|
|
52
|
-
def
|
53
|
-
|
54
|
-
|
55
|
-
|
82
|
+
def find_required_entities; end
|
83
|
+
|
84
|
+
def handle_exceptions
|
85
|
+
yield
|
86
|
+
rescue StandardError => exception
|
87
|
+
error = Cuprum::Errors::UncaughtException.new(
|
88
|
+
exception: exception,
|
89
|
+
message: "uncaught exception in #{self.class.name} -"
|
56
90
|
)
|
91
|
+
failure(error)
|
92
|
+
end
|
93
|
+
|
94
|
+
def permitted_attributes
|
95
|
+
@permitted_attributes ||=
|
96
|
+
Set.new(resource.permitted_attributes.map(&:to_s))
|
97
|
+
end
|
98
|
+
|
99
|
+
def perform_action; end
|
100
|
+
|
101
|
+
def process(resource:, **rest)
|
102
|
+
@resource = resource
|
103
|
+
@resource_id = nil
|
104
|
+
@resource_params = nil
|
105
|
+
|
106
|
+
step { require_permitted_attributes }
|
107
|
+
|
108
|
+
super(**rest)
|
109
|
+
|
110
|
+
handle_exceptions do
|
111
|
+
step { find_required_entities }
|
112
|
+
step { perform_action }
|
113
|
+
step { build_response }
|
114
|
+
end
|
57
115
|
end
|
58
116
|
|
59
|
-
def
|
60
|
-
|
117
|
+
def require_permitted_attributes
|
118
|
+
return unless require_permitted_attributes?
|
119
|
+
|
120
|
+
return if resource.permitted_attributes.present?
|
121
|
+
|
122
|
+
error = Cuprum::Rails::Errors::ResourceError.new(
|
123
|
+
message: "permitted attributes can't be blank",
|
124
|
+
resource: resource
|
125
|
+
)
|
126
|
+
failure(error)
|
61
127
|
end
|
62
128
|
|
63
|
-
def
|
64
|
-
|
65
|
-
.new(resource_name: singular_resource_name)
|
129
|
+
def require_permitted_attributes?
|
130
|
+
false
|
66
131
|
end
|
67
132
|
|
68
|
-
def
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
.
|
133
|
+
def transaction(&block) # rubocop:disable Metrics/MethodLength
|
134
|
+
result = nil
|
135
|
+
entity_class = resource.entity_class
|
136
|
+
transaction_class =
|
137
|
+
if entity_class.is_a?(Class) && entity_class < ActiveRecord::Base
|
138
|
+
entity_class
|
139
|
+
else
|
140
|
+
ActiveRecord::Base
|
141
|
+
end
|
142
|
+
|
143
|
+
transaction_class.transaction do
|
144
|
+
result = steps { block.call }
|
145
|
+
|
146
|
+
raise ActiveRecord::Rollback if result.failure?
|
147
|
+
end
|
148
|
+
|
149
|
+
result
|
73
150
|
end
|
74
151
|
end
|
75
152
|
end
|
@@ -8,15 +8,31 @@ module Cuprum::Rails::Actions
|
|
8
8
|
class Show < Cuprum::Rails::Actions::ResourceAction
|
9
9
|
private
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
attr_reader :entity
|
12
|
+
|
13
|
+
def build_response
|
14
|
+
{ resource.singular_name => entity }
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_entity(primary_key:)
|
18
|
+
collection.find_one.call(primary_key: primary_key)
|
19
|
+
end
|
13
20
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
21
|
+
def parameters_contract
|
22
|
+
@parameters_contract ||=
|
23
|
+
Cuprum::Rails::Constraints::ParametersContract.new do
|
24
|
+
key 'id', Stannum::Constraints::Presence.new
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def perform_action
|
29
|
+
@entity = step { find_entity(primary_key: resource_id) }
|
30
|
+
end
|
18
31
|
|
19
|
-
|
32
|
+
def process(**)
|
33
|
+
@entity = nil
|
34
|
+
|
35
|
+
super
|
20
36
|
end
|
21
37
|
end
|
22
38
|
end
|