cuprum-rails 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|