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,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails/responders'
|
4
|
+
require 'cuprum/rails/responders/actions'
|
5
|
+
require 'cuprum/rails/responders/matching'
|
6
|
+
require 'cuprum/rails/responders/serialization'
|
7
|
+
require 'cuprum/rails/responses/json_response'
|
8
|
+
require 'cuprum/rails/serializers/json'
|
9
|
+
|
10
|
+
module Cuprum::Rails::Responders
|
11
|
+
# Provides a DSL for defining responses to JSON requests.
|
12
|
+
#
|
13
|
+
# By default, responds to any successful result by serializing the result
|
14
|
+
# value and generating a JSON object of the form { 'ok' => true, 'data' =>
|
15
|
+
# serialized_value }.
|
16
|
+
#
|
17
|
+
# For a failing result, it generates and serializes a generic error and
|
18
|
+
# generates a JSON object of the form { 'ok' => false, 'data' =>
|
19
|
+
# serialized_error }. This is to prevent leaks of internal states that might
|
20
|
+
# help an adversary access your system. Use the .match class method to define
|
21
|
+
# more useful responses for whitelisted errors.
|
22
|
+
#
|
23
|
+
# @example Defining Error Responses
|
24
|
+
# class CustomResponder < Cuprum::Rails::Responders::HtmlResponder
|
25
|
+
# match :failure, error: Spec::NotFound do |result|
|
26
|
+
# render_failure(result.error, status: 404)
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# match :failure, error: Spec::AuthorizationFailure do
|
30
|
+
# error = Cuprum::Error.new(message: "I can't let you do that, Dave")
|
31
|
+
#
|
32
|
+
# render_failure(error, status: 403)
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
class JsonResponder
|
36
|
+
include Cuprum::Rails::Responders::Matching
|
37
|
+
include Cuprum::Rails::Responders::Actions
|
38
|
+
include Cuprum::Rails::Responders::Serialization
|
39
|
+
|
40
|
+
GENERIC_ERROR = Cuprum::Error.new(
|
41
|
+
message: 'Something went wrong when processing the request'
|
42
|
+
).freeze
|
43
|
+
private_constant :GENERIC_ERROR
|
44
|
+
|
45
|
+
match :success do |result|
|
46
|
+
render_success(result.value)
|
47
|
+
end
|
48
|
+
|
49
|
+
match :failure do
|
50
|
+
render_failure(GENERIC_ERROR)
|
51
|
+
end
|
52
|
+
|
53
|
+
# @param action_name [String, Symbol] The name of the action to match.
|
54
|
+
# @param matcher [Cuprum::Matcher] An optional matcher specific to the
|
55
|
+
# action. This will be matched before any of the generic matchers.
|
56
|
+
# @param member_action [Boolean] True if the action acts on a collection
|
57
|
+
# item, not on the collection as a whole.
|
58
|
+
# @param resource [Cuprum::Rails::Resource] The resource for the controller.
|
59
|
+
def initialize( # rubocop:disable Metrics/ParameterLists
|
60
|
+
action_name:,
|
61
|
+
resource:,
|
62
|
+
serializers:,
|
63
|
+
matcher: nil,
|
64
|
+
member_action: false,
|
65
|
+
**_options
|
66
|
+
)
|
67
|
+
super(
|
68
|
+
action_name: action_name,
|
69
|
+
matcher: matcher,
|
70
|
+
member_action: member_action,
|
71
|
+
resource: resource,
|
72
|
+
root_serializer: Cuprum::Rails::Serializers::Json::Serializer.instance,
|
73
|
+
serializers: serializers
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
# @!method call(result)
|
78
|
+
# (see Cuprum::Rails::Responders::Actions#call)
|
79
|
+
|
80
|
+
# @return [Symbol] the format of the responder.
|
81
|
+
def format
|
82
|
+
:json
|
83
|
+
end
|
84
|
+
|
85
|
+
# Creates a JsonResponse based on the given data and options.
|
86
|
+
#
|
87
|
+
# @param json [Object] The data to serialize.
|
88
|
+
# @param status [Integer] The HTTP status of the response.
|
89
|
+
#
|
90
|
+
# @return [Cuprum::Rails::Responses::JsonResponse] the response.
|
91
|
+
def render(json, status: 200)
|
92
|
+
Cuprum::Rails::Responses::JsonResponse.new(
|
93
|
+
data: serialize(json),
|
94
|
+
status: status
|
95
|
+
)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Creates a JsonResponse for a failed result.
|
99
|
+
#
|
100
|
+
# @param error [Cuprum::Error] The error from the failed result.
|
101
|
+
# @param status [Integer] The HTTP status of the response.
|
102
|
+
#
|
103
|
+
# @return [Cuprum::Rails::Responses::JsonResponse] the response.
|
104
|
+
def render_failure(error, status: 500)
|
105
|
+
json = { 'ok' => false, 'error' => error }
|
106
|
+
|
107
|
+
render(json, status: status)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Creates a JsonResponse for a successful result.
|
111
|
+
#
|
112
|
+
# @param value [Object] The value of the successful result.
|
113
|
+
# @param status [Integer] The HTTP status of the response.
|
114
|
+
#
|
115
|
+
# @return [Cuprum::Rails::Responses::JsonResponse] the response.
|
116
|
+
def render_success(value, status: 200)
|
117
|
+
json = { 'ok' => true, 'data' => value }
|
118
|
+
|
119
|
+
render(json, status: status)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/matcher'
|
4
|
+
require 'cuprum/matcher_list'
|
5
|
+
|
6
|
+
require 'cuprum/rails/responders'
|
7
|
+
|
8
|
+
module Cuprum::Rails::Responders
|
9
|
+
# Implements matching an action result to a response clause.
|
10
|
+
module Matching
|
11
|
+
# Provides a DSL for generating response clauses for matching results.
|
12
|
+
module ClassMethods
|
13
|
+
# Creates a match clause that maps a result to a response.
|
14
|
+
#
|
15
|
+
# @param status [Symbol] The status of the result, either :success or
|
16
|
+
# :failure.
|
17
|
+
# @param error [Class] The class of the result error. If given, the clause
|
18
|
+
# will only match results with an error that is an instance of this
|
19
|
+
# class or a subclass.
|
20
|
+
# @param value [Class] The class of the result value. If given, the clause
|
21
|
+
# will only match results with a value that is an instance of this class
|
22
|
+
# or a subclass.
|
23
|
+
#
|
24
|
+
# @yield The clause implementation. This block will be called in the
|
25
|
+
# context of the matcher.
|
26
|
+
# @yieldreturn [#call, #renderer] the response for the action.
|
27
|
+
def match(status, error: nil, value: nil, &block)
|
28
|
+
matcher = @matcher || Cuprum::Matcher.new
|
29
|
+
|
30
|
+
matcher.singleton_class.match(
|
31
|
+
status, error: error, value: value, &block
|
32
|
+
)
|
33
|
+
|
34
|
+
@matcher = matcher
|
35
|
+
end
|
36
|
+
|
37
|
+
# @private
|
38
|
+
def matchers(**_keywords)
|
39
|
+
return [] unless @matcher
|
40
|
+
|
41
|
+
[@matcher]
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
attr_reader :matcher
|
47
|
+
end
|
48
|
+
|
49
|
+
# @private
|
50
|
+
def self.included(other)
|
51
|
+
super
|
52
|
+
|
53
|
+
other.extend(ClassMethods)
|
54
|
+
end
|
55
|
+
|
56
|
+
# @param action_name [String, Symbol] The name of the action to match.
|
57
|
+
# @param matcher [Cuprum::Matcher] An optional matcher specific to the
|
58
|
+
# action. This will be matched before any of the generic matchers.
|
59
|
+
# @param member_action [Boolean] True if the action acts on a collection
|
60
|
+
# item, not on the collection as a whole.
|
61
|
+
# @param resource [Cuprum::Rails::Resource] The resource for the controller.
|
62
|
+
def initialize(
|
63
|
+
action_name:,
|
64
|
+
resource:,
|
65
|
+
matcher: nil,
|
66
|
+
member_action: false,
|
67
|
+
**_options
|
68
|
+
)
|
69
|
+
@action_name = action_name
|
70
|
+
@matcher = matcher
|
71
|
+
@member_action = !!member_action # rubocop:disable Style/DoubleNegation
|
72
|
+
@resource = resource
|
73
|
+
end
|
74
|
+
|
75
|
+
# @return [String, Symbol] the name of the action to match.
|
76
|
+
attr_reader :action_name
|
77
|
+
|
78
|
+
# @return [Cuprum::Matcher] an optional matcher specific to the action.
|
79
|
+
attr_reader :matcher
|
80
|
+
|
81
|
+
# @return [Cuprum::Rails::Resource] the resource for the controller.
|
82
|
+
attr_reader :resource
|
83
|
+
|
84
|
+
# @return [Cuprum::Result] the result of calling the action.
|
85
|
+
attr_reader :result
|
86
|
+
|
87
|
+
# Finds and calls the response clause that matches the given result.
|
88
|
+
#
|
89
|
+
# 1. Checks for an exact match (the result status, value, and error all
|
90
|
+
# match) in the given matcher (if any), then the responder class, then
|
91
|
+
# each ancestor of the responder class in ascending order.
|
92
|
+
# 2. If a match is not found, checks for a partial match (the result
|
93
|
+
# status, and either the value or the error match) in the same order.
|
94
|
+
# 3. If there is still no match found, checks for a generic match (the
|
95
|
+
# result status matches, and the match clause does not specify either an
|
96
|
+
# error or a value.
|
97
|
+
# 4. If there is no matching response clause, raises an exception.
|
98
|
+
#
|
99
|
+
# @return [#call, #renderer] The response object from the matching response
|
100
|
+
# clause.
|
101
|
+
#
|
102
|
+
# @raise [Cuprum::Matching::NoMatchError] if none of the response clauses
|
103
|
+
# match the result.
|
104
|
+
def call(result)
|
105
|
+
@result = result
|
106
|
+
|
107
|
+
Cuprum::MatcherList
|
108
|
+
.new(matchers.map { |matcher| matcher.with_context(self) })
|
109
|
+
.call(result)
|
110
|
+
end
|
111
|
+
|
112
|
+
# @return [Symbol, nil] the format of the responder.
|
113
|
+
def format
|
114
|
+
nil
|
115
|
+
end
|
116
|
+
|
117
|
+
# @return [Boolean] true if the action acts on a collection item, not on the
|
118
|
+
# collection as a whole.
|
119
|
+
def member_action?
|
120
|
+
@member_action
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def class_matchers
|
126
|
+
options = matcher_options
|
127
|
+
|
128
|
+
singleton_class
|
129
|
+
.ancestors
|
130
|
+
.select { |ancestor| ancestor < Cuprum::Rails::Responders::Matching }
|
131
|
+
.map { |ancestor| ancestor.matchers(**options) }
|
132
|
+
.flatten
|
133
|
+
end
|
134
|
+
|
135
|
+
def matcher_options
|
136
|
+
{}
|
137
|
+
end
|
138
|
+
|
139
|
+
def matchers
|
140
|
+
return class_matchers if matcher.nil?
|
141
|
+
|
142
|
+
[matcher, *class_matchers]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails/responders'
|
4
|
+
|
5
|
+
module Cuprum::Rails::Responders
|
6
|
+
# Implements serializing a result value into response data.
|
7
|
+
module Serialization
|
8
|
+
# @param root_serializer [Class] The root serializer for serializing the
|
9
|
+
# result value.
|
10
|
+
# @param serializers [Hash<Class, Object>] The serializers for converting
|
11
|
+
# result values into serialized data.
|
12
|
+
# @param options [Hash] Additional parameters for the responder.
|
13
|
+
def initialize(root_serializer:, serializers:, **options)
|
14
|
+
super(**options)
|
15
|
+
|
16
|
+
@root_serializer = root_serializer
|
17
|
+
@serializers = serializers
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Object] the root serializer for serializing the result value.
|
21
|
+
attr_reader :root_serializer
|
22
|
+
|
23
|
+
# @return [Hash<Class, Object>] The serializers for converting result values
|
24
|
+
# into serialized data.
|
25
|
+
attr_reader :serializers
|
26
|
+
|
27
|
+
# Converts a result value into a serialized data structure.
|
28
|
+
#
|
29
|
+
# @param object [Object] The object to serialize.
|
30
|
+
#
|
31
|
+
# @return [Object] the serialized data.
|
32
|
+
def serialize(object)
|
33
|
+
root_serializer.call(object, serializers: serializers)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails'
|
4
|
+
|
5
|
+
module Cuprum::Rails
|
6
|
+
# Namespace for responders, which process action results into responses.
|
7
|
+
module Responders
|
8
|
+
autoload :Actions, 'cuprum/rails/responders/actions'
|
9
|
+
autoload :Html, 'cuprum/rails/responders/html'
|
10
|
+
autoload :HtmlResponder, 'cuprum/rails/responders/html_responder'
|
11
|
+
autoload :Json, 'cuprum/rails/responders/json'
|
12
|
+
autoload :JsonResponder, 'cuprum/rails/responders/json_responder'
|
13
|
+
autoload :Matching, 'cuprum/rails/responders/matching'
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails/responses/html'
|
4
|
+
|
5
|
+
module Cuprum::Rails::Responses::Html
|
6
|
+
# Encapsulates an HTML response that redirects to a given path.
|
7
|
+
class RedirectResponse
|
8
|
+
# @param path [String] The path or url to redirect to.
|
9
|
+
# @param status [Integer] The HTTP status of the response.
|
10
|
+
def initialize(path, status: 302)
|
11
|
+
@path = path
|
12
|
+
@status = status
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [String] the path or url to redirect to.
|
16
|
+
attr_reader :path
|
17
|
+
|
18
|
+
# @return [Integer] the HTTP status of the response.
|
19
|
+
attr_reader :status
|
20
|
+
|
21
|
+
# Calls the renderer's #redirect_to method with the path and status.
|
22
|
+
#
|
23
|
+
# @param renderer [#redirect_to] The context for executing the response,
|
24
|
+
# such as a Rails controller.
|
25
|
+
def call(renderer)
|
26
|
+
renderer.redirect_to(path, status: status)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails/responses/html'
|
4
|
+
|
5
|
+
module Cuprum::Rails::Responses::Html
|
6
|
+
# Encapsulates an HTML response that renders a given template.
|
7
|
+
class RenderResponse
|
8
|
+
# @param assigns [Hash] Variables to assign when rendering the template.
|
9
|
+
# @param layout [String] The layout to render.
|
10
|
+
# @param status [Integer] The HTTP status of the response.
|
11
|
+
# @param template [String, Symbol] The template to render.
|
12
|
+
def initialize(template, assigns: {}, layout: nil, status: 200)
|
13
|
+
@assigns = assigns
|
14
|
+
@layout = layout
|
15
|
+
@status = status
|
16
|
+
@template = template
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Hash] variables to assign when rendering the template.
|
20
|
+
attr_reader :assigns
|
21
|
+
|
22
|
+
# @return [String] the layout to render.
|
23
|
+
attr_reader :layout
|
24
|
+
|
25
|
+
# @return [Integer] the HTTP status of the response.
|
26
|
+
attr_reader :status
|
27
|
+
|
28
|
+
# @return [String, Symbol] the template to render.
|
29
|
+
attr_reader :template
|
30
|
+
|
31
|
+
# Calls the renderer's #render method with the template and parameters.
|
32
|
+
#
|
33
|
+
# @param renderer [#render] The context for executing the response, such as
|
34
|
+
# a Rails controller.
|
35
|
+
def call(renderer)
|
36
|
+
assign_variables(renderer)
|
37
|
+
|
38
|
+
options = { status: status }
|
39
|
+
options[:layout] = layout if layout
|
40
|
+
|
41
|
+
renderer.render(template, **options)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def assign_variables(renderer)
|
47
|
+
assigns.each do |key, value|
|
48
|
+
renderer.instance_variable_set("@#{key}", value)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails/responses'
|
4
|
+
|
5
|
+
module Cuprum::Rails::Responses
|
6
|
+
# Namespace for response objects, which encapsulate Html responses.
|
7
|
+
module Html
|
8
|
+
autoload :RedirectResponse, 'cuprum/rails/responses/html/redirect_response'
|
9
|
+
autoload :RenderResponse, 'cuprum/rails/responses/html/render_response'
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails/responses'
|
4
|
+
|
5
|
+
module Cuprum::Rails::Responses
|
6
|
+
# Encapsulates a JSON response that returns the given serialized data.
|
7
|
+
class JsonResponse
|
8
|
+
# @param data [Object] The JSON data to return.
|
9
|
+
# @param status [Integer] The HTTP status of the response.
|
10
|
+
def initialize(data:, status: 200)
|
11
|
+
@data = data
|
12
|
+
@status = status
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Object] the JSON data to return.
|
16
|
+
attr_reader :data
|
17
|
+
|
18
|
+
# @return [Integer] the HTTP status of the response.
|
19
|
+
attr_reader :status
|
20
|
+
|
21
|
+
# Calls the renderer's #render method with the serialized data and status.
|
22
|
+
#
|
23
|
+
# @param renderer [#render] The context for executing the response, such as
|
24
|
+
# a Rails controller.
|
25
|
+
def call(renderer)
|
26
|
+
renderer.render(json: data, status: status)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails'
|
4
|
+
|
5
|
+
module Cuprum::Rails
|
6
|
+
# Namespace for response objects, which encapsulate server responses.
|
7
|
+
module Responses
|
8
|
+
autoload :Html, 'cuprum/rails/responses/html'
|
9
|
+
autoload :JsonResponse, 'cuprum/rails/responses/json_response'
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails'
|
4
|
+
|
5
|
+
module Cuprum::Rails
|
6
|
+
# Represent the routes available for a given resource.
|
7
|
+
class Routes
|
8
|
+
# Error class when a wildcard value is missing for a route.
|
9
|
+
class MissingWildcardError < StandardError; end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
# Defines a route for the resource routes.
|
13
|
+
#
|
14
|
+
# Each defined route creates a helper method on the routes, which returns
|
15
|
+
# the full path of the resource route. A member action helper (if the
|
16
|
+
# path ends in an :id wildcard) will require passing in either the primary
|
17
|
+
# key of the member or the member entity.
|
18
|
+
#
|
19
|
+
# If the base path includes wildcards, then the wildcards must be set
|
20
|
+
# (using #with_wildcards) before calling a route helper.
|
21
|
+
#
|
22
|
+
# @param action_name [String, Symbol] The name of the action.
|
23
|
+
# @param path [String] The path of the action relative to the resource
|
24
|
+
# root. If the path has a leading slash, the path is treated as an
|
25
|
+
# absolute path and does not include the routes base path.
|
26
|
+
def route(action_name, path)
|
27
|
+
validate_action_name!(action_name)
|
28
|
+
validate_path!(path)
|
29
|
+
|
30
|
+
if member_route?(path)
|
31
|
+
define_member_route(action_name, path)
|
32
|
+
else
|
33
|
+
define_collection_route(action_name, path)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def define_collection_route(action_name, original_path)
|
40
|
+
define_method :"#{action_name}_path" do
|
41
|
+
path = resolve_base_path(original_path)
|
42
|
+
|
43
|
+
insert_wildcards(path)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def define_member_route(action_name, original_path)
|
48
|
+
define_method :"#{action_name}_path" do |entity|
|
49
|
+
path = resolve_base_path(original_path)
|
50
|
+
|
51
|
+
insert_wildcards(path, entity)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def member_route?(path)
|
56
|
+
path.split('/').include?(':id')
|
57
|
+
end
|
58
|
+
|
59
|
+
def validate_action_name!(action_name)
|
60
|
+
unless action_name.is_a?(String) || action_name.is_a?(Symbol)
|
61
|
+
raise ArgumentError,
|
62
|
+
'action_name must be a String or Symbol',
|
63
|
+
caller(1..-1)
|
64
|
+
end
|
65
|
+
|
66
|
+
return unless action_name.to_s.empty?
|
67
|
+
|
68
|
+
raise ArgumentError, "action_name can't be blank", caller(1..-1)
|
69
|
+
end
|
70
|
+
|
71
|
+
def validate_path!(path)
|
72
|
+
return if path.is_a?(String) || path.is_a?(Symbol)
|
73
|
+
|
74
|
+
raise ArgumentError, 'path must be a String or Symbol', caller(1..-1)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# @param base_path [String] The relative path of the resource.
|
79
|
+
def initialize(base_path:, &block)
|
80
|
+
@base_path = base_path
|
81
|
+
@wildcards = {}
|
82
|
+
|
83
|
+
singleton_class.instance_exec(&block) if block_given?
|
84
|
+
end
|
85
|
+
|
86
|
+
# @return [String] the relative path of the resource.
|
87
|
+
attr_reader :base_path
|
88
|
+
|
89
|
+
# @return [String] the url wildcards for the resource.
|
90
|
+
attr_reader :wildcards
|
91
|
+
|
92
|
+
# @return [String] the path to the parent resource index.
|
93
|
+
def parent_path
|
94
|
+
root_path
|
95
|
+
end
|
96
|
+
|
97
|
+
# @return [String] the root path for the application.
|
98
|
+
def root_path
|
99
|
+
'/'
|
100
|
+
end
|
101
|
+
|
102
|
+
# @param wildcards [Hash] The wildcards to use with the routes.
|
103
|
+
#
|
104
|
+
# @return [Cuprum::Rails::Routes] a copy of the routes with the wildcards.
|
105
|
+
def with_wildcards(wildcards)
|
106
|
+
unless wildcards.is_a?(Hash)
|
107
|
+
raise ArgumentError, 'wildcards must be a Hash'
|
108
|
+
end
|
109
|
+
|
110
|
+
clone.apply_wildcards(wildcards)
|
111
|
+
end
|
112
|
+
|
113
|
+
protected
|
114
|
+
|
115
|
+
def apply_wildcards(wildcards)
|
116
|
+
@wildcards = wildcards.stringify_keys
|
117
|
+
|
118
|
+
self
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def insert_wildcards(path, entity = nil)
|
124
|
+
path
|
125
|
+
.split('/')
|
126
|
+
.map do |segment|
|
127
|
+
next segment unless segment.start_with?(':')
|
128
|
+
|
129
|
+
next resolve_primary_key(entity) if segment == ':id'
|
130
|
+
|
131
|
+
resolve_wildcard(segment)
|
132
|
+
end
|
133
|
+
.join('/')
|
134
|
+
end
|
135
|
+
|
136
|
+
def resolve_base_path(path)
|
137
|
+
return path if path.start_with?('/')
|
138
|
+
|
139
|
+
return base_path if path.empty?
|
140
|
+
|
141
|
+
"#{base_path}/#{path}"
|
142
|
+
end
|
143
|
+
|
144
|
+
def resolve_primary_key(entity)
|
145
|
+
raise MissingWildcardError, 'missing wildcard :id' if entity.nil?
|
146
|
+
|
147
|
+
primary_key = entity.class.primary_key
|
148
|
+
|
149
|
+
entity[primary_key]
|
150
|
+
end
|
151
|
+
|
152
|
+
def resolve_wildcard(segment)
|
153
|
+
wildcard = segment[1..] # :something_id to something_id
|
154
|
+
|
155
|
+
return wildcards[wildcard] if wildcards.include?(wildcard)
|
156
|
+
|
157
|
+
wildcard = segment[1...-3] # :something_id to something
|
158
|
+
|
159
|
+
if wildcards.include?(wildcard)
|
160
|
+
return resolve_primary_key(wildcards[wildcard])
|
161
|
+
end
|
162
|
+
|
163
|
+
raise MissingWildcardError, "missing wildcard #{segment}"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails/routing'
|
4
|
+
|
5
|
+
module Cuprum::Rails::Routing
|
6
|
+
# Routes object with predefined routes for a RESTful resource.
|
7
|
+
#
|
8
|
+
# The following route helpers are defined:
|
9
|
+
#
|
10
|
+
# - #create_path => '/' or '/path/to/resource/'
|
11
|
+
# - #destroy_path => '/:id' or '/path/to/resource/:id'
|
12
|
+
# - #edit_path => '/:id/edit' or '/path/to/resource/:id/edit'
|
13
|
+
# - #index_path => '/' or '/path/to/resource/'
|
14
|
+
# - #new_path => '/new' or '/path/to/resource/new'
|
15
|
+
# - #show_path => '/:id/edit' or '/path/to/resource/:id/edit'
|
16
|
+
# - #update_path => '/:id/edit' or '/path/to/resource/:id/edit'
|
17
|
+
class PluralRoutes < Cuprum::Rails::Routes
|
18
|
+
route :create, ''
|
19
|
+
route :destroy, ':id'
|
20
|
+
route :edit, ':id/edit'
|
21
|
+
route :index, ''
|
22
|
+
route :new, 'new'
|
23
|
+
route :show, ':id'
|
24
|
+
route :update, ':id'
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails/routing'
|
4
|
+
|
5
|
+
module Cuprum::Rails::Routing
|
6
|
+
# Routes object with predefined routes for a singular RESTful resource.
|
7
|
+
#
|
8
|
+
# The following route helpers are defined:
|
9
|
+
#
|
10
|
+
# - #create_path => '/' or '/path/to/resource/'
|
11
|
+
# - #destroy_path => '/' or '/path/to/resource/'
|
12
|
+
# - #edit_path => '/edit' or '/path/to/resource/edit'
|
13
|
+
# - #new_path => '/new' or '/path/to/resource/new'
|
14
|
+
# - #show_path => '/' or '/path/to/resource/'
|
15
|
+
# - #update_path => '/' or '/path/to/resource/'
|
16
|
+
class SingularRoutes < Cuprum::Rails::Routes
|
17
|
+
route :create, ''
|
18
|
+
route :destroy, ''
|
19
|
+
route :edit, 'edit'
|
20
|
+
route :new, 'new'
|
21
|
+
route :show, ''
|
22
|
+
route :update, ''
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails'
|
4
|
+
|
5
|
+
module Cuprum::Rails
|
6
|
+
# Namespace for routing-specific functionality.
|
7
|
+
module Routing
|
8
|
+
autoload :PluralRoutes, 'cuprum/rails/routing/plural_routes'
|
9
|
+
autoload :SingularRoutes, 'cuprum/rails/routing/singular_routes'
|
10
|
+
end
|
11
|
+
end
|