cuprum-rails 0.1.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 +7 -0
- data/CHANGELOG.md +98 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/DEVELOPMENT.md +28 -0
- data/LICENSE +22 -0
- data/README.md +1045 -0
- data/lib/cuprum/rails/action.rb +45 -0
- data/lib/cuprum/rails/actions/create.rb +49 -0
- data/lib/cuprum/rails/actions/destroy.rb +22 -0
- data/lib/cuprum/rails/actions/edit.rb +22 -0
- data/lib/cuprum/rails/actions/index.rb +55 -0
- data/lib/cuprum/rails/actions/new.rb +19 -0
- data/lib/cuprum/rails/actions/resource_action.rb +75 -0
- data/lib/cuprum/rails/actions/show.rb +22 -0
- data/lib/cuprum/rails/actions/update.rb +59 -0
- data/lib/cuprum/rails/actions.rb +16 -0
- data/lib/cuprum/rails/collection.rb +115 -0
- data/lib/cuprum/rails/command.rb +137 -0
- data/lib/cuprum/rails/commands/assign_one.rb +66 -0
- data/lib/cuprum/rails/commands/build_one.rb +55 -0
- data/lib/cuprum/rails/commands/destroy_one.rb +43 -0
- data/lib/cuprum/rails/commands/find_many.rb +60 -0
- data/lib/cuprum/rails/commands/find_matching.rb +121 -0
- data/lib/cuprum/rails/commands/find_one.rb +50 -0
- data/lib/cuprum/rails/commands/insert_one.rb +41 -0
- data/lib/cuprum/rails/commands/update_one.rb +49 -0
- data/lib/cuprum/rails/commands/validate_one.rb +68 -0
- data/lib/cuprum/rails/commands.rb +18 -0
- data/lib/cuprum/rails/controller.rb +50 -0
- data/lib/cuprum/rails/controller_action.rb +121 -0
- data/lib/cuprum/rails/controllers/class_methods/actions.rb +57 -0
- data/lib/cuprum/rails/controllers/class_methods/configuration.rb +64 -0
- data/lib/cuprum/rails/controllers/class_methods/validations.rb +30 -0
- data/lib/cuprum/rails/controllers/class_methods.rb +15 -0
- data/lib/cuprum/rails/controllers/configuration.rb +53 -0
- data/lib/cuprum/rails/controllers.rb +10 -0
- data/lib/cuprum/rails/errors/missing_parameters.rb +33 -0
- data/lib/cuprum/rails/errors/missing_primary_key.rb +46 -0
- data/lib/cuprum/rails/errors/undefined_permitted_attributes.rb +34 -0
- data/lib/cuprum/rails/errors.rb +8 -0
- data/lib/cuprum/rails/map_errors.rb +44 -0
- data/lib/cuprum/rails/query.rb +77 -0
- data/lib/cuprum/rails/query_builder.rb +78 -0
- data/lib/cuprum/rails/repository.rb +44 -0
- data/lib/cuprum/rails/request.rb +105 -0
- data/lib/cuprum/rails/resource.rb +145 -0
- data/lib/cuprum/rails/responders/actions.rb +73 -0
- data/lib/cuprum/rails/responders/html/plural_resource.rb +62 -0
- data/lib/cuprum/rails/responders/html/singular_resource.rb +59 -0
- data/lib/cuprum/rails/responders/html.rb +11 -0
- data/lib/cuprum/rails/responders/html_responder.rb +129 -0
- data/lib/cuprum/rails/responders/json/resource.rb +60 -0
- data/lib/cuprum/rails/responders/json.rb +10 -0
- data/lib/cuprum/rails/responders/json_responder.rb +122 -0
- data/lib/cuprum/rails/responders/matching.rb +145 -0
- data/lib/cuprum/rails/responders/serialization.rb +36 -0
- data/lib/cuprum/rails/responders.rb +15 -0
- data/lib/cuprum/rails/responses/html/redirect_response.rb +29 -0
- data/lib/cuprum/rails/responses/html/render_response.rb +52 -0
- data/lib/cuprum/rails/responses/html.rb +11 -0
- data/lib/cuprum/rails/responses/json_response.rb +29 -0
- data/lib/cuprum/rails/responses.rb +11 -0
- data/lib/cuprum/rails/routes.rb +166 -0
- data/lib/cuprum/rails/routing/plural_routes.rb +26 -0
- data/lib/cuprum/rails/routing/singular_routes.rb +24 -0
- data/lib/cuprum/rails/routing.rb +11 -0
- data/lib/cuprum/rails/rspec/command_contract.rb +460 -0
- data/lib/cuprum/rails/rspec/define_route_contract.rb +84 -0
- data/lib/cuprum/rails/rspec.rb +8 -0
- data/lib/cuprum/rails/serializers/json/active_record_serializer.rb +24 -0
- data/lib/cuprum/rails/serializers/json/array_serializer.rb +40 -0
- data/lib/cuprum/rails/serializers/json/attributes_serializer.rb +217 -0
- data/lib/cuprum/rails/serializers/json/error_serializer.rb +24 -0
- data/lib/cuprum/rails/serializers/json/hash_serializer.rb +44 -0
- data/lib/cuprum/rails/serializers/json/identity_serializer.rb +21 -0
- data/lib/cuprum/rails/serializers/json/serializer.rb +66 -0
- data/lib/cuprum/rails/serializers/json.rb +40 -0
- data/lib/cuprum/rails/serializers.rb +10 -0
- data/lib/cuprum/rails/version.rb +59 -0
- data/lib/cuprum/rails.rb +31 -0
- metadata +286 -0
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
require 'cuprum/rails'
|
6
|
+
|
7
|
+
module Cuprum::Rails
|
8
|
+
# @api private
|
9
|
+
#
|
10
|
+
# Implements a controller action.
|
11
|
+
#
|
12
|
+
# @note This class should not be initialized directly. Instead, use the
|
13
|
+
# Cuprum::Rails::Controller.action class method to define an action.
|
14
|
+
class ControllerAction
|
15
|
+
extend Forwardable
|
16
|
+
|
17
|
+
# @param configuration [Cuprum::Rails::Controllers::Configuration] the
|
18
|
+
# configuration for the originating controller.
|
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
|
+
# @param serializers
|
25
|
+
# [Hash<Class, Object>, Hash<Symbol, Hash<Class, Object>>] The serializers
|
26
|
+
# for converting result values into serialized data.
|
27
|
+
def initialize(
|
28
|
+
configuration,
|
29
|
+
action_class:,
|
30
|
+
action_name:,
|
31
|
+
member_action: false,
|
32
|
+
serializers: {}
|
33
|
+
)
|
34
|
+
@configuration = configuration
|
35
|
+
@action_class = action_class
|
36
|
+
@action_name = action_name
|
37
|
+
@member_action = !!member_action # rubocop:disable Style/DoubleNegation
|
38
|
+
@serializers = serializers
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [Class] the class of the action command.
|
42
|
+
attr_reader :action_class
|
43
|
+
|
44
|
+
# @return [String, Symbol] the name of the action.
|
45
|
+
attr_reader :action_name
|
46
|
+
|
47
|
+
# @return [Cuprum::Rails::Controllers::Configuration] the configuration for
|
48
|
+
# the originating controller.
|
49
|
+
attr_reader :configuration
|
50
|
+
|
51
|
+
# @return [Hash<Class, Object>, Hash<Symbol, Hash<Class, Object>>] the
|
52
|
+
# serializers for converting result values into serialized data.
|
53
|
+
attr_reader :serializers
|
54
|
+
|
55
|
+
# @!method resource
|
56
|
+
# @return [Cuprum::Rails::Resource] the resource defined for the
|
57
|
+
# controller.
|
58
|
+
|
59
|
+
# @!method responder_for(format)
|
60
|
+
# Finds the configured responder for the requested format.
|
61
|
+
#
|
62
|
+
# @param format [Symbol] The format to respond to.
|
63
|
+
#
|
64
|
+
# @return [Class] the responder class defined for the format.
|
65
|
+
#
|
66
|
+
# @raise [Cuprum::Rails::Controller::UnknownFormatError] if the controller
|
67
|
+
# does not define a responder for the given format.
|
68
|
+
|
69
|
+
def_delegators :@configuration,
|
70
|
+
:resource,
|
71
|
+
:responder_for
|
72
|
+
|
73
|
+
# Executes the controller action.
|
74
|
+
#
|
75
|
+
# 1. Initializes the action command with the resource.
|
76
|
+
# 2. Calls the command with the request.
|
77
|
+
# 3. Builds the responder with the resource and action metadata.
|
78
|
+
# 4. Calls the responder with the action result.
|
79
|
+
#
|
80
|
+
# @param request [ActionDispatch::Request] The request to process.
|
81
|
+
#
|
82
|
+
# @return [#call] The response object.
|
83
|
+
def call(request)
|
84
|
+
responder = build_responder(request)
|
85
|
+
action = action_class.new(resource: resource)
|
86
|
+
result = action.call(request: request)
|
87
|
+
|
88
|
+
responder.call(result)
|
89
|
+
end
|
90
|
+
|
91
|
+
# @return [Boolean] true if the action acts on a collection item, not on the
|
92
|
+
# collection as a whole.
|
93
|
+
def member_action?
|
94
|
+
@member_action
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def build_responder(request)
|
100
|
+
responder_class = responder_for(request.format)
|
101
|
+
|
102
|
+
responder_class.new(
|
103
|
+
action_name: action_name,
|
104
|
+
member_action: member_action?,
|
105
|
+
resource: resource,
|
106
|
+
serializers: merge_serializers_for(request.format)
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
def merge_serializers_for(format)
|
111
|
+
scoped_serializers(configuration.serializers, format: format)
|
112
|
+
.merge(scoped_serializers(serializers, format: format))
|
113
|
+
end
|
114
|
+
|
115
|
+
def scoped_serializers(serializers, format:)
|
116
|
+
serializers
|
117
|
+
.select { |key, _| key.is_a?(Class) }
|
118
|
+
.merge(serializers.fetch(format, {}))
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails/controller_action'
|
4
|
+
require 'cuprum/rails/controllers/class_methods'
|
5
|
+
|
6
|
+
module Cuprum::Rails::Controllers::ClassMethods
|
7
|
+
# Provides a DSL for defining controller actions.
|
8
|
+
module Actions
|
9
|
+
# Defines a controller action.
|
10
|
+
#
|
11
|
+
# @param action_name [String, Symbol] The name of the action.
|
12
|
+
# @param action_class [Class] The class of the action command. Must be
|
13
|
+
# constructible with keyword :resource.
|
14
|
+
# @param member [Boolean] True if the action acts on a collection item, not
|
15
|
+
# on the collection as a whole.
|
16
|
+
def action(action_name, action_class, member: false)
|
17
|
+
validate_name(action_name, as: 'action name')
|
18
|
+
validate_class(action_class, as: 'action class')
|
19
|
+
|
20
|
+
action_name = action_name.intern
|
21
|
+
own_actions[action_name] = Cuprum::Rails::ControllerAction.new(
|
22
|
+
configuration,
|
23
|
+
action_class: action_class,
|
24
|
+
action_name: action_name,
|
25
|
+
member_action: member
|
26
|
+
)
|
27
|
+
|
28
|
+
define_action(action_name)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Hash<Symbol, Cuprum::Rails::ControllerAction>] the actions
|
32
|
+
# defined for the controller.
|
33
|
+
def actions
|
34
|
+
ancestors
|
35
|
+
.select { |ancestor| ancestor.respond_to?(:own_actions) }
|
36
|
+
.reverse_each
|
37
|
+
.map(&:own_actions)
|
38
|
+
.reduce(&:merge)
|
39
|
+
end
|
40
|
+
|
41
|
+
# @private
|
42
|
+
def own_actions
|
43
|
+
@own_actions ||= {}
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def define_action(action_name)
|
49
|
+
define_method(action_name) do
|
50
|
+
request = Cuprum::Rails::Request.build(request: self.request)
|
51
|
+
action = self.class.actions[action_name]
|
52
|
+
response = action.call(request)
|
53
|
+
response.call(self)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails/controllers/class_methods'
|
4
|
+
require 'cuprum/rails/controllers/configuration'
|
5
|
+
require 'cuprum/rails/serializers/json'
|
6
|
+
|
7
|
+
module Cuprum::Rails::Controllers::ClassMethods
|
8
|
+
# Provides a DSL for defining controller configuration.
|
9
|
+
module Configuration
|
10
|
+
# @return [Cuprum::Rails::Controllers::Configuration] the configured options
|
11
|
+
# for the controller.
|
12
|
+
def configuration
|
13
|
+
Cuprum::Rails::Controllers::Configuration.new(self)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @private
|
17
|
+
def own_responders
|
18
|
+
@own_responders ||= {}
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the resource defined for the controller.
|
22
|
+
#
|
23
|
+
# Controller subclasses must override this method.
|
24
|
+
#
|
25
|
+
# @return [Cuprum::Rails::Resource] the resource defined for the
|
26
|
+
# controller.
|
27
|
+
#
|
28
|
+
# @raise [Cuprum::Rails::Controller::UndefinedResourceError] if the
|
29
|
+
# controller does not define a resource.
|
30
|
+
def resource
|
31
|
+
raise Cuprum::Rails::Controller::UndefinedResourceError,
|
32
|
+
"no resource defined for #{name}"
|
33
|
+
end
|
34
|
+
|
35
|
+
# Assigns a responder class to handle requests of the specified format.
|
36
|
+
#
|
37
|
+
# @param format [String, Symbol] The request format to handle.
|
38
|
+
# @param responder_class [Class] The class of responder.
|
39
|
+
def responder(format, responder_class)
|
40
|
+
validate_name(format, as: 'format')
|
41
|
+
validate_class(responder_class, as: 'responder')
|
42
|
+
|
43
|
+
own_responders[format.intern] = responder_class
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [Hash<Symbol, Class>] the responder classes defined for the
|
47
|
+
# controller, by format.
|
48
|
+
def responders
|
49
|
+
ancestors
|
50
|
+
.select { |ancestor| ancestor.respond_to?(:own_responders) }
|
51
|
+
.reverse_each
|
52
|
+
.map(&:own_responders)
|
53
|
+
.reduce(&:merge)
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [Hash<Class, Object>, Hash<Symbol, Hash<Class, Object>>] the
|
57
|
+
# serializers for converting result values into serialized data.
|
58
|
+
def serializers
|
59
|
+
{
|
60
|
+
json: Cuprum::Rails::Serializers::Json.default_serializers
|
61
|
+
}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails/controllers/class_methods'
|
4
|
+
|
5
|
+
module Cuprum::Rails::Controllers::ClassMethods
|
6
|
+
# @private
|
7
|
+
module Validations
|
8
|
+
private
|
9
|
+
|
10
|
+
def validate_class(value, as:) # rubocop:disable Naming/MethodParameterName
|
11
|
+
return if value.is_a?(Class)
|
12
|
+
|
13
|
+
raise ArgumentError, "#{as} must be a Class", caller(1..-1)
|
14
|
+
end
|
15
|
+
|
16
|
+
def validate_name(value, as:) # rubocop:disable Naming/MethodParameterName
|
17
|
+
raise ArgumentError, "#{as} can't be blank", caller(1..-1) if value.nil?
|
18
|
+
|
19
|
+
unless value.is_a?(String) || value.is_a?(Symbol)
|
20
|
+
raise ArgumentError,
|
21
|
+
"#{as} must be a String or Symbol",
|
22
|
+
caller(1..-1)
|
23
|
+
end
|
24
|
+
|
25
|
+
return unless value.to_s.empty?
|
26
|
+
|
27
|
+
raise ArgumentError, "#{as} can't be blank", caller(1..-1)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails/controllers'
|
4
|
+
|
5
|
+
module Cuprum::Rails::Controllers
|
6
|
+
# Namespace for controller class-specific functionality.
|
7
|
+
module ClassMethods
|
8
|
+
autoload :Actions,
|
9
|
+
'cuprum/rails/controllers/class_methods/actions'
|
10
|
+
autoload :Configuration,
|
11
|
+
'cuprum/rails/controllers/class_methods/configuration'
|
12
|
+
autoload :Validations,
|
13
|
+
'cuprum/rails/controllers/class_methods/validations'
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
require 'cuprum/rails/controllers'
|
6
|
+
|
7
|
+
module Cuprum::Rails::Controllers
|
8
|
+
# Data object that stores a controller's configuration.
|
9
|
+
class Configuration
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
# @param controller [#resource, #responders] The controller to delegate
|
13
|
+
# configuration.
|
14
|
+
def initialize(controller)
|
15
|
+
@controller = controller
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [#resource, #responders] the controller to delegate configuration.
|
19
|
+
attr_reader :controller
|
20
|
+
|
21
|
+
# @!method resource
|
22
|
+
# @return [Cuprum::Rails::Resource] the resource defined for the
|
23
|
+
# controller.
|
24
|
+
|
25
|
+
# @!method responders
|
26
|
+
# @return [Hash<Symbol, Class>] the responder classes defined for the
|
27
|
+
# controller, by format.
|
28
|
+
|
29
|
+
# @!method serializers
|
30
|
+
# @return [Hash<Class, Object>, Hash<Symbol, Hash<Class, Object>>] the
|
31
|
+
# serializers for converting result values into serialized data.
|
32
|
+
|
33
|
+
def_delegators :@controller,
|
34
|
+
:resource,
|
35
|
+
:responders,
|
36
|
+
:serializers
|
37
|
+
|
38
|
+
# Finds the configured responder for the requested format.
|
39
|
+
#
|
40
|
+
# @param format [Symbol] The format to respond to.
|
41
|
+
#
|
42
|
+
# @return [Class] the responder class defined for the format.
|
43
|
+
#
|
44
|
+
# @raise [Cuprum::Rails::Controller::UnknownFormatError] if the controller
|
45
|
+
# does not define a responder for the given format.
|
46
|
+
def responder_for(format)
|
47
|
+
responders.fetch(format) do
|
48
|
+
raise Cuprum::Rails::Controller::UnknownFormatError,
|
49
|
+
"no responder registered for format #{format.inspect}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/error'
|
4
|
+
|
5
|
+
require 'cuprum/rails/errors'
|
6
|
+
|
7
|
+
module Cuprum::Rails::Errors
|
8
|
+
# Error class when a parameters hash does not include a resource.
|
9
|
+
class MissingParameters < Cuprum::Error
|
10
|
+
# Short string used to identify the type of error.
|
11
|
+
TYPE = 'cuprum.rails.errors.missing_parameters'
|
12
|
+
|
13
|
+
# @param resource_name [Cuprum::Rails::Resource] The name of the resource.
|
14
|
+
def initialize(resource_name:)
|
15
|
+
@resource_name = resource_name
|
16
|
+
|
17
|
+
super(message: default_message, resource_name: resource_name)
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Cuprum::Rails::Resource] the name of the resource.
|
21
|
+
attr_reader :resource_name
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def as_json_data
|
26
|
+
{ 'resource_name' => resource_name }
|
27
|
+
end
|
28
|
+
|
29
|
+
def default_message
|
30
|
+
"The #{resource_name.inspect} parameter is missing or empty"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/error'
|
4
|
+
|
5
|
+
require 'cuprum/rails/errors'
|
6
|
+
|
7
|
+
module Cuprum::Rails::Errors
|
8
|
+
# Error class when a parameters hash does not include a primary key.
|
9
|
+
class MissingPrimaryKey < Cuprum::Error
|
10
|
+
# Short string used to identify the type of error.
|
11
|
+
TYPE = 'cuprum.rails.errors.missing_primary_key'
|
12
|
+
|
13
|
+
# @param primary_key [String, Symbol] The name of the resource primary key.
|
14
|
+
# @param resource_name [Cuprum::Rails::Resource] The name of the resource.
|
15
|
+
def initialize(primary_key:, resource_name:)
|
16
|
+
@primary_key = primary_key
|
17
|
+
@resource_name = resource_name
|
18
|
+
|
19
|
+
super(
|
20
|
+
message: default_message,
|
21
|
+
primary_key: primary_key,
|
22
|
+
resource_name: resource_name
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [String] the name of the resource primary key.
|
27
|
+
attr_reader :primary_key
|
28
|
+
|
29
|
+
# @return [Cuprum::Rails::Resource] the name of the resource.
|
30
|
+
attr_reader :resource_name
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def as_json_data
|
35
|
+
{
|
36
|
+
'primary_key' => primary_key,
|
37
|
+
'resource_name' => resource_name
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def default_message
|
42
|
+
"Unable to find #{resource_name} because the #{primary_key.inspect}" \
|
43
|
+
' parameter is missing or empty'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/error'
|
4
|
+
|
5
|
+
require 'cuprum/rails/errors'
|
6
|
+
|
7
|
+
module Cuprum::Rails::Errors
|
8
|
+
# Error class when a resource does not define permitted attributes.
|
9
|
+
class UndefinedPermittedAttributes < Cuprum::Error
|
10
|
+
# Short string used to identify the type of error.
|
11
|
+
TYPE = 'cuprum.rails.errors.undefined_permitted_attributes'
|
12
|
+
|
13
|
+
# @param resource_name [Cuprum::Rails::Resource] The name of the resource.
|
14
|
+
def initialize(resource_name:)
|
15
|
+
@resource_name = resource_name
|
16
|
+
|
17
|
+
super(message: default_message, resource_name: resource_name)
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Cuprum::Rails::Resource] the name of the resource.
|
21
|
+
attr_reader :resource_name
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def as_json_data
|
26
|
+
{ 'resource_name' => resource_name }
|
27
|
+
end
|
28
|
+
|
29
|
+
def default_message
|
30
|
+
"Resource #{resource_name.inspect} does not define" \
|
31
|
+
' permitted attributes'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stannum/errors'
|
4
|
+
|
5
|
+
require 'cuprum/rails'
|
6
|
+
|
7
|
+
module Cuprum::Rails
|
8
|
+
# Maps errors from a validated Rails model to a Stannum::Errors object.
|
9
|
+
class MapErrors
|
10
|
+
# @return [MapErrors] a memoized instance of the class.
|
11
|
+
def self.instance
|
12
|
+
@instance ||= new
|
13
|
+
end
|
14
|
+
|
15
|
+
# Maps an ActiveModel::Errors object to a Stannum::Errors object.
|
16
|
+
#
|
17
|
+
# @param native_errors [ActiveModel::Errors] The Rails error object.
|
18
|
+
#
|
19
|
+
# @return [Stannum::Errors] the generated errors object.
|
20
|
+
def call(native_errors:)
|
21
|
+
unless native_errors.is_a?(ActiveModel::Errors)
|
22
|
+
raise ArgumentError,
|
23
|
+
'native_errors must be an instance of ActiveModel::Errors'
|
24
|
+
end
|
25
|
+
|
26
|
+
map_errors(native_errors: native_errors)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def map_errors(native_errors:)
|
32
|
+
errors = Stannum::Errors.new
|
33
|
+
|
34
|
+
native_errors.each do |error|
|
35
|
+
attribute = error.attribute
|
36
|
+
scoped = attribute == :base ? errors : errors[attribute]
|
37
|
+
|
38
|
+
scoped.add(error.type, message: error.message, **error.options)
|
39
|
+
end
|
40
|
+
|
41
|
+
errors
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/query'
|
4
|
+
|
5
|
+
require 'cuprum/rails'
|
6
|
+
require 'cuprum/rails/query_builder'
|
7
|
+
|
8
|
+
module Cuprum::Rails
|
9
|
+
# Interface for performing query operations on a Rails collection.
|
10
|
+
class Query < Cuprum::Collections::Query
|
11
|
+
extend Forwardable
|
12
|
+
include Enumerable
|
13
|
+
|
14
|
+
def_delegators :@native_query,
|
15
|
+
:each,
|
16
|
+
:exists?,
|
17
|
+
:to_a
|
18
|
+
|
19
|
+
# @param record_class [Class] The class of the collection items. Must be a
|
20
|
+
# subclass of ActiveRecord::Base.
|
21
|
+
# @param native_query [ActiveRecord::Relation] A relation used to scope the
|
22
|
+
# query.
|
23
|
+
def initialize(record_class, native_query: nil)
|
24
|
+
super()
|
25
|
+
|
26
|
+
default_order = { record_class.primary_key => :asc }
|
27
|
+
@native_query = native_query || record_class.all.order(default_order)
|
28
|
+
@record_class = record_class
|
29
|
+
@limit = nil
|
30
|
+
@offset = nil
|
31
|
+
@order = default_order
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [Class] the class of the collection items.
|
35
|
+
attr_reader :record_class
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
def query_builder
|
40
|
+
Cuprum::Rails::QueryBuilder.new(self)
|
41
|
+
end
|
42
|
+
|
43
|
+
def reset!
|
44
|
+
@native_query.reset
|
45
|
+
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def with_limit(count)
|
50
|
+
@native_query = @native_query.limit(count)
|
51
|
+
|
52
|
+
super
|
53
|
+
end
|
54
|
+
|
55
|
+
def with_native_query(native_query)
|
56
|
+
@native_query = native_query
|
57
|
+
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
def with_offset(count)
|
62
|
+
@native_query = @native_query.offset(count)
|
63
|
+
|
64
|
+
super
|
65
|
+
end
|
66
|
+
|
67
|
+
def with_order(order)
|
68
|
+
@native_query = @native_query.reorder(order)
|
69
|
+
|
70
|
+
super
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
attr_reader :native_query
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/query_builder'
|
4
|
+
|
5
|
+
require 'cuprum/rails'
|
6
|
+
|
7
|
+
module Cuprum::Rails
|
8
|
+
# Applies filter operations for a Rails collection query.
|
9
|
+
class QueryBuilder < Cuprum::Collections::QueryBuilder
|
10
|
+
# @param base_query [Cuprum::Rails::Query] The query to build.
|
11
|
+
def initialize(base_query)
|
12
|
+
super
|
13
|
+
|
14
|
+
@native_query = base_query.send(:native_query)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :native_query
|
20
|
+
|
21
|
+
def build_native_query(criteria)
|
22
|
+
native_query.where(
|
23
|
+
criteria
|
24
|
+
.map do |(attribute, operator, value)|
|
25
|
+
send(operator, attribute, value)
|
26
|
+
end
|
27
|
+
.join(' AND ')
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
def build_query(criteria)
|
32
|
+
super.send(:with_native_query, build_native_query(criteria))
|
33
|
+
end
|
34
|
+
|
35
|
+
def equal(attribute, value)
|
36
|
+
return sanitize("#{attribute} IS NULL") if value.nil?
|
37
|
+
|
38
|
+
sanitize("#{attribute} = :value", value: value)
|
39
|
+
end
|
40
|
+
|
41
|
+
def greater_than(attribute, value)
|
42
|
+
sanitize("#{attribute} > :value", value: value)
|
43
|
+
end
|
44
|
+
|
45
|
+
def greater_than_or_equal_to(attribute, value)
|
46
|
+
sanitize("#{attribute} >= :value", value: value)
|
47
|
+
end
|
48
|
+
|
49
|
+
def less_than(attribute, value)
|
50
|
+
sanitize("#{attribute} < :value", value: value)
|
51
|
+
end
|
52
|
+
|
53
|
+
def less_than_or_equal_to(attribute, value)
|
54
|
+
sanitize("#{attribute} <= :value", value: value)
|
55
|
+
end
|
56
|
+
|
57
|
+
def not_equal(attribute, value)
|
58
|
+
return sanitize("#{attribute} IS NOT NULL") if value.nil?
|
59
|
+
|
60
|
+
sanitize("(#{attribute} != :value OR #{attribute} IS NULL)", value: value)
|
61
|
+
end
|
62
|
+
|
63
|
+
def not_one_of(attribute, value)
|
64
|
+
sanitize(
|
65
|
+
"(#{attribute} NOT IN (:value) OR #{attribute} IS NULL)",
|
66
|
+
value: value
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
def one_of(attribute, value)
|
71
|
+
sanitize("#{attribute} IN (:value)", value: value)
|
72
|
+
end
|
73
|
+
|
74
|
+
def sanitize(*conditions)
|
75
|
+
ActiveRecord::Base.sanitize_sql_for_conditions(conditions)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|