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,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
- def process(request:)
12
- super
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
- instance = step { collection.build_one.call(attributes: {}) }
25
+ def process(**)
26
+ @entity = nil
15
27
 
16
- { singular_resource_name => instance }
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 'forwardable'
3
+ require 'cuprum/errors/uncaught_exception'
4
4
 
5
- require 'cuprum/rails/actions'
6
- require 'cuprum/rails/errors/missing_parameters'
7
- require 'cuprum/rails/errors/missing_primary_key'
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
- extend Forwardable
14
-
15
- # @param resource [Cuprum::Rails::Resource] The controller resource.
16
- def initialize(resource:)
17
- if resource.collection.nil?
18
- raise ArgumentError, 'resource must have a collection'
19
- end
20
-
21
- super
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
- def_delegators :@resource,
25
- :collection,
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
- return success(params[:id]) if params[:id].present?
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 failure(permitted_attributes_error) unless permitted_attributes?
64
+ return @resource_params if @resource_params
65
+
66
+ resource_params = params.fetch(resource.singular_name, {})
39
67
 
40
- success(raw_resource_params)
41
- rescue ActionController::ParameterMissing
42
- failure(missing_parameters_error)
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 missing_parameters_error
48
- Cuprum::Rails::Errors::MissingParameters
49
- .new(resource_name: singular_resource_name)
78
+ def build_response
79
+ Cuprum::Result.new(status: :success)
50
80
  end
51
81
 
52
- def missing_primary_key_error
53
- Cuprum::Rails::Errors::MissingPrimaryKey.new(
54
- primary_key: resource.primary_key,
55
- resource_name: singular_resource_name
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 permitted_attributes?
60
- !resource.permitted_attributes.nil?
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 permitted_attributes_error
64
- Cuprum::Rails::Errors::UndefinedPermittedAttributes
65
- .new(resource_name: singular_resource_name)
129
+ def require_permitted_attributes?
130
+ false
66
131
  end
67
132
 
68
- def raw_resource_params
69
- params
70
- .require(singular_resource_name)
71
- .permit(*resource.permitted_attributes)
72
- .to_hash
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
- def process(request:)
12
- super
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
- primary_key = step { resource_id }
15
- entity = step do
16
- collection.find_one.call(primary_key: primary_key)
17
- end
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
- { singular_resource_name => entity }
32
+ def process(**)
33
+ @entity = nil
34
+
35
+ super
20
36
  end
21
37
  end
22
38
  end