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,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
|