actionpack 5.2.1 → 7.0.2.4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +264 -220
- data/MIT-LICENSE +1 -1
- data/README.rdoc +6 -6
- data/lib/abstract_controller/asset_paths.rb +1 -1
- data/lib/abstract_controller/base.rb +24 -4
- data/lib/abstract_controller/caching/fragments.rb +8 -24
- data/lib/abstract_controller/caching.rb +2 -2
- data/lib/abstract_controller/callbacks.rb +34 -8
- data/lib/abstract_controller/collector.rb +5 -4
- data/lib/abstract_controller/error.rb +1 -1
- data/lib/abstract_controller/helpers.rb +107 -90
- data/lib/abstract_controller/logger.rb +1 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +19 -1
- data/lib/abstract_controller/rendering.rb +9 -9
- data/lib/abstract_controller/translation.rb +12 -5
- data/lib/abstract_controller/url_for.rb +4 -6
- data/lib/abstract_controller.rb +2 -0
- data/lib/action_controller/api.rb +5 -4
- data/lib/action_controller/base.rb +6 -9
- data/lib/action_controller/caching.rb +1 -3
- data/lib/action_controller/log_subscriber.rb +13 -9
- data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
- data/lib/action_controller/metal/conditional_get.rb +57 -6
- data/lib/action_controller/metal/content_security_policy.rb +2 -3
- data/lib/action_controller/metal/cookies.rb +4 -2
- data/lib/action_controller/metal/data_streaming.rb +9 -18
- data/lib/action_controller/metal/default_headers.rb +17 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +4 -6
- data/lib/action_controller/metal/exceptions.rb +55 -12
- data/lib/action_controller/metal/flash.rb +10 -6
- data/lib/action_controller/metal/head.rb +7 -4
- data/lib/action_controller/metal/helpers.rb +15 -6
- data/lib/action_controller/metal/http_authentication.rb +41 -39
- data/lib/action_controller/metal/implicit_render.rb +5 -15
- data/lib/action_controller/metal/instrumentation.rb +59 -55
- data/lib/action_controller/metal/live.rb +80 -33
- data/lib/action_controller/metal/logging.rb +20 -0
- data/lib/action_controller/metal/mime_responds.rb +22 -7
- data/lib/action_controller/metal/parameter_encoding.rb +35 -4
- data/lib/action_controller/metal/params_wrapper.rb +50 -31
- data/lib/action_controller/metal/permissions_policy.rb +46 -0
- data/lib/action_controller/metal/redirecting.rb +93 -23
- data/lib/action_controller/metal/renderers.rb +4 -4
- data/lib/action_controller/metal/rendering.rb +14 -9
- data/lib/action_controller/metal/request_forgery_protection.rb +160 -58
- data/lib/action_controller/metal/rescue.rb +2 -2
- data/lib/action_controller/metal/streaming.rb +1 -4
- data/lib/action_controller/metal/strong_parameters.rb +236 -88
- data/lib/action_controller/metal/testing.rb +9 -2
- data/lib/action_controller/metal/url_for.rb +1 -1
- data/lib/action_controller/metal.rb +16 -17
- data/lib/action_controller/railtie.rb +49 -6
- data/lib/action_controller/railties/helpers.rb +1 -1
- data/lib/action_controller/renderer.rb +37 -13
- data/lib/action_controller/template_assertions.rb +1 -1
- data/lib/action_controller/test_case.rb +98 -68
- data/lib/action_controller.rb +4 -5
- data/lib/action_dispatch/http/cache.rb +45 -32
- data/lib/action_dispatch/http/content_disposition.rb +45 -0
- data/lib/action_dispatch/http/content_security_policy.rb +69 -56
- data/lib/action_dispatch/http/filter_parameters.rb +14 -8
- data/lib/action_dispatch/http/filter_redirect.rb +2 -3
- data/lib/action_dispatch/http/headers.rb +4 -4
- data/lib/action_dispatch/http/mime_negotiation.rb +44 -16
- data/lib/action_dispatch/http/mime_type.rb +47 -30
- data/lib/action_dispatch/http/parameters.rb +18 -27
- data/lib/action_dispatch/http/permissions_policy.rb +173 -0
- data/lib/action_dispatch/http/request.rb +49 -35
- data/lib/action_dispatch/http/response.rb +34 -26
- data/lib/action_dispatch/http/upload.rb +9 -1
- data/lib/action_dispatch/http/url.rb +86 -94
- data/lib/action_dispatch/journey/formatter.rb +55 -31
- data/lib/action_dispatch/journey/gtg/builder.rb +30 -46
- data/lib/action_dispatch/journey/gtg/simulator.rb +15 -8
- data/lib/action_dispatch/journey/gtg/transition_table.rb +78 -21
- data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
- data/lib/action_dispatch/journey/nodes/node.rb +83 -16
- data/lib/action_dispatch/journey/parser.rb +13 -13
- data/lib/action_dispatch/journey/parser.y +1 -1
- data/lib/action_dispatch/journey/path/pattern.rb +42 -34
- data/lib/action_dispatch/journey/route.rb +14 -31
- data/lib/action_dispatch/journey/router/utils.rb +16 -14
- data/lib/action_dispatch/journey/router.rb +27 -35
- data/lib/action_dispatch/journey/routes.rb +3 -5
- data/lib/action_dispatch/journey/scanner.rb +10 -4
- data/lib/action_dispatch/journey/visitors.rb +1 -4
- data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
- data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
- data/lib/action_dispatch/journey.rb +0 -2
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +45 -0
- data/lib/action_dispatch/middleware/callbacks.rb +2 -4
- data/lib/action_dispatch/middleware/cookies.rb +136 -113
- data/lib/action_dispatch/middleware/debug_exceptions.rb +47 -68
- data/lib/action_dispatch/middleware/debug_locks.rb +8 -8
- data/lib/action_dispatch/middleware/debug_view.rb +66 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +79 -30
- data/lib/action_dispatch/middleware/executor.rb +4 -1
- data/lib/action_dispatch/middleware/flash.rb +10 -12
- data/lib/action_dispatch/middleware/host_authorization.rb +159 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
- data/lib/action_dispatch/middleware/remote_ip.rb +30 -20
- data/lib/action_dispatch/middleware/request_id.rb +5 -6
- data/lib/action_dispatch/middleware/server_timing.rb +33 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +16 -3
- data/lib/action_dispatch/middleware/session/cache_store.rb +11 -6
- data/lib/action_dispatch/middleware/session/cookie_store.rb +24 -19
- data/lib/action_dispatch/middleware/show_exceptions.rb +20 -11
- data/lib/action_dispatch/middleware/ssl.rb +20 -15
- data/lib/action_dispatch/middleware/stack.rb +79 -7
- data/lib/action_dispatch/middleware/static.rb +150 -94
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
- data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +6 -11
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +25 -6
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +9 -6
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +4 -1
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +121 -15
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +5 -5
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +5 -5
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +16 -2
- data/lib/action_dispatch/railtie.rb +16 -4
- data/lib/action_dispatch/request/session.rb +59 -22
- data/lib/action_dispatch/request/utils.rb +28 -2
- data/lib/action_dispatch/routing/inspector.rb +102 -54
- data/lib/action_dispatch/routing/mapper.rb +184 -156
- data/lib/action_dispatch/routing/polymorphic_routes.rb +21 -19
- data/lib/action_dispatch/routing/redirection.rb +4 -6
- data/lib/action_dispatch/routing/route_set.rb +83 -73
- data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
- data/lib/action_dispatch/routing/url_for.rb +2 -3
- data/lib/action_dispatch/routing.rb +23 -22
- data/lib/action_dispatch/system_test_case.rb +65 -16
- data/lib/action_dispatch/system_testing/browser.rb +43 -16
- data/lib/action_dispatch/system_testing/driver.rb +42 -10
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +58 -12
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +3 -10
- data/lib/action_dispatch/testing/assertion_response.rb +0 -1
- data/lib/action_dispatch/testing/assertions/response.rb +4 -7
- data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
- data/lib/action_dispatch/testing/assertions.rb +3 -6
- data/lib/action_dispatch/testing/integration.rb +61 -30
- data/lib/action_dispatch/testing/request_encoder.rb +2 -2
- data/lib/action_dispatch/testing/test_process.rb +8 -6
- data/lib/action_dispatch/testing/test_request.rb +3 -3
- data/lib/action_dispatch/testing/test_response.rb +4 -32
- data/lib/action_dispatch.rb +15 -7
- data/lib/action_pack/gem_version.rb +4 -4
- data/lib/action_pack.rb +1 -1
- metadata +44 -25
- data/lib/action_controller/metal/force_ssl.rb +0 -99
- data/lib/action_dispatch/http/parameter_filter.rb +0 -86
- data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
- data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
- data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
- data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -12,7 +12,7 @@ module ActionDispatch
|
|
12
12
|
class Mapper
|
13
13
|
URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
|
14
14
|
|
15
|
-
class Constraints < Routing::Endpoint
|
15
|
+
class Constraints < Routing::Endpoint # :nodoc:
|
16
16
|
attr_reader :app, :constraints
|
17
17
|
|
18
18
|
SERVE = ->(app, req) { app.serve req }
|
@@ -50,25 +50,41 @@ module ActionDispatch
|
|
50
50
|
|
51
51
|
private
|
52
52
|
def constraint_args(constraint, request)
|
53
|
-
|
53
|
+
arity = if constraint.respond_to?(:arity)
|
54
|
+
constraint.arity
|
55
|
+
else
|
56
|
+
constraint.method(:call).arity
|
57
|
+
end
|
58
|
+
|
59
|
+
if arity < 1
|
60
|
+
[]
|
61
|
+
elsif arity == 1
|
62
|
+
[request]
|
63
|
+
else
|
64
|
+
[request.path_parameters, request]
|
65
|
+
end
|
54
66
|
end
|
55
67
|
end
|
56
68
|
|
57
|
-
class Mapping
|
69
|
+
class Mapping # :nodoc:
|
58
70
|
ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
|
59
71
|
OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z}
|
60
72
|
|
61
|
-
attr_reader :requirements, :defaults
|
62
|
-
|
63
|
-
attr_reader :required_defaults, :ast
|
73
|
+
attr_reader :path, :requirements, :defaults, :to, :default_controller,
|
74
|
+
:default_action, :required_defaults, :ast, :scope_options
|
64
75
|
|
65
76
|
def self.build(scope, set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
77
|
+
scope_params = {
|
78
|
+
blocks: scope[:blocks] || [],
|
79
|
+
constraints: scope[:constraints] || {},
|
80
|
+
defaults: (scope[:defaults] || {}).dup,
|
81
|
+
module: scope[:module],
|
82
|
+
options: scope[:options] || {}
|
83
|
+
}
|
70
84
|
|
71
|
-
new set, ast
|
85
|
+
new set: set, ast: ast, controller: controller, default_action: default_action,
|
86
|
+
to: to, formatted: formatted, via: via, options_constraints: options_constraints,
|
87
|
+
anchor: anchor, scope_params: scope_params, options: scope_params[:options].merge(options)
|
72
88
|
end
|
73
89
|
|
74
90
|
def self.check_via(via)
|
@@ -96,43 +112,41 @@ module ActionDispatch
|
|
96
112
|
end
|
97
113
|
|
98
114
|
def self.optional_format?(path, format)
|
99
|
-
format != false && path
|
115
|
+
format != false && !path.match?(OPTIONAL_FORMAT_REGEX)
|
100
116
|
end
|
101
117
|
|
102
|
-
def initialize(set
|
103
|
-
@defaults
|
104
|
-
@set
|
105
|
-
|
106
|
-
@
|
107
|
-
@
|
108
|
-
@default_action = default_action
|
109
|
-
@ast = ast
|
118
|
+
def initialize(set:, ast:, controller:, default_action:, to:, formatted:, via:, options_constraints:, anchor:, scope_params:, options:)
|
119
|
+
@defaults = scope_params[:defaults]
|
120
|
+
@set = set
|
121
|
+
@to = intern(to)
|
122
|
+
@default_controller = intern(controller)
|
123
|
+
@default_action = intern(default_action)
|
110
124
|
@anchor = anchor
|
111
125
|
@via = via
|
112
126
|
@internal = options.delete(:internal)
|
127
|
+
@scope_options = scope_params[:options]
|
128
|
+
ast = Journey::Ast.new(ast, formatted)
|
113
129
|
|
114
|
-
|
115
|
-
|
116
|
-
options = add_wildcard_options(options, formatted, ast)
|
130
|
+
options = ast.wildcard_options.merge!(options)
|
117
131
|
|
118
|
-
options = normalize_options!(options, path_params,
|
132
|
+
options = normalize_options!(options, ast.path_params, scope_params[:module])
|
119
133
|
|
120
|
-
split_options = constraints(options, path_params)
|
134
|
+
split_options = constraints(options, ast.path_params)
|
121
135
|
|
122
|
-
constraints =
|
136
|
+
constraints = scope_params[:constraints].merge Hash[split_options[:constraints] || []]
|
123
137
|
|
124
138
|
if options_constraints.is_a?(Hash)
|
125
139
|
@defaults = Hash[options_constraints.find_all { |key, default|
|
126
140
|
URL_OPTIONS.include?(key) && (String === default || Integer === default)
|
127
141
|
}].merge @defaults
|
128
|
-
@blocks = blocks
|
142
|
+
@blocks = scope_params[:blocks]
|
129
143
|
constraints.merge! options_constraints
|
130
144
|
else
|
131
145
|
@blocks = blocks(options_constraints)
|
132
146
|
end
|
133
147
|
|
134
|
-
requirements, conditions = split_constraints path_params, constraints
|
135
|
-
verify_regexp_requirements requirements.
|
148
|
+
requirements, conditions = split_constraints ast.path_params, constraints
|
149
|
+
verify_regexp_requirements requirements, ast.wildcard_options
|
136
150
|
|
137
151
|
formats = normalize_format(formatted)
|
138
152
|
|
@@ -140,35 +154,29 @@ module ActionDispatch
|
|
140
154
|
@conditions = Hash[conditions]
|
141
155
|
@defaults = formats[:defaults].merge(@defaults).merge(normalize_defaults(options))
|
142
156
|
|
143
|
-
if path_params.include?(:action) && !@requirements.key?(:action)
|
157
|
+
if ast.path_params.include?(:action) && !@requirements.key?(:action)
|
144
158
|
@defaults[:action] ||= "index"
|
145
159
|
end
|
146
160
|
|
147
161
|
@required_defaults = (split_options[:required_defaults] || []).map(&:first)
|
162
|
+
|
163
|
+
ast.requirements = @requirements
|
164
|
+
@path = Journey::Path::Pattern.new(ast, @requirements, JOINED_SEPARATORS, @anchor)
|
148
165
|
end
|
149
166
|
|
167
|
+
JOINED_SEPARATORS = SEPARATORS.join # :nodoc:
|
168
|
+
|
150
169
|
def make_route(name, precedence)
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
required_defaults,
|
156
|
-
defaults,
|
157
|
-
request_method,
|
158
|
-
precedence,
|
159
|
-
@internal)
|
160
|
-
|
161
|
-
route
|
170
|
+
Journey::Route.new(name: name, app: application, path: path, constraints: conditions,
|
171
|
+
required_defaults: required_defaults, defaults: defaults,
|
172
|
+
request_method_match: request_method, precedence: precedence,
|
173
|
+
scope_options: scope_options, internal: @internal)
|
162
174
|
end
|
163
175
|
|
164
176
|
def application
|
165
177
|
app(@blocks)
|
166
178
|
end
|
167
179
|
|
168
|
-
def path
|
169
|
-
build_path @ast, requirements, @anchor
|
170
|
-
end
|
171
|
-
|
172
180
|
def conditions
|
173
181
|
build_conditions @conditions, @set.request_class
|
174
182
|
end
|
@@ -187,48 +195,9 @@ module ActionDispatch
|
|
187
195
|
end
|
188
196
|
private :request_method
|
189
197
|
|
190
|
-
JOINED_SEPARATORS = SEPARATORS.join # :nodoc:
|
191
|
-
|
192
|
-
def build_path(ast, requirements, anchor)
|
193
|
-
pattern = Journey::Path::Pattern.new(ast, requirements, JOINED_SEPARATORS, anchor)
|
194
|
-
|
195
|
-
# Find all the symbol nodes that are adjacent to literal nodes and alter
|
196
|
-
# the regexp so that Journey will partition them into custom routes.
|
197
|
-
ast.find_all { |node|
|
198
|
-
next unless node.cat?
|
199
|
-
|
200
|
-
if node.left.literal? && node.right.symbol?
|
201
|
-
symbol = node.right
|
202
|
-
elsif node.left.literal? && node.right.cat? && node.right.left.symbol?
|
203
|
-
symbol = node.right.left
|
204
|
-
elsif node.left.symbol? && node.right.literal?
|
205
|
-
symbol = node.left
|
206
|
-
elsif node.left.symbol? && node.right.cat? && node.right.left.literal?
|
207
|
-
symbol = node.left
|
208
|
-
else
|
209
|
-
next
|
210
|
-
end
|
211
|
-
|
212
|
-
if symbol
|
213
|
-
symbol.regexp = /(?:#{Regexp.union(symbol.regexp, '-')})+/
|
214
|
-
end
|
215
|
-
}
|
216
|
-
|
217
|
-
pattern
|
218
|
-
end
|
219
|
-
private :build_path
|
220
|
-
|
221
198
|
private
|
222
|
-
def
|
223
|
-
|
224
|
-
# optional format part of the route by default.
|
225
|
-
if formatted != false
|
226
|
-
path_ast.grep(Journey::Nodes::Star).each_with_object({}) { |node, hash|
|
227
|
-
hash[node.name.to_sym] ||= /.+?/
|
228
|
-
}.merge options
|
229
|
-
else
|
230
|
-
options
|
231
|
-
end
|
199
|
+
def intern(object)
|
200
|
+
object.is_a?(String) ? -object : object
|
232
201
|
end
|
233
202
|
|
234
203
|
def normalize_options!(options, path_params, modyoule)
|
@@ -277,14 +246,18 @@ module ActionDispatch
|
|
277
246
|
end
|
278
247
|
end
|
279
248
|
|
280
|
-
def verify_regexp_requirements(requirements)
|
281
|
-
requirements.each do |requirement|
|
282
|
-
|
249
|
+
def verify_regexp_requirements(requirements, wildcard_options)
|
250
|
+
requirements.each do |requirement, regex|
|
251
|
+
next unless regex.is_a? Regexp
|
252
|
+
|
253
|
+
if ANCHOR_CHARACTERS_REGEX.match?(regex.source)
|
283
254
|
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
|
284
255
|
end
|
285
256
|
|
286
|
-
if
|
287
|
-
|
257
|
+
if regex.multiline?
|
258
|
+
next if wildcard_options.key?(requirement)
|
259
|
+
|
260
|
+
raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{regex.inspect}"
|
288
261
|
end
|
289
262
|
end
|
290
263
|
end
|
@@ -308,8 +281,8 @@ module ActionDispatch
|
|
308
281
|
def check_controller_and_action(path_params, controller, action)
|
309
282
|
hash = check_part(:controller, controller, path_params, {}) do |part|
|
310
283
|
translate_controller(part) {
|
311
|
-
message = "'#{part}' is not a supported controller name. This can lead to potential routing problems."
|
312
|
-
message << " See
|
284
|
+
message = +"'#{part}' is not a supported controller name. This can lead to potential routing problems."
|
285
|
+
message << " See https://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
|
313
286
|
|
314
287
|
raise ArgumentError, message
|
315
288
|
}
|
@@ -333,8 +306,8 @@ module ActionDispatch
|
|
333
306
|
end
|
334
307
|
|
335
308
|
def split_to(to)
|
336
|
-
if to
|
337
|
-
to.split("#")
|
309
|
+
if /#/.match?(to)
|
310
|
+
to.split("#").map!(&:-@)
|
338
311
|
else
|
339
312
|
[]
|
340
313
|
end
|
@@ -342,10 +315,10 @@ module ActionDispatch
|
|
342
315
|
|
343
316
|
def add_controller_module(controller, modyoule)
|
344
317
|
if modyoule && !controller.is_a?(Regexp)
|
345
|
-
if controller
|
346
|
-
controller[1..-1]
|
318
|
+
if controller&.start_with?("/")
|
319
|
+
-controller[1..-1]
|
347
320
|
else
|
348
|
-
[modyoule, controller].compact.join("/")
|
321
|
+
-[modyoule, controller].compact.join("/")
|
349
322
|
end
|
350
323
|
else
|
351
324
|
controller
|
@@ -354,7 +327,7 @@ module ActionDispatch
|
|
354
327
|
|
355
328
|
def translate_controller(controller)
|
356
329
|
return controller if Regexp === controller
|
357
|
-
return controller.to_s if
|
330
|
+
return controller.to_s if /\A[a-z_0-9][a-z_0-9\/]*\z/.match?(controller)
|
358
331
|
|
359
332
|
yield
|
360
333
|
end
|
@@ -385,12 +358,23 @@ module ActionDispatch
|
|
385
358
|
end
|
386
359
|
end
|
387
360
|
|
388
|
-
# Invokes Journey::Router::Utils.normalize_path
|
389
|
-
# (:locale) becomes (/:locale)
|
390
|
-
#
|
361
|
+
# Invokes Journey::Router::Utils.normalize_path, then ensures that
|
362
|
+
# /(:locale) becomes (/:locale). Except for root cases, where the
|
363
|
+
# former is the correct one.
|
391
364
|
def self.normalize_path(path)
|
392
365
|
path = Journey::Router::Utils.normalize_path(path)
|
393
|
-
|
366
|
+
|
367
|
+
# the path for a root URL at this point can be something like
|
368
|
+
# "/(/:locale)(/:platform)/(:browser)", and we would want
|
369
|
+
# "/(:locale)(/:platform)(/:browser)"
|
370
|
+
|
371
|
+
# reverse "/(", "/((" etc to "(/", "((/" etc
|
372
|
+
path.gsub!(%r{/(\(+)/?}, '\1/')
|
373
|
+
# if a path is all optional segments, change the leading "(/" back to
|
374
|
+
# "/(" so it evaluates to "/" when interpreted with no options.
|
375
|
+
# Unless, however, at least one secondary segment consists of a static
|
376
|
+
# part, ex. "(/:locale)(/pages/:page)"
|
377
|
+
path.sub!(%r{^(\(+)/}, '/\1') if %r{^(\(+[^)]+\))(\(+/:[^)]+\))*$}.match?(path)
|
394
378
|
path
|
395
379
|
end
|
396
380
|
|
@@ -547,16 +531,16 @@ module ActionDispatch
|
|
547
531
|
# Constrains parameters with a hash of regular expressions
|
548
532
|
# or an object that responds to <tt>matches?</tt>. In addition, constraints
|
549
533
|
# other than path can also be specified with any object
|
550
|
-
# that responds to <tt>===</tt> (
|
534
|
+
# that responds to <tt>===</tt> (e.g. String, Array, Range, etc.).
|
551
535
|
#
|
552
536
|
# match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via: :get
|
553
537
|
#
|
554
538
|
# match 'json_only', constraints: { format: 'json' }, via: :get
|
555
539
|
#
|
556
|
-
# class
|
540
|
+
# class PermitList
|
557
541
|
# def matches?(request) request.remote_ip == '1.2.3.4' end
|
558
542
|
# end
|
559
|
-
# match 'path', to: 'c#a', constraints:
|
543
|
+
# match 'path', to: 'c#a', constraints: PermitList.new, via: :get
|
560
544
|
#
|
561
545
|
# See <tt>Scoping#constraints</tt> for more examples with its scope
|
562
546
|
# equivalent.
|
@@ -611,7 +595,7 @@ module ActionDispatch
|
|
611
595
|
end
|
612
596
|
|
613
597
|
raise ArgumentError, "A rack application must be specified" unless app.respond_to?(:call)
|
614
|
-
raise ArgumentError,
|
598
|
+
raise ArgumentError, <<~MSG unless path
|
615
599
|
Must be called with mount point
|
616
600
|
|
617
601
|
mount SomeRackApp, at: "some_route"
|
@@ -644,7 +628,7 @@ module ActionDispatch
|
|
644
628
|
|
645
629
|
# Query if the following named route was already defined.
|
646
630
|
def has_named_route?(name)
|
647
|
-
@set.named_routes.key?
|
631
|
+
@set.named_routes.key?(name)
|
648
632
|
end
|
649
633
|
|
650
634
|
private
|
@@ -668,7 +652,7 @@ module ActionDispatch
|
|
668
652
|
|
669
653
|
script_namer = ->(options) do
|
670
654
|
prefix_options = options.slice(*_route.segment_keys)
|
671
|
-
prefix_options[:relative_url_root] = ""
|
655
|
+
prefix_options[:relative_url_root] = ""
|
672
656
|
|
673
657
|
if options[:_recall]
|
674
658
|
prefix_options.reverse_merge!(options[:_recall].slice(*_route.segment_keys))
|
@@ -676,7 +660,7 @@ module ActionDispatch
|
|
676
660
|
|
677
661
|
# We must actually delete prefix segment keys to avoid passing them to next url_for.
|
678
662
|
_route.segment_keys.each { |k| options.delete(k) }
|
679
|
-
_url_helpers.
|
663
|
+
_url_helpers.public_send("#{name}_path", prefix_options)
|
680
664
|
end
|
681
665
|
|
682
666
|
app.routes.define_mounted_helper(name, script_namer)
|
@@ -736,6 +720,14 @@ module ActionDispatch
|
|
736
720
|
map_method(:delete, args, &block)
|
737
721
|
end
|
738
722
|
|
723
|
+
# Define a route that only recognizes HTTP OPTIONS.
|
724
|
+
# For supported arguments, see match[rdoc-ref:Base#match]
|
725
|
+
#
|
726
|
+
# options 'carrots', to: 'food#carrots'
|
727
|
+
def options(*args, &block)
|
728
|
+
map_method(:options, args, &block)
|
729
|
+
end
|
730
|
+
|
739
731
|
private
|
740
732
|
def map_method(method, args, &block)
|
741
733
|
options = args.extract_options!
|
@@ -934,7 +926,7 @@ module ActionDispatch
|
|
934
926
|
# namespace :admin, as: "sekret" do
|
935
927
|
# resources :posts
|
936
928
|
# end
|
937
|
-
def namespace(path, options = {})
|
929
|
+
def namespace(path, options = {}, &block)
|
938
930
|
path = path.to_s
|
939
931
|
|
940
932
|
defaults = {
|
@@ -945,7 +937,7 @@ module ActionDispatch
|
|
945
937
|
}
|
946
938
|
|
947
939
|
path_scope(options.delete(:path) { path }) do
|
948
|
-
scope(defaults.merge!(options))
|
940
|
+
scope(defaults.merge!(options), &block)
|
949
941
|
end
|
950
942
|
end
|
951
943
|
|
@@ -983,7 +975,7 @@ module ActionDispatch
|
|
983
975
|
#
|
984
976
|
# Requests to routes can be constrained based on specific criteria:
|
985
977
|
#
|
986
|
-
# constraints(-> (req) { req.env["HTTP_USER_AGENT"]
|
978
|
+
# constraints(-> (req) { /iPhone/.match?(req.env["HTTP_USER_AGENT"]) }) do
|
987
979
|
# resources :iphones
|
988
980
|
# end
|
989
981
|
#
|
@@ -993,7 +985,7 @@ module ActionDispatch
|
|
993
985
|
#
|
994
986
|
# class Iphone
|
995
987
|
# def self.matches?(request)
|
996
|
-
# request.env["HTTP_USER_AGENT"]
|
988
|
+
# /iPhone/.match?(request.env["HTTP_USER_AGENT"])
|
997
989
|
# end
|
998
990
|
# end
|
999
991
|
#
|
@@ -1004,8 +996,8 @@ module ActionDispatch
|
|
1004
996
|
# constraints(Iphone) do
|
1005
997
|
# resources :iphones
|
1006
998
|
# end
|
1007
|
-
def constraints(constraints = {})
|
1008
|
-
scope(constraints: constraints)
|
999
|
+
def constraints(constraints = {}, &block)
|
1000
|
+
scope(constraints: constraints, &block)
|
1009
1001
|
end
|
1010
1002
|
|
1011
1003
|
# Allows you to set default parameters for a route, such as this:
|
@@ -1134,10 +1126,14 @@ module ActionDispatch
|
|
1134
1126
|
RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns]
|
1135
1127
|
CANONICAL_ACTIONS = %w(index create new show update destroy)
|
1136
1128
|
|
1137
|
-
class Resource
|
1129
|
+
class Resource # :nodoc:
|
1138
1130
|
attr_reader :controller, :path, :param
|
1139
1131
|
|
1140
1132
|
def initialize(entities, api_only, shallow, options = {})
|
1133
|
+
if options[:param].to_s.include?(":")
|
1134
|
+
raise ArgumentError, ":param option can't contain colons"
|
1135
|
+
end
|
1136
|
+
|
1141
1137
|
@name = entities.to_s
|
1142
1138
|
@path = (options[:path] || @name).to_s
|
1143
1139
|
@controller = (options[:controller] || @name).to_s
|
@@ -1159,10 +1155,16 @@ module ActionDispatch
|
|
1159
1155
|
end
|
1160
1156
|
|
1161
1157
|
def actions
|
1158
|
+
if @except
|
1159
|
+
available_actions - Array(@except).map(&:to_sym)
|
1160
|
+
else
|
1161
|
+
available_actions
|
1162
|
+
end
|
1163
|
+
end
|
1164
|
+
|
1165
|
+
def available_actions
|
1162
1166
|
if @only
|
1163
1167
|
Array(@only).map(&:to_sym)
|
1164
|
-
elsif @except
|
1165
|
-
default_actions - Array(@except).map(&:to_sym)
|
1166
1168
|
else
|
1167
1169
|
default_actions
|
1168
1170
|
end
|
@@ -1219,7 +1221,7 @@ module ActionDispatch
|
|
1219
1221
|
def singleton?; false; end
|
1220
1222
|
end
|
1221
1223
|
|
1222
|
-
class SingletonResource < Resource
|
1224
|
+
class SingletonResource < Resource # :nodoc:
|
1223
1225
|
def initialize(entities, api_only, shallow, options)
|
1224
1226
|
super
|
1225
1227
|
@as = nil
|
@@ -1275,6 +1277,16 @@ module ActionDispatch
|
|
1275
1277
|
# DELETE /profile
|
1276
1278
|
# POST /profile
|
1277
1279
|
#
|
1280
|
+
# If you want instances of a model to work with this resource via
|
1281
|
+
# record identification (e.g. in +form_with+ or +redirect_to+), you
|
1282
|
+
# will need to call resolve[rdoc-ref:CustomUrls#resolve]:
|
1283
|
+
#
|
1284
|
+
# resource :profile
|
1285
|
+
# resolve('Profile') { [:profile] }
|
1286
|
+
#
|
1287
|
+
# # Enables this to work with singular routes:
|
1288
|
+
# form_with(model: @profile) {}
|
1289
|
+
#
|
1278
1290
|
# === Options
|
1279
1291
|
# Takes same options as resources[rdoc-ref:#resources]
|
1280
1292
|
def resource(*resources, &block)
|
@@ -1389,6 +1401,8 @@ module ActionDispatch
|
|
1389
1401
|
# as a comment on a blog post like <tt>/posts/a-long-permalink/comments/1234</tt>
|
1390
1402
|
# to be shortened to just <tt>/comments/1234</tt>.
|
1391
1403
|
#
|
1404
|
+
# Set <tt>shallow: false</tt> on a child resource to ignore a parent's shallow parameter.
|
1405
|
+
#
|
1392
1406
|
# [:shallow_path]
|
1393
1407
|
# Prefixes nested shallow routes with the specified path.
|
1394
1408
|
#
|
@@ -1431,6 +1445,9 @@ module ActionDispatch
|
|
1431
1445
|
# Allows you to specify the default value for optional +format+
|
1432
1446
|
# segment or disable it by supplying +false+.
|
1433
1447
|
#
|
1448
|
+
# [:param]
|
1449
|
+
# Allows you to override the default param name of +:id+ in the URL.
|
1450
|
+
#
|
1434
1451
|
# === Examples
|
1435
1452
|
#
|
1436
1453
|
# # routes call <tt>Admin::PostsController</tt>
|
@@ -1480,15 +1497,13 @@ module ActionDispatch
|
|
1480
1497
|
# with GET, and route to the search action of +PhotosController+. It will also
|
1481
1498
|
# create the <tt>search_photos_url</tt> and <tt>search_photos_path</tt>
|
1482
1499
|
# route helpers.
|
1483
|
-
def collection
|
1500
|
+
def collection(&block)
|
1484
1501
|
unless resource_scope?
|
1485
1502
|
raise ArgumentError, "can't use collection outside resource(s) scope"
|
1486
1503
|
end
|
1487
1504
|
|
1488
1505
|
with_scope_level(:collection) do
|
1489
|
-
path_scope(parent_resource.collection_scope)
|
1490
|
-
yield
|
1491
|
-
end
|
1506
|
+
path_scope(parent_resource.collection_scope, &block)
|
1492
1507
|
end
|
1493
1508
|
end
|
1494
1509
|
|
@@ -1503,7 +1518,7 @@ module ActionDispatch
|
|
1503
1518
|
# This will recognize <tt>/photos/1/preview</tt> with GET, and route to the
|
1504
1519
|
# preview action of +PhotosController+. It will also create the
|
1505
1520
|
# <tt>preview_photo_url</tt> and <tt>preview_photo_path</tt> helpers.
|
1506
|
-
def member
|
1521
|
+
def member(&block)
|
1507
1522
|
unless resource_scope?
|
1508
1523
|
raise ArgumentError, "can't use member outside resource(s) scope"
|
1509
1524
|
end
|
@@ -1511,27 +1526,25 @@ module ActionDispatch
|
|
1511
1526
|
with_scope_level(:member) do
|
1512
1527
|
if shallow?
|
1513
1528
|
shallow_scope {
|
1514
|
-
path_scope(parent_resource.member_scope)
|
1529
|
+
path_scope(parent_resource.member_scope, &block)
|
1515
1530
|
}
|
1516
1531
|
else
|
1517
|
-
path_scope(parent_resource.member_scope)
|
1532
|
+
path_scope(parent_resource.member_scope, &block)
|
1518
1533
|
end
|
1519
1534
|
end
|
1520
1535
|
end
|
1521
1536
|
|
1522
|
-
def new
|
1537
|
+
def new(&block)
|
1523
1538
|
unless resource_scope?
|
1524
1539
|
raise ArgumentError, "can't use new outside resource(s) scope"
|
1525
1540
|
end
|
1526
1541
|
|
1527
1542
|
with_scope_level(:new) do
|
1528
|
-
path_scope(parent_resource.new_scope(action_path(:new)))
|
1529
|
-
yield
|
1530
|
-
end
|
1543
|
+
path_scope(parent_resource.new_scope(action_path(:new)), &block)
|
1531
1544
|
end
|
1532
1545
|
end
|
1533
1546
|
|
1534
|
-
def nested
|
1547
|
+
def nested(&block)
|
1535
1548
|
unless resource_scope?
|
1536
1549
|
raise ArgumentError, "can't use nested outside resource(s) scope"
|
1537
1550
|
end
|
@@ -1540,12 +1553,12 @@ module ActionDispatch
|
|
1540
1553
|
if shallow? && shallow_nesting_depth >= 1
|
1541
1554
|
shallow_scope do
|
1542
1555
|
path_scope(parent_resource.nested_scope) do
|
1543
|
-
scope(nested_options)
|
1556
|
+
scope(nested_options, &block)
|
1544
1557
|
end
|
1545
1558
|
end
|
1546
1559
|
else
|
1547
1560
|
path_scope(parent_resource.nested_scope) do
|
1548
|
-
scope(nested_options)
|
1561
|
+
scope(nested_options, &block)
|
1549
1562
|
end
|
1550
1563
|
end
|
1551
1564
|
end
|
@@ -1571,6 +1584,22 @@ module ActionDispatch
|
|
1571
1584
|
!parent_resource.singleton? && @scope[:shallow]
|
1572
1585
|
end
|
1573
1586
|
|
1587
|
+
def draw(name)
|
1588
|
+
path = @draw_paths.find do |_path|
|
1589
|
+
File.exist? "#{_path}/#{name}.rb"
|
1590
|
+
end
|
1591
|
+
|
1592
|
+
unless path
|
1593
|
+
msg = "Your router tried to #draw the external file #{name}.rb,\n" \
|
1594
|
+
"but the file was not found in:\n\n"
|
1595
|
+
msg += @draw_paths.map { |_path| " * #{_path}" }.join("\n")
|
1596
|
+
raise ArgumentError, msg
|
1597
|
+
end
|
1598
|
+
|
1599
|
+
route_path = "#{path}/#{name}.rb"
|
1600
|
+
instance_eval(File.read(route_path), route_path.to_s)
|
1601
|
+
end
|
1602
|
+
|
1574
1603
|
# Matches a URL pattern to one or more routes.
|
1575
1604
|
# For more information, see match[rdoc-ref:Base#match].
|
1576
1605
|
#
|
@@ -1588,7 +1617,7 @@ module ActionDispatch
|
|
1588
1617
|
when Symbol
|
1589
1618
|
options[:action] = to
|
1590
1619
|
when String
|
1591
|
-
if to
|
1620
|
+
if /#/.match?(to)
|
1592
1621
|
options[:to] = to
|
1593
1622
|
else
|
1594
1623
|
options[:controller] = to
|
@@ -1645,26 +1674,26 @@ module ActionDispatch
|
|
1645
1674
|
end
|
1646
1675
|
|
1647
1676
|
private
|
1648
|
-
|
1649
1677
|
def parent_resource
|
1650
1678
|
@scope[:scope_level_resource]
|
1651
1679
|
end
|
1652
1680
|
|
1653
1681
|
def apply_common_behavior_for(method, resources, options, &block)
|
1654
1682
|
if resources.length > 1
|
1655
|
-
resources.each { |r|
|
1683
|
+
resources.each { |r| public_send(method, r, options, &block) }
|
1656
1684
|
return true
|
1657
1685
|
end
|
1658
1686
|
|
1659
|
-
if options
|
1687
|
+
if options[:shallow]
|
1688
|
+
options.delete(:shallow)
|
1660
1689
|
shallow do
|
1661
|
-
|
1690
|
+
public_send(method, resources.pop, options, &block)
|
1662
1691
|
end
|
1663
1692
|
return true
|
1664
1693
|
end
|
1665
1694
|
|
1666
1695
|
if resource_scope?
|
1667
|
-
nested {
|
1696
|
+
nested { public_send(method, resources.pop, options, &block) }
|
1668
1697
|
return true
|
1669
1698
|
end
|
1670
1699
|
|
@@ -1675,7 +1704,7 @@ module ActionDispatch
|
|
1675
1704
|
scope_options = options.slice!(*RESOURCE_OPTIONS)
|
1676
1705
|
unless scope_options.empty?
|
1677
1706
|
scope(scope_options) do
|
1678
|
-
|
1707
|
+
public_send(method, resources.pop, options, &block)
|
1679
1708
|
end
|
1680
1709
|
return true
|
1681
1710
|
end
|
@@ -1715,10 +1744,10 @@ module ActionDispatch
|
|
1715
1744
|
@scope = @scope.parent
|
1716
1745
|
end
|
1717
1746
|
|
1718
|
-
def resource_scope(resource)
|
1747
|
+
def resource_scope(resource, &block)
|
1719
1748
|
@scope = @scope.new(scope_level_resource: resource)
|
1720
1749
|
|
1721
|
-
controller(resource.resource_scope)
|
1750
|
+
controller(resource.resource_scope, &block)
|
1722
1751
|
ensure
|
1723
1752
|
@scope = @scope.parent
|
1724
1753
|
end
|
@@ -1805,7 +1834,7 @@ module ActionDispatch
|
|
1805
1834
|
# and return nil in case it isn't. Otherwise, we pass the invalid name
|
1806
1835
|
# forward so the underlying router engine treats it and raises an exception.
|
1807
1836
|
if as.nil?
|
1808
|
-
candidate unless candidate
|
1837
|
+
candidate unless !candidate.match?(/\A[_a-z]/i) || has_named_route?(candidate)
|
1809
1838
|
else
|
1810
1839
|
candidate
|
1811
1840
|
end
|
@@ -1836,7 +1865,7 @@ module ActionDispatch
|
|
1836
1865
|
end
|
1837
1866
|
|
1838
1867
|
def map_match(paths, options)
|
1839
|
-
if options[:on] && !VALID_ON_OPTIONS.include?(
|
1868
|
+
if (on = options[:on]) && !VALID_ON_OPTIONS.include?(on)
|
1840
1869
|
raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
|
1841
1870
|
end
|
1842
1871
|
|
@@ -1859,7 +1888,7 @@ module ActionDispatch
|
|
1859
1888
|
options_constraints = options.delete(:constraints) || {}
|
1860
1889
|
|
1861
1890
|
path_types = paths.group_by(&:class)
|
1862
|
-
path_types
|
1891
|
+
(path_types[String] || []).each do |_path|
|
1863
1892
|
route_options = options.dup
|
1864
1893
|
if _path && option_path
|
1865
1894
|
raise ArgumentError, "Ambiguous route definition. Both :path and the route path were specified as strings."
|
@@ -1868,7 +1897,7 @@ module ActionDispatch
|
|
1868
1897
|
decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints)
|
1869
1898
|
end
|
1870
1899
|
|
1871
|
-
path_types
|
1900
|
+
(path_types[Symbol] || []).each do |action|
|
1872
1901
|
route_options = options.dup
|
1873
1902
|
decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints)
|
1874
1903
|
end
|
@@ -1881,14 +1910,14 @@ module ActionDispatch
|
|
1881
1910
|
|
1882
1911
|
path_without_format = path.sub(/\(\.:format\)$/, "")
|
1883
1912
|
if using_match_shorthand?(path_without_format)
|
1884
|
-
path_without_format.
|
1913
|
+
path_without_format.delete_prefix("/").sub(%r{/([^/]*)$}, '#\1').tr("-", "_")
|
1885
1914
|
else
|
1886
1915
|
nil
|
1887
1916
|
end
|
1888
1917
|
end
|
1889
1918
|
|
1890
1919
|
def using_match_shorthand?(path)
|
1891
|
-
|
1920
|
+
%r{^/?[-\w]+/[-\w/]+$}.match?(path)
|
1892
1921
|
end
|
1893
1922
|
|
1894
1923
|
def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
|
@@ -1914,7 +1943,7 @@ module ActionDispatch
|
|
1914
1943
|
|
1915
1944
|
default_action = options.delete(:action) || @scope[:action]
|
1916
1945
|
|
1917
|
-
if
|
1946
|
+
if /^[\w\-\/]+$/.match?(action)
|
1918
1947
|
default_action ||= action.tr("-", "_") unless action.include?("/")
|
1919
1948
|
else
|
1920
1949
|
action = nil
|
@@ -1926,7 +1955,7 @@ module ActionDispatch
|
|
1926
1955
|
name_for_action(options.delete(:as), action)
|
1927
1956
|
end
|
1928
1957
|
|
1929
|
-
path = Mapping.normalize_path URI.
|
1958
|
+
path = Mapping.normalize_path URI::DEFAULT_PARSER.escape(path), formatted
|
1930
1959
|
ast = Journey::Parser.parse path
|
1931
1960
|
|
1932
1961
|
mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
|
@@ -1934,9 +1963,7 @@ module ActionDispatch
|
|
1934
1963
|
end
|
1935
1964
|
|
1936
1965
|
def match_root_route(options)
|
1937
|
-
|
1938
|
-
args = ["/", { as: name, via: :get }.merge!(options)]
|
1939
|
-
|
1966
|
+
args = ["/", { as: :root, via: :get }.merge(options)]
|
1940
1967
|
match(*args)
|
1941
1968
|
end
|
1942
1969
|
end
|
@@ -2052,7 +2079,7 @@ module ActionDispatch
|
|
2052
2079
|
# of routing helpers, e.g:
|
2053
2080
|
#
|
2054
2081
|
# direct :homepage do
|
2055
|
-
# "
|
2082
|
+
# "https://rubyonrails.org"
|
2056
2083
|
# end
|
2057
2084
|
#
|
2058
2085
|
# direct :commentable do |model|
|
@@ -2249,8 +2276,9 @@ module ActionDispatch
|
|
2249
2276
|
NULL = Scope.new(nil, nil)
|
2250
2277
|
end
|
2251
2278
|
|
2252
|
-
def initialize(set)
|
2279
|
+
def initialize(set) # :nodoc:
|
2253
2280
|
@set = set
|
2281
|
+
@draw_paths = set.draw_paths
|
2254
2282
|
@scope = Scope.new(path_names: @set.resources_path_names)
|
2255
2283
|
@concerns = {}
|
2256
2284
|
end
|