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
@@ -10,15 +10,10 @@ module Cuprum::Rails::Actions
10
10
  class Update < Cuprum::Rails::Actions::ResourceAction
11
11
  private
12
12
 
13
- def assign_resource
14
- primary_key = step { resource_id }
15
- attributes = step { resource_params }
16
- entity = step do
17
- collection.find_one.call(primary_key: primary_key)
18
- end
19
- step do
20
- collection.assign_one.call(attributes: attributes, entity: entity)
21
- end
13
+ attr_reader :entity
14
+
15
+ def build_response
16
+ { resource.singular_name => entity }
22
17
  end
23
18
 
24
19
  def failed_validation?(result)
@@ -26,34 +21,87 @@ module Cuprum::Rails::Actions
26
21
  result.error.is_a?(Cuprum::Collections::Errors::FailedValidation)
27
22
  end
28
23
 
29
- def process(request:)
30
- super
24
+ def find_entity(primary_key:)
25
+ collection.find_one.call(primary_key: primary_key)
26
+ end
27
+
28
+ def find_required_entities
29
+ @entity = step { find_entity(primary_key: resource_id) }
30
+ end
31
31
 
32
- entity, result = update_resource
32
+ def handle_failed_validation
33
+ result = yield
33
34
 
34
35
  return result unless failed_validation?(result)
35
36
 
36
37
  Cuprum::Result.new(
37
- error: result.error,
38
+ error: scope_validation_errors(result.error),
38
39
  status: :failure,
39
- value: { singular_resource_name => entity }
40
+ value: { resource.singular_name => entity }
40
41
  )
41
42
  end
42
43
 
43
- def update_resource
44
- entity = nil
44
+ def parameters_contract
45
+ return @parameters_contract if @parameters_contract
45
46
 
46
- result = steps do
47
- entity = assign_resource
47
+ resource_name = resource.singular_name
48
+ parameters_constraint = require_parameters_constraint
48
49
 
49
- step { collection.validate_one.call(entity: entity) }
50
+ @parameters_contract =
51
+ Cuprum::Rails::Constraints::ParametersContract.new do
52
+ key 'id', Stannum::Constraints::Presence.new
53
+ key resource_name, parameters_constraint
54
+ end
55
+ end
50
56
 
51
- step { collection.update_one.call(entity: entity) }
57
+ def perform_action
58
+ handle_failed_validation do
59
+ update_entity(attributes: resource_params)
60
+ end
61
+ end
62
+
63
+ def process(**)
64
+ @entity = nil
65
+
66
+ super
67
+ end
68
+
69
+ def require_parameters_constraint
70
+ Stannum::Contract.new do
71
+ constraint Stannum::Constraints::Presence.new, sanity: true
72
+ constraint Stannum::Constraints::Types::HashType.new
73
+ end
74
+ end
52
75
 
53
- { singular_resource_name => entity }
76
+ def require_permitted_attributes?
77
+ true
78
+ end
79
+
80
+ def scope_validation_errors(error)
81
+ mapped_errors = Stannum::Errors.new
82
+
83
+ error.errors.each do |err|
84
+ mapped_errors
85
+ .dig(resource.singular_name, *err[:path].map(&:to_s))
86
+ .add(err[:type], message: err[:message], **err[:data])
54
87
  end
55
88
 
56
- [entity, result]
89
+ Cuprum::Collections::Errors::FailedValidation.new(
90
+ entity_class: error.entity_class,
91
+ errors: mapped_errors
92
+ )
93
+ end
94
+
95
+ def update_entity(attributes:)
96
+ steps do
97
+ step do
98
+ collection.assign_one.call(attributes: attributes, entity: entity)
99
+ end
100
+
101
+ step { collection.validate_one.call(entity: entity) }
102
+
103
+ step { collection.update_one.call(entity: entity) }
104
+ end
57
105
  end
58
106
  end
59
107
  end
@@ -5,12 +5,16 @@ require 'cuprum/rails'
5
5
  module Cuprum::Rails
6
6
  # Namespace for defined resourceful actions.
7
7
  module Actions
8
- autoload :Create, 'cuprum/rails/actions/create'
9
- autoload :Destroy, 'cuprum/rails/actions/destroy'
10
- autoload :Edit, 'cuprum/rails/actions/edit'
11
- autoload :Index, 'cuprum/rails/actions/index'
12
- autoload :New, 'cuprum/rails/actions/new'
13
- autoload :Show, 'cuprum/rails/actions/show'
14
- autoload :Update, 'cuprum/rails/actions/update'
8
+ autoload :Create, 'cuprum/rails/actions/create'
9
+ autoload :Destroy, 'cuprum/rails/actions/destroy'
10
+ autoload :Edit, 'cuprum/rails/actions/edit'
11
+ autoload :Index, 'cuprum/rails/actions/index'
12
+ autoload :Middleware, 'cuprum/rails/actions/middleware'
13
+ autoload :New, 'cuprum/rails/actions/new'
14
+ autoload :ParameterValidation, 'cuprum/rails/actions/parameter_validation'
15
+ autoload :ResourceAction, 'cuprum/rails/actions/resource_action'
16
+ autoload :ResourceMethods, 'cuprum/rails/actions/resource_methods'
17
+ autoload :Show, 'cuprum/rails/actions/show'
18
+ autoload :Update, 'cuprum/rails/actions/update'
15
19
  end
16
20
  end
@@ -1,42 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'cuprum/collections/collection'
3
4
  require 'cuprum/command_factory'
4
5
 
5
6
  require 'cuprum/rails'
6
7
 
7
8
  module Cuprum::Rails
8
9
  # Wraps an ActiveRecord model as a Cuprum collection.
9
- class Collection < Cuprum::CommandFactory
10
- # @param collection_name [String, Symbol] The name of the collection.
11
- # @param member_name [String] The name of a collection entity.
12
- # @param options [Hash<Symbol>] Additional options for the command.
13
- # @param record_class [Class] The ActiveRecord class for the collection.
14
- def initialize(
15
- record_class:,
16
- collection_name: nil,
17
- member_name: nil,
18
- **options
19
- )
20
- super()
21
-
22
- @collection_name = resolve_collection_name(collection_name, record_class)
23
- @member_name = resolve_member_name(@collection_name, member_name)
24
- @record_class = record_class
25
- @options = options
10
+ class Collection < Cuprum::Collections::Collection
11
+ # @overload initialize(entity_class: nil, name: nil, qualified_name: nil, singular_name: nil, **options)
12
+ # @param entity_class [Class, String] the class of entity represented by
13
+ # the collection.
14
+ # @param name [String] the name of the collection.
15
+ # @param qualified_name [String] a scoped name for the collection.
16
+ # @param singular_name [String] the name of an entity in the collection.
17
+ # @param options [Hash] additional options for the collection.
18
+ #
19
+ # @option options primary_key_name [String] the name of the primary key
20
+ # attribute. Defaults to 'id'.
21
+ # @option primary_key_type [Class, Stannum::Constraint] the type of
22
+ # the primary key attribute. Defaults to Integer.
23
+ def initialize(**params)
24
+ params = disambiguate_keyword(params, :entity_class, :record_class)
25
+
26
+ super(**params)
26
27
  end
27
28
 
28
- # @return [String] The name of the collection.
29
- attr_reader :collection_name
30
-
31
- # @return [String] the name of a collection entity.
32
- attr_reader :member_name
33
-
34
- # @return [Hash<Symbol>] additional options for the command.
35
- attr_reader :options
36
-
37
- # @return [Class] the ActiveRecord class for the collection.
38
- attr_reader :record_class
39
-
40
29
  command_class :assign_one do
41
30
  Cuprum::Rails::Commands::AssignOne
42
31
  .subclass(**command_options)
@@ -86,30 +75,21 @@ module Cuprum::Rails
86
75
  #
87
76
  # @return [Cuprum::Rails::Query] the query.
88
77
  def query
89
- Cuprum::Rails::Query.new(record_class)
90
- end
91
-
92
- private
93
-
94
- def command_options
95
- @command_options ||= {
96
- collection_name: collection_name,
97
- member_name: member_name,
98
- record_class: record_class,
99
- **options
100
- }
78
+ Cuprum::Rails::Query.new(entity_class)
101
79
  end
102
80
 
103
- def resolve_collection_name(collection_name, record_class)
104
- return collection_name.to_s unless collection_name.nil?
81
+ # @return [Class] the class of entity represented by the collection.
82
+ def record_class
83
+ tools.core_tools.deprecate '#record_class method',
84
+ message: 'Use #entity_class instead'
105
85
 
106
- record_class.name.underscore.pluralize
86
+ entity_class
107
87
  end
108
88
 
109
- def resolve_member_name(collection_name, member_name)
110
- return member_name.to_s unless member_name.nil?
89
+ private
111
90
 
112
- collection_name.singularize
91
+ def command_options
92
+ super().merge(record_class: entity_class)
113
93
  end
114
94
  end
115
95
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'cuprum/collections'
4
+
3
5
  require 'cuprum/rails'
4
6
 
5
7
  module Cuprum::Rails
@@ -46,7 +48,7 @@ module Cuprum::Rails
46
48
 
47
49
  # @return [Symbol] the name of the primary key attribute.
48
50
  def primary_key_name
49
- @primary_key_name ||= record_class.primary_key.intern
51
+ @primary_key_name ||= record_class.primary_key
50
52
  end
51
53
 
52
54
  # @return [Class] the type of the primary key attribute.
@@ -25,6 +25,15 @@ module Cuprum::Rails::Commands
25
25
 
26
26
  private
27
27
 
28
+ def not_found_error(primary_key)
29
+ Cuprum::Collections::Errors::NotFound.new(
30
+ attribute_name: primary_key_name,
31
+ attribute_value: primary_key,
32
+ collection_name: collection_name,
33
+ primary_key: true
34
+ )
35
+ end
36
+
28
37
  def process(primary_key:)
29
38
  step { validate_primary_key(primary_key) }
30
39
 
@@ -32,12 +41,7 @@ module Cuprum::Rails::Commands
32
41
 
33
42
  entity.destroy
34
43
  rescue ActiveRecord::RecordNotFound
35
- error = Cuprum::Collections::Errors::NotFound.new(
36
- collection_name: collection_name,
37
- primary_key_name: primary_key_name,
38
- primary_key_values: [primary_key]
39
- )
40
- Cuprum::Result.new(error: error)
44
+ Cuprum::Result.new(error: not_found_error(primary_key))
41
45
  end
42
46
  end
43
47
  end
@@ -6,13 +6,14 @@ require 'cuprum/collections/commands/abstract_find_many'
6
6
 
7
7
  require 'cuprum/rails/command'
8
8
  require 'cuprum/rails/commands'
9
+ require 'cuprum/rails/errors/invalid_statement'
9
10
 
10
11
  module Cuprum::Rails::Commands
11
12
  # Command for finding multiple ActiveRecord records by primary key.
12
13
  class FindMany < Cuprum::Rails::Command
13
14
  include Cuprum::Collections::Commands::AbstractFindMany
14
15
 
15
- # @!method call(primary_keys:, allow_partial: false, envelope: false, scope: nil) # rubocop:disable Layout/LineLength
16
+ # @!method call(primary_keys:, allow_partial: false, envelope: false, scope: nil)
16
17
  # Queries the collection for the records with the given primary keys.
17
18
  #
18
19
  # The command will find and return the entities with the given primary
@@ -46,6 +47,10 @@ module Cuprum::Rails::Commands
46
47
  Cuprum::Rails::Query.new(record_class)
47
48
  end
48
49
 
50
+ def invalid_statement_error(message)
51
+ Cuprum::Rails::Errors::InvalidStatement.new(message: message)
52
+ end
53
+
49
54
  def process(
50
55
  primary_keys:,
51
56
  allow_partial: false,
@@ -55,6 +60,8 @@ module Cuprum::Rails::Commands
55
60
  step { validate_primary_keys(primary_keys) }
56
61
 
57
62
  super
63
+ rescue ActiveRecord::StatementInvalid => exception
64
+ failure(invalid_statement_error(exception.message))
58
65
  end
59
66
  end
60
67
  end
@@ -12,7 +12,7 @@ module Cuprum::Rails::Commands
12
12
  class FindMatching < Cuprum::Rails::Command
13
13
  include Cuprum::Collections::Commands::AbstractFindMatching
14
14
 
15
- # @!method call(envelope: false, limit: nil, offset: nil, order: nil, scope: nil, where: nil, &block) # rubocop:disable Layout/LineLength
15
+ # @!method call(envelope: false, limit: nil, offset: nil, order: nil, scope: nil, where: nil, &block)
16
16
  # Queries the collection for records matching the given conditions.
17
17
  #
18
18
  # @param envelope [Boolean] If true, wraps the result value in a Hash.
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'stannum/constraints/boolean'
4
+ require 'stannum/errors'
4
5
 
5
6
  require 'cuprum/collections/commands/abstract_find_one'
6
7
 
7
8
  require 'cuprum/rails/command'
8
9
  require 'cuprum/rails/commands'
10
+ require 'cuprum/rails/errors/invalid_statement'
9
11
 
10
12
  module Cuprum::Rails::Commands
11
13
  # Command for finding one ActiveRecord record by primary key.
@@ -41,10 +43,16 @@ module Cuprum::Rails::Commands
41
43
  Cuprum::Rails::Query.new(record_class)
42
44
  end
43
45
 
46
+ def invalid_statement_error(message)
47
+ Cuprum::Rails::Errors::InvalidStatement.new(message: message)
48
+ end
49
+
44
50
  def process(primary_key:, envelope: false, scope: nil)
45
51
  step { validate_primary_key(primary_key) }
46
52
 
47
53
  super
54
+ rescue ActiveRecord::StatementInvalid => exception
55
+ failure(invalid_statement_error(exception.message))
48
56
  end
49
57
  end
50
58
  end
@@ -4,6 +4,7 @@ require 'cuprum/collections/errors/already_exists'
4
4
 
5
5
  require 'cuprum/rails/command'
6
6
  require 'cuprum/rails/commands'
7
+ require 'cuprum/rails/errors/invalid_statement'
7
8
 
8
9
  module Cuprum::Rails::Commands
9
10
  # Command for inserting an ActiveRecord record into the collection.
@@ -23,6 +24,19 @@ module Cuprum::Rails::Commands
23
24
 
24
25
  private
25
26
 
27
+ def already_exists_error(primary_key)
28
+ Cuprum::Collections::Errors::AlreadyExists.new(
29
+ attribute_name: primary_key_name,
30
+ attribute_value: primary_key,
31
+ collection_name: collection_name,
32
+ primary_key: true
33
+ )
34
+ end
35
+
36
+ def invalid_statement_error(message)
37
+ Cuprum::Rails::Errors::InvalidStatement.new(message: message)
38
+ end
39
+
26
40
  def process(entity:)
27
41
  step { validate_entity(entity) }
28
42
 
@@ -30,12 +44,9 @@ module Cuprum::Rails::Commands
30
44
 
31
45
  entity
32
46
  rescue ActiveRecord::RecordNotUnique
33
- error = Cuprum::Collections::Errors::AlreadyExists.new(
34
- collection_name: collection_name,
35
- primary_key_name: primary_key_name,
36
- primary_key_values: entity[primary_key_name]
37
- )
38
- failure(error)
47
+ failure(already_exists_error(entity[primary_key_name]))
48
+ rescue ActiveRecord::StatementInvalid => exception
49
+ failure(invalid_statement_error(exception.message))
39
50
  end
40
51
  end
41
52
  end
@@ -4,6 +4,7 @@ require 'cuprum/collections/errors/not_found'
4
4
 
5
5
  require 'cuprum/rails/command'
6
6
  require 'cuprum/rails/commands'
7
+ require 'cuprum/rails/errors/invalid_statement'
7
8
 
8
9
  module Cuprum::Rails::Commands
9
10
  # Command for updating an ActiveRecord record in the collection.
@@ -28,12 +29,20 @@ module Cuprum::Rails::Commands
28
29
 
29
30
  return if query.exists?
30
31
 
31
- error = Cuprum::Collections::Errors::NotFound.new(
32
- collection_name: collection_name,
33
- primary_key_name: primary_key_name,
34
- primary_key_values: primary_key
32
+ failure(not_found_error(primary_key))
33
+ end
34
+
35
+ def invalid_statement_error(message)
36
+ Cuprum::Rails::Errors::InvalidStatement.new(message: message)
37
+ end
38
+
39
+ def not_found_error(primary_key)
40
+ Cuprum::Collections::Errors::NotFound.new(
41
+ attribute_name: primary_key_name,
42
+ attribute_value: primary_key,
43
+ collection_name: collection_name,
44
+ primary_key: true
35
45
  )
36
- failure(error)
37
46
  end
38
47
 
39
48
  def process(entity:)
@@ -44,6 +53,8 @@ module Cuprum::Rails::Commands
44
53
  entity.save
45
54
 
46
55
  entity
56
+ rescue ActiveRecord::StatementInvalid => exception
57
+ failure(invalid_statement_error(exception.message))
47
58
  end
48
59
  end
49
60
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stannum/contracts/indifferent_hash_contract'
4
+
5
+ require 'cuprum/rails/constraints'
6
+
7
+ module Cuprum::Rails::Constraints
8
+ # Contract for validating request parameters.
9
+ class ParametersContract < Stannum::Contracts::IndifferentHashContract
10
+ def initialize(**options)
11
+ super(allow_extra_keys: true, **options)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/rails'
4
+
5
+ module Cuprum::Rails
6
+ # Namespace for Stannum constraints and contracts, which validate objects.
7
+ module Constraints
8
+ autoload :ParametersContract, 'cuprum/rails/constraints/parameters_contract'
9
+ end
10
+ end
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'cuprum/rails'
4
- require 'cuprum/rails/controller_action'
4
+ require 'cuprum/rails/controllers/class_methods/actions'
5
5
  require 'cuprum/rails/controllers/class_methods/configuration'
6
+ require 'cuprum/rails/controllers/class_methods/middleware'
6
7
  require 'cuprum/rails/controllers/class_methods/validations'
7
8
 
8
9
  module Cuprum::Rails
@@ -21,7 +22,7 @@ module Cuprum::Rails
21
22
  # class BooksController
22
23
  # include Cuprum::Rails::Controller
23
24
  #
24
- # responder :html, Cuprum::Rails::Responders::Html::PluralResource
25
+ # responder :html, Cuprum::Rails::Responders::Html::Resource
25
26
  #
26
27
  # action :index, Cuprum::Rails::Actions::Index
27
28
  # action :show, Cuprum::Rails::Actions::Show, member: true
@@ -43,8 +44,17 @@ module Cuprum::Rails
43
44
 
44
45
  other.extend(Cuprum::Rails::Controllers::ClassMethods::Actions)
45
46
  other.extend(Cuprum::Rails::Controllers::ClassMethods::Configuration)
47
+ other.extend(Cuprum::Rails::Controllers::ClassMethods::Middleware)
46
48
  other.extend(Cuprum::Rails::Controllers::ClassMethods::Validations)
47
49
  end
48
50
  end
51
+
52
+ # @api private
53
+ def action_options
54
+ {
55
+ repository: self.class.repository,
56
+ resource: self.class.resource
57
+ }
58
+ end
49
59
  end
50
60
  end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ require 'cuprum/middleware'
6
+
7
+ require 'cuprum/rails/controllers'
8
+
9
+ module Cuprum::Rails::Controllers
10
+ # @api private
11
+ #
12
+ # Implements a controller action.
13
+ #
14
+ # @note This class should not be initialized directly. Instead, use the
15
+ # Cuprum::Rails::Controller.action class method to define an action.
16
+ class Action
17
+ extend Forwardable
18
+
19
+ # @param action_class [Class] the class of the action command. Must be
20
+ # constructible with keyword :resource.
21
+ # @param action_name [String, Symbol] the name of the action.
22
+ # @param member_action [Boolean] true if the action acts on a collection
23
+ # item, not on the collection as a whole.
24
+ def initialize(
25
+ action_class:,
26
+ action_name:,
27
+ member_action: false
28
+ )
29
+ @action_class = action_class
30
+ @action_name = action_name
31
+ @member_action = !!member_action
32
+ end
33
+
34
+ # @return [Class] the class of the action command.
35
+ attr_reader :action_class
36
+
37
+ # @return [String, Symbol] the name of the action.
38
+ attr_reader :action_name
39
+
40
+ # Executes the controller action.
41
+ #
42
+ # 1. Initializes the action command with the resource.
43
+ # 2. Calls the command with the request.
44
+ # 3. Builds the responder with the resource and action metadata.
45
+ # 4. Calls the responder with the action result.
46
+ #
47
+ # @param controller [Cuprum::Rails::Controller] the controller instance
48
+ # calling the request.
49
+ # @param request [Cuprum::Rails::Request] the request to process.
50
+ #
51
+ # @return [#call] the response object.
52
+ def call(controller, request)
53
+ responder = build_responder(controller, request)
54
+ action = apply_middleware(controller, action_class.new)
55
+ result = action.call(request: request, **controller.action_options)
56
+
57
+ responder.call(result)
58
+ end
59
+
60
+ # @return [Boolean] true if the action acts on a collection item, not on the
61
+ # collection as a whole.
62
+ def member_action?
63
+ @member_action
64
+ end
65
+
66
+ private
67
+
68
+ def apply_middleware(controller, command)
69
+ configuration = controller.class.configuration
70
+ middleware =
71
+ configuration
72
+ .middleware_for(action_name)
73
+ .map { |config| build_middleware(config.command) }
74
+
75
+ Cuprum::Middleware.apply(
76
+ command: command,
77
+ middleware: middleware
78
+ )
79
+ end
80
+
81
+ def build_middleware(command)
82
+ return command unless command.is_a?(Class)
83
+
84
+ command.new
85
+ end
86
+
87
+ def build_responder(controller, request)
88
+ configuration = controller.class.configuration
89
+ responder_class = configuration.responder_for(request.format)
90
+
91
+ responder_class.new(
92
+ action_name: action_name,
93
+ controller: controller,
94
+ member_action: member_action?,
95
+ request: request,
96
+ serializers: configuration.serializers_for(request.format)
97
+ )
98
+ end
99
+ end
100
+ end