actionpack 4.2.11.1 → 6.1.3.2
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 +291 -489
- data/MIT-LICENSE +1 -1
- data/README.rdoc +9 -9
- data/lib/abstract_controller/asset_paths.rb +2 -0
- data/lib/abstract_controller/base.rb +81 -51
- data/lib/{action_controller → abstract_controller}/caching/fragments.rb +64 -17
- data/lib/abstract_controller/caching.rb +66 -0
- data/lib/abstract_controller/callbacks.rb +61 -33
- data/lib/abstract_controller/collector.rb +9 -13
- data/lib/abstract_controller/error.rb +6 -0
- data/lib/abstract_controller/helpers.rb +115 -99
- data/lib/abstract_controller/logger.rb +2 -0
- data/lib/abstract_controller/railties/routes_helpers.rb +21 -3
- data/lib/abstract_controller/rendering.rb +48 -47
- data/lib/abstract_controller/translation.rb +17 -8
- data/lib/abstract_controller/url_for.rb +2 -0
- data/lib/abstract_controller.rb +13 -5
- data/lib/action_controller/api/api_rendering.rb +16 -0
- data/lib/action_controller/api.rb +150 -0
- data/lib/action_controller/base.rb +29 -24
- data/lib/action_controller/caching.rb +12 -57
- data/lib/action_controller/form_builder.rb +50 -0
- data/lib/action_controller/log_subscriber.rb +17 -19
- data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
- data/lib/action_controller/metal/conditional_get.rb +134 -46
- data/lib/action_controller/metal/content_security_policy.rb +51 -0
- data/lib/action_controller/metal/cookies.rb +6 -4
- data/lib/action_controller/metal/data_streaming.rb +30 -50
- data/lib/action_controller/metal/default_headers.rb +17 -0
- data/lib/action_controller/metal/etag_with_flash.rb +18 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +21 -16
- data/lib/action_controller/metal/exceptions.rb +63 -15
- data/lib/action_controller/metal/flash.rb +9 -8
- data/lib/action_controller/metal/head.rb +26 -21
- data/lib/action_controller/metal/helpers.rb +37 -18
- data/lib/action_controller/metal/http_authentication.rb +81 -73
- data/lib/action_controller/metal/implicit_render.rb +53 -9
- data/lib/action_controller/metal/instrumentation.rb +32 -35
- data/lib/action_controller/metal/live.rb +102 -120
- data/lib/action_controller/metal/logging.rb +20 -0
- data/lib/action_controller/metal/mime_responds.rb +49 -47
- data/lib/action_controller/metal/parameter_encoding.rb +82 -0
- data/lib/action_controller/metal/params_wrapper.rb +83 -66
- data/lib/action_controller/metal/permissions_policy.rb +46 -0
- data/lib/action_controller/metal/redirecting.rb +53 -32
- data/lib/action_controller/metal/renderers.rb +87 -44
- data/lib/action_controller/metal/rendering.rb +77 -50
- data/lib/action_controller/metal/request_forgery_protection.rb +267 -103
- data/lib/action_controller/metal/rescue.rb +10 -17
- data/lib/action_controller/metal/streaming.rb +12 -11
- data/lib/action_controller/metal/strong_parameters.rb +714 -186
- data/lib/action_controller/metal/testing.rb +2 -17
- data/lib/action_controller/metal/url_for.rb +19 -10
- data/lib/action_controller/metal.rb +104 -87
- data/lib/action_controller/railtie.rb +28 -10
- data/lib/action_controller/railties/helpers.rb +3 -1
- data/lib/action_controller/renderer.rb +141 -0
- data/lib/action_controller/template_assertions.rb +11 -0
- data/lib/action_controller/test_case.rb +296 -422
- data/lib/action_controller.rb +34 -23
- data/lib/action_dispatch/http/cache.rb +107 -56
- data/lib/action_dispatch/http/content_disposition.rb +45 -0
- data/lib/action_dispatch/http/content_security_policy.rb +286 -0
- data/lib/action_dispatch/http/filter_parameters.rb +32 -25
- data/lib/action_dispatch/http/filter_redirect.rb +10 -12
- data/lib/action_dispatch/http/headers.rb +55 -22
- data/lib/action_dispatch/http/mime_negotiation.rb +79 -51
- data/lib/action_dispatch/http/mime_type.rb +153 -121
- data/lib/action_dispatch/http/mime_types.rb +20 -6
- data/lib/action_dispatch/http/parameters.rb +90 -40
- data/lib/action_dispatch/http/permissions_policy.rb +173 -0
- data/lib/action_dispatch/http/rack_cache.rb +2 -0
- data/lib/action_dispatch/http/request.rb +226 -121
- data/lib/action_dispatch/http/response.rb +248 -113
- data/lib/action_dispatch/http/upload.rb +21 -7
- data/lib/action_dispatch/http/url.rb +182 -100
- data/lib/action_dispatch/journey/formatter.rb +90 -43
- data/lib/action_dispatch/journey/gtg/builder.rb +28 -41
- data/lib/action_dispatch/journey/gtg/simulator.rb +11 -16
- data/lib/action_dispatch/journey/gtg/transition_table.rb +23 -21
- data/lib/action_dispatch/journey/nfa/dot.rb +3 -14
- data/lib/action_dispatch/journey/nodes/node.rb +29 -15
- data/lib/action_dispatch/journey/parser.rb +17 -16
- data/lib/action_dispatch/journey/parser.y +4 -3
- data/lib/action_dispatch/journey/parser_extras.rb +12 -4
- data/lib/action_dispatch/journey/path/pattern.rb +58 -54
- data/lib/action_dispatch/journey/route.rb +100 -32
- data/lib/action_dispatch/journey/router/utils.rb +29 -18
- data/lib/action_dispatch/journey/router.rb +55 -51
- data/lib/action_dispatch/journey/routes.rb +17 -17
- data/lib/action_dispatch/journey/scanner.rb +26 -17
- data/lib/action_dispatch/journey/visitors.rb +98 -54
- data/lib/action_dispatch/journey.rb +5 -5
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
- data/lib/action_dispatch/middleware/callbacks.rb +3 -6
- data/lib/action_dispatch/middleware/cookies.rb +347 -217
- data/lib/action_dispatch/middleware/debug_exceptions.rb +135 -63
- data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
- data/lib/action_dispatch/middleware/debug_view.rb +66 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +115 -71
- data/lib/action_dispatch/middleware/executor.rb +21 -0
- data/lib/action_dispatch/middleware/flash.rb +78 -54
- data/lib/action_dispatch/middleware/host_authorization.rb +130 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +32 -27
- data/lib/action_dispatch/middleware/reloader.rb +5 -91
- data/lib/action_dispatch/middleware/remote_ip.rb +53 -45
- data/lib/action_dispatch/middleware/request_id.rb +17 -10
- data/lib/action_dispatch/middleware/session/abstract_store.rb +41 -26
- data/lib/action_dispatch/middleware/session/cache_store.rb +24 -14
- data/lib/action_dispatch/middleware/session/cookie_store.rb +74 -75
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -2
- data/lib/action_dispatch/middleware/show_exceptions.rb +28 -23
- data/lib/action_dispatch/middleware/ssl.rb +118 -35
- data/lib/action_dispatch/middleware/stack.rb +82 -41
- data/lib/action_dispatch/middleware/static.rb +156 -89
- 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 +4 -14
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +4 -2
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +23 -4
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +15 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +105 -8
- 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 +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +87 -64
- data/lib/action_dispatch/railtie.rb +27 -13
- data/lib/action_dispatch/request/session.rb +109 -61
- data/lib/action_dispatch/request/utils.rb +90 -23
- data/lib/action_dispatch/routing/endpoint.rb +9 -2
- data/lib/action_dispatch/routing/inspector.rb +141 -102
- data/lib/action_dispatch/routing/mapper.rb +811 -473
- data/lib/action_dispatch/routing/polymorphic_routes.rb +167 -143
- data/lib/action_dispatch/routing/redirection.rb +37 -27
- data/lib/action_dispatch/routing/route_set.rb +363 -331
- data/lib/action_dispatch/routing/routes_proxy.rb +32 -5
- data/lib/action_dispatch/routing/url_for.rb +66 -26
- data/lib/action_dispatch/routing.rb +36 -36
- data/lib/action_dispatch/system_test_case.rb +190 -0
- data/lib/action_dispatch/system_testing/browser.rb +86 -0
- data/lib/action_dispatch/system_testing/driver.rb +67 -0
- data/lib/action_dispatch/system_testing/server.rb +31 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +138 -0
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +29 -0
- data/lib/action_dispatch/testing/assertion_response.rb +46 -0
- data/lib/action_dispatch/testing/assertions/response.rb +44 -22
- data/lib/action_dispatch/testing/assertions/routing.rb +47 -31
- data/lib/action_dispatch/testing/assertions.rb +6 -4
- data/lib/action_dispatch/testing/integration.rb +391 -220
- data/lib/action_dispatch/testing/request_encoder.rb +55 -0
- data/lib/action_dispatch/testing/test_process.rb +53 -22
- data/lib/action_dispatch/testing/test_request.rb +27 -34
- data/lib/action_dispatch/testing/test_response.rb +11 -11
- data/lib/action_dispatch.rb +35 -21
- data/lib/action_pack/gem_version.rb +6 -4
- data/lib/action_pack/version.rb +3 -1
- data/lib/action_pack.rb +4 -2
- metadata +78 -48
- data/lib/action_controller/metal/force_ssl.rb +0 -97
- data/lib/action_controller/metal/hide_actions.rb +0 -40
- data/lib/action_controller/metal/rack_delegation.rb +0 -32
- data/lib/action_controller/middleware.rb +0 -39
- data/lib/action_controller/model_naming.rb +0 -12
- data/lib/action_dispatch/http/parameter_filter.rb +0 -72
- data/lib/action_dispatch/journey/backwards.rb +0 -5
- data/lib/action_dispatch/journey/nfa/builder.rb +0 -76
- data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
- data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -163
- data/lib/action_dispatch/journey/router/strexp.rb +0 -27
- data/lib/action_dispatch/middleware/params_parser.rb +0 -60
- data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
- data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
- data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,39 +1,40 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require 'action_dispatch/routing/endpoint'
|
11
|
-
require 'active_support/deprecation'
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/hash/slice"
|
4
|
+
require "active_support/core_ext/enumerable"
|
5
|
+
require "active_support/core_ext/array/extract_options"
|
6
|
+
require "active_support/core_ext/regexp"
|
7
|
+
require "active_support/core_ext/symbol/starts_ends_with"
|
8
|
+
require "action_dispatch/routing/redirection"
|
9
|
+
require "action_dispatch/routing/endpoint"
|
12
10
|
|
13
11
|
module ActionDispatch
|
14
12
|
module Routing
|
15
13
|
class Mapper
|
16
14
|
URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
|
17
15
|
|
18
|
-
class Constraints < Endpoint #:nodoc:
|
16
|
+
class Constraints < Routing::Endpoint #:nodoc:
|
19
17
|
attr_reader :app, :constraints
|
20
18
|
|
21
|
-
|
22
|
-
|
19
|
+
SERVE = ->(app, req) { app.serve req }
|
20
|
+
CALL = ->(app, req) { app.call req.env }
|
21
|
+
|
22
|
+
def initialize(app, constraints, strategy)
|
23
|
+
# Unwrap Constraints objects. I don't actually think it's possible
|
23
24
|
# to pass a Constraints object to this constructor, but there were
|
24
|
-
# multiple places that kept testing children of this object.
|
25
|
+
# multiple places that kept testing children of this object. I
|
25
26
|
# *think* they were just being defensive, but I have no idea.
|
26
27
|
if app.is_a?(self.class)
|
27
28
|
constraints += app.constraints
|
28
29
|
app = app.app
|
29
30
|
end
|
30
31
|
|
31
|
-
@
|
32
|
+
@strategy = strategy
|
32
33
|
|
33
34
|
@app, @constraints, = app, constraints
|
34
35
|
end
|
35
36
|
|
36
|
-
def dispatcher?; @
|
37
|
+
def dispatcher?; @strategy == SERVE; end
|
37
38
|
|
38
39
|
def matches?(req)
|
39
40
|
@constraints.all? do |constraint|
|
@@ -43,18 +44,26 @@ module ActionDispatch
|
|
43
44
|
end
|
44
45
|
|
45
46
|
def serve(req)
|
46
|
-
return [ 404, {
|
47
|
+
return [ 404, { "X-Cascade" => "pass" }, [] ] unless matches?(req)
|
47
48
|
|
48
|
-
|
49
|
-
@app.serve req
|
50
|
-
else
|
51
|
-
@app.call req.env
|
52
|
-
end
|
49
|
+
@strategy.call @app, req
|
53
50
|
end
|
54
51
|
|
55
52
|
private
|
56
53
|
def constraint_args(constraint, request)
|
57
|
-
|
54
|
+
arity = if constraint.respond_to?(:arity)
|
55
|
+
constraint.arity
|
56
|
+
else
|
57
|
+
constraint.method(:call).arity
|
58
|
+
end
|
59
|
+
|
60
|
+
if arity < 1
|
61
|
+
[]
|
62
|
+
elsif arity == 1
|
63
|
+
[request]
|
64
|
+
else
|
65
|
+
[request.path_parameters, request]
|
66
|
+
end
|
58
67
|
end
|
59
68
|
end
|
60
69
|
|
@@ -62,101 +71,170 @@ module ActionDispatch
|
|
62
71
|
ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
|
63
72
|
OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z}
|
64
73
|
|
65
|
-
attr_reader :requirements, :
|
66
|
-
|
74
|
+
attr_reader :requirements, :defaults, :to, :default_controller,
|
75
|
+
:default_action, :required_defaults, :ast, :scope_options
|
67
76
|
|
68
|
-
def self.build(scope, set,
|
69
|
-
|
77
|
+
def self.build(scope, set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
|
78
|
+
scope_params = {
|
79
|
+
blocks: scope[:blocks] || [],
|
80
|
+
constraints: scope[:constraints] || {},
|
81
|
+
defaults: (scope[:defaults] || {}).dup,
|
82
|
+
module: scope[:module],
|
83
|
+
options: scope[:options] || {}
|
84
|
+
}
|
70
85
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
86
|
+
new set: set, ast: ast, controller: controller, default_action: default_action,
|
87
|
+
to: to, formatted: formatted, via: via, options_constraints: options_constraints,
|
88
|
+
anchor: anchor, scope_params: scope_params, options: scope_params[:options].merge(options)
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.check_via(via)
|
92
|
+
if via.empty?
|
93
|
+
msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
|
94
|
+
"If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
|
95
|
+
"If you want to expose your action to GET, use `get` in the router:\n" \
|
96
|
+
" Instead of: match \"controller#action\"\n" \
|
97
|
+
" Do: get \"controller#action\""
|
98
|
+
raise ArgumentError, msg
|
99
|
+
end
|
100
|
+
via
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.normalize_path(path, format)
|
104
|
+
path = Mapper.normalize_path(path)
|
76
105
|
|
77
|
-
|
106
|
+
if format == true
|
107
|
+
"#{path}.:format"
|
108
|
+
elsif optional_format?(path, format)
|
109
|
+
"#{path}(.:format)"
|
110
|
+
else
|
111
|
+
path
|
112
|
+
end
|
113
|
+
end
|
78
114
|
|
79
|
-
|
115
|
+
def self.optional_format?(path, format)
|
116
|
+
format != false && !path.match?(OPTIONAL_FORMAT_REGEX)
|
80
117
|
end
|
81
118
|
|
82
|
-
def initialize(
|
83
|
-
@
|
84
|
-
@
|
85
|
-
@
|
119
|
+
def initialize(set:, ast:, controller:, default_action:, to:, formatted:, via:, options_constraints:, anchor:, scope_params:, options:)
|
120
|
+
@defaults = scope_params[:defaults]
|
121
|
+
@set = set
|
122
|
+
@to = intern(to)
|
123
|
+
@default_controller = intern(controller)
|
124
|
+
@default_action = intern(default_action)
|
125
|
+
@ast = ast
|
126
|
+
@anchor = anchor
|
127
|
+
@via = via
|
128
|
+
@internal = options.delete(:internal)
|
129
|
+
@scope_options = scope_params[:options]
|
130
|
+
|
131
|
+
path_params = []
|
132
|
+
wildcard_options = {}
|
133
|
+
ast.each do |node|
|
134
|
+
if node.symbol?
|
135
|
+
path_params << node.to_sym
|
136
|
+
elsif formatted != false && node.star?
|
137
|
+
# Add a constraint for wildcard route to make it non-greedy and match the
|
138
|
+
# optional format part of the route by default.
|
139
|
+
wildcard_options[node.name.to_sym] ||= /.+?/
|
140
|
+
elsif node.cat?
|
141
|
+
alter_regex_for_custom_routes(node)
|
142
|
+
end
|
143
|
+
end
|
86
144
|
|
87
|
-
|
88
|
-
@default_controller = options.delete(:controller) || scope[:controller]
|
89
|
-
@default_action = options.delete(:action) || scope[:action]
|
90
|
-
@as = as
|
91
|
-
@anchor = options.delete :anchor
|
145
|
+
options = wildcard_options.merge!(options)
|
92
146
|
|
93
|
-
|
94
|
-
via = Array(options.delete(:via) { [] })
|
95
|
-
options_constraints = options.delete :constraints
|
147
|
+
options = normalize_options!(options, path_params, scope_params[:module])
|
96
148
|
|
97
|
-
|
98
|
-
ast = path_ast path
|
99
|
-
path_params = path_params ast
|
149
|
+
split_options = constraints(options, path_params)
|
100
150
|
|
101
|
-
|
151
|
+
constraints = scope_params[:constraints].merge Hash[split_options[:constraints] || []]
|
102
152
|
|
153
|
+
if options_constraints.is_a?(Hash)
|
154
|
+
@defaults = Hash[options_constraints.find_all { |key, default|
|
155
|
+
URL_OPTIONS.include?(key) && (String === default || Integer === default)
|
156
|
+
}].merge @defaults
|
157
|
+
@blocks = scope_params[:blocks]
|
158
|
+
constraints.merge! options_constraints
|
159
|
+
else
|
160
|
+
@blocks = blocks(options_constraints)
|
161
|
+
end
|
103
162
|
|
104
|
-
split_constraints
|
105
|
-
|
163
|
+
requirements, conditions = split_constraints path_params, constraints
|
164
|
+
verify_regexp_requirements requirements.map(&:last).grep(Regexp)
|
106
165
|
|
107
|
-
|
166
|
+
formats = normalize_format(formatted)
|
108
167
|
|
109
|
-
@
|
168
|
+
@requirements = formats[:requirements].merge Hash[requirements]
|
169
|
+
@conditions = Hash[conditions]
|
170
|
+
@defaults = formats[:defaults].merge(@defaults).merge(normalize_defaults(options))
|
110
171
|
|
111
|
-
if
|
112
|
-
|
113
|
-
options_constraints.each do |key, default|
|
114
|
-
if URL_OPTIONS.include?(key) && (String === default || Integer === default)
|
115
|
-
@defaults[key] ||= default
|
116
|
-
end
|
117
|
-
end
|
172
|
+
if path_params.include?(:action) && !@requirements.key?(:action)
|
173
|
+
@defaults[:action] ||= "index"
|
118
174
|
end
|
119
175
|
|
120
|
-
|
176
|
+
@required_defaults = (split_options[:required_defaults] || []).map(&:first)
|
177
|
+
end
|
121
178
|
|
122
|
-
|
123
|
-
|
179
|
+
def make_route(name, precedence)
|
180
|
+
Journey::Route.new(name: name, app: application, path: path, constraints: conditions,
|
181
|
+
required_defaults: required_defaults, defaults: defaults,
|
182
|
+
request_method_match: request_method, precedence: precedence,
|
183
|
+
scope_options: scope_options, internal: @internal)
|
184
|
+
end
|
124
185
|
|
125
|
-
|
126
|
-
|
186
|
+
def application
|
187
|
+
app(@blocks)
|
127
188
|
end
|
128
189
|
|
129
|
-
|
130
|
-
|
190
|
+
JOINED_SEPARATORS = SEPARATORS.join # :nodoc:
|
191
|
+
|
192
|
+
def path
|
193
|
+
Journey::Path::Pattern.new(@ast, requirements, JOINED_SEPARATORS, @anchor)
|
131
194
|
end
|
132
195
|
|
133
|
-
|
196
|
+
def conditions
|
197
|
+
build_conditions @conditions, @set.request_class
|
198
|
+
end
|
134
199
|
|
135
|
-
|
136
|
-
|
200
|
+
def build_conditions(current_conditions, request_class)
|
201
|
+
conditions = current_conditions.dup
|
137
202
|
|
138
|
-
|
139
|
-
|
140
|
-
elsif optional_format?(path, format)
|
141
|
-
"#{path}(.:format)"
|
142
|
-
else
|
143
|
-
path
|
144
|
-
end
|
203
|
+
conditions.keep_if do |k, _|
|
204
|
+
request_class.public_method_defined?(k)
|
145
205
|
end
|
206
|
+
end
|
207
|
+
private :build_conditions
|
146
208
|
|
147
|
-
|
148
|
-
|
149
|
-
|
209
|
+
def request_method
|
210
|
+
@via.map { |x| Journey::Route.verb_matcher(x) }
|
211
|
+
end
|
212
|
+
private :request_method
|
150
213
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
214
|
+
private
|
215
|
+
# Find all the symbol nodes that are adjacent to literal nodes and alter
|
216
|
+
# the regexp so that Journey will partition them into custom routes.
|
217
|
+
def alter_regex_for_custom_routes(node)
|
218
|
+
if node.left.literal? && node.right.symbol?
|
219
|
+
symbol = node.right
|
220
|
+
elsif node.left.literal? && node.right.cat? && node.right.left.symbol?
|
221
|
+
symbol = node.right.left
|
222
|
+
elsif node.left.symbol? && node.right.literal?
|
223
|
+
symbol = node.left
|
224
|
+
elsif node.left.symbol? && node.right.cat? && node.right.left.literal?
|
225
|
+
symbol = node.left
|
158
226
|
end
|
159
227
|
|
228
|
+
if symbol
|
229
|
+
symbol.regexp = /(?:#{Regexp.union(symbol.regexp, '-')})+/
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def intern(object)
|
234
|
+
object.is_a?(String) ? -object : object
|
235
|
+
end
|
236
|
+
|
237
|
+
def normalize_options!(options, path_params, modyoule)
|
160
238
|
if path_params.include?(:controller)
|
161
239
|
raise ArgumentError, ":controller segment is not allowed within a namespace block" if modyoule
|
162
240
|
|
@@ -167,7 +245,7 @@ module ActionDispatch
|
|
167
245
|
options[:controller] ||= /.+?/
|
168
246
|
end
|
169
247
|
|
170
|
-
if to.respond_to? :call
|
248
|
+
if to.respond_to?(:action) || to.respond_to?(:call)
|
171
249
|
options
|
172
250
|
else
|
173
251
|
to_endpoint = split_to to
|
@@ -181,82 +259,60 @@ module ActionDispatch
|
|
181
259
|
end
|
182
260
|
|
183
261
|
def split_constraints(path_params, constraints)
|
184
|
-
constraints.
|
185
|
-
|
186
|
-
verify_regexp_requirement(requirement) if requirement.is_a?(Regexp)
|
187
|
-
@requirements[key] = requirement
|
188
|
-
else
|
189
|
-
@conditions[key] = requirement
|
190
|
-
end
|
262
|
+
constraints.partition do |key, requirement|
|
263
|
+
path_params.include?(key) || key == :controller
|
191
264
|
end
|
192
265
|
end
|
193
266
|
|
194
|
-
def normalize_format
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
if requirement.source =~ ANCHOR_CHARACTERS_REGEX
|
208
|
-
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
|
209
|
-
end
|
210
|
-
|
211
|
-
if requirement.multiline?
|
212
|
-
raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
|
267
|
+
def normalize_format(formatted)
|
268
|
+
case formatted
|
269
|
+
when true
|
270
|
+
{ requirements: { format: /.+/ },
|
271
|
+
defaults: {} }
|
272
|
+
when Regexp
|
273
|
+
{ requirements: { format: formatted },
|
274
|
+
defaults: { format: nil } }
|
275
|
+
when String
|
276
|
+
{ requirements: { format: Regexp.compile(formatted) },
|
277
|
+
defaults: { format: formatted } }
|
278
|
+
else
|
279
|
+
{ requirements: {}, defaults: {} }
|
213
280
|
end
|
214
281
|
end
|
215
282
|
|
216
|
-
def
|
217
|
-
|
218
|
-
|
219
|
-
|
283
|
+
def verify_regexp_requirements(requirements)
|
284
|
+
requirements.each do |requirement|
|
285
|
+
if ANCHOR_CHARACTERS_REGEX.match?(requirement.source)
|
286
|
+
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
|
220
287
|
end
|
221
|
-
end
|
222
|
-
end
|
223
288
|
|
224
|
-
|
225
|
-
|
226
|
-
|
289
|
+
if requirement.multiline?
|
290
|
+
raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
|
291
|
+
end
|
227
292
|
end
|
228
293
|
end
|
229
294
|
|
230
|
-
def
|
231
|
-
|
232
|
-
|
233
|
-
if via.empty?
|
234
|
-
msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
|
235
|
-
"If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
|
236
|
-
"If you want to expose your action to GET, use `get` in the router:\n" \
|
237
|
-
" Instead of: match \"controller#action\"\n" \
|
238
|
-
" Do: get \"controller#action\""
|
239
|
-
raise ArgumentError, msg
|
240
|
-
end
|
241
|
-
|
242
|
-
conditions[:request_method] = via.map { |m| m.to_s.dasherize.upcase }
|
295
|
+
def normalize_defaults(options)
|
296
|
+
Hash[options.reject { |_, default| Regexp === default }]
|
243
297
|
end
|
244
298
|
|
245
299
|
def app(blocks)
|
246
|
-
if to.respond_to?(:
|
247
|
-
|
300
|
+
if to.respond_to?(:action)
|
301
|
+
Routing::RouteSet::StaticDispatcher.new to
|
302
|
+
elsif to.respond_to?(:call)
|
303
|
+
Constraints.new(to, blocks, Constraints::CALL)
|
248
304
|
elsif blocks.any?
|
249
|
-
Constraints.new(dispatcher(defaults), blocks,
|
305
|
+
Constraints.new(dispatcher(defaults.key?(:controller)), blocks, Constraints::SERVE)
|
250
306
|
else
|
251
|
-
dispatcher(defaults)
|
307
|
+
dispatcher(defaults.key?(:controller))
|
252
308
|
end
|
253
309
|
end
|
254
310
|
|
255
311
|
def check_controller_and_action(path_params, controller, action)
|
256
312
|
hash = check_part(:controller, controller, path_params, {}) do |part|
|
257
313
|
translate_controller(part) {
|
258
|
-
message = "'#{part}' is not a supported controller name. This can lead to potential routing problems."
|
259
|
-
message << " See
|
314
|
+
message = +"'#{part}' is not a supported controller name. This can lead to potential routing problems."
|
315
|
+
message << " See https://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
|
260
316
|
|
261
317
|
raise ArgumentError, message
|
262
318
|
}
|
@@ -280,22 +336,8 @@ module ActionDispatch
|
|
280
336
|
end
|
281
337
|
|
282
338
|
def split_to(to)
|
283
|
-
|
284
|
-
|
285
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
286
|
-
Defining a route where `to` is a symbol is deprecated.
|
287
|
-
Please change `to: :#{to}` to `action: :#{to}`.
|
288
|
-
MSG
|
289
|
-
|
290
|
-
[nil, to.to_s]
|
291
|
-
when /#/ then to.split('#')
|
292
|
-
when String
|
293
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
294
|
-
Defining a route where `to` is a controller without an action is deprecated.
|
295
|
-
Please change `to: '#{to}'` to `controller: '#{to}'`.
|
296
|
-
MSG
|
297
|
-
|
298
|
-
[to, nil]
|
339
|
+
if /#/.match?(to)
|
340
|
+
to.split("#").map!(&:-@)
|
299
341
|
else
|
300
342
|
[]
|
301
343
|
end
|
@@ -303,10 +345,10 @@ module ActionDispatch
|
|
303
345
|
|
304
346
|
def add_controller_module(controller, modyoule)
|
305
347
|
if modyoule && !controller.is_a?(Regexp)
|
306
|
-
if controller
|
307
|
-
controller[1..-1]
|
348
|
+
if controller&.start_with?("/")
|
349
|
+
-controller[1..-1]
|
308
350
|
else
|
309
|
-
[modyoule, controller].compact.join("/")
|
351
|
+
-[modyoule, controller].compact.join("/")
|
310
352
|
end
|
311
353
|
else
|
312
354
|
controller
|
@@ -315,54 +357,54 @@ module ActionDispatch
|
|
315
357
|
|
316
358
|
def translate_controller(controller)
|
317
359
|
return controller if Regexp === controller
|
318
|
-
return controller.to_s if
|
360
|
+
return controller.to_s if /\A[a-z_0-9][a-z_0-9\/]*\z/.match?(controller)
|
319
361
|
|
320
362
|
yield
|
321
363
|
end
|
322
364
|
|
323
|
-
def blocks(
|
324
|
-
|
325
|
-
|
326
|
-
[options_constraints]
|
327
|
-
else
|
328
|
-
scope_blocks || []
|
365
|
+
def blocks(callable_constraint)
|
366
|
+
unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?)
|
367
|
+
raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?"
|
329
368
|
end
|
369
|
+
[callable_constraint]
|
330
370
|
end
|
331
371
|
|
332
372
|
def constraints(options, path_params)
|
333
|
-
|
334
|
-
required_defaults = []
|
335
|
-
options.each_pair do |key, option|
|
373
|
+
options.group_by do |key, option|
|
336
374
|
if Regexp === option
|
337
|
-
constraints
|
375
|
+
:constraints
|
338
376
|
else
|
339
|
-
|
377
|
+
if path_params.include?(key)
|
378
|
+
:path_params
|
379
|
+
else
|
380
|
+
:required_defaults
|
381
|
+
end
|
340
382
|
end
|
341
383
|
end
|
342
|
-
@conditions[:required_defaults] = required_defaults
|
343
|
-
constraints
|
344
|
-
end
|
345
|
-
|
346
|
-
def path_params(ast)
|
347
|
-
ast.grep(Journey::Nodes::Symbol).map { |n| n.name.to_sym }
|
348
384
|
end
|
349
385
|
|
350
|
-
def
|
351
|
-
|
352
|
-
parser.parse path
|
353
|
-
end
|
354
|
-
|
355
|
-
def dispatcher(defaults)
|
356
|
-
@set.dispatcher defaults
|
386
|
+
def dispatcher(raise_on_name_error)
|
387
|
+
Routing::RouteSet::Dispatcher.new raise_on_name_error
|
357
388
|
end
|
358
389
|
end
|
359
390
|
|
360
|
-
# Invokes Journey::Router::Utils.normalize_path
|
361
|
-
# (:locale) becomes (/:locale)
|
362
|
-
#
|
391
|
+
# Invokes Journey::Router::Utils.normalize_path, then ensures that
|
392
|
+
# /(:locale) becomes (/:locale). Except for root cases, where the
|
393
|
+
# former is the correct one.
|
363
394
|
def self.normalize_path(path)
|
364
395
|
path = Journey::Router::Utils.normalize_path(path)
|
365
|
-
|
396
|
+
|
397
|
+
# the path for a root URL at this point can be something like
|
398
|
+
# "/(/:locale)(/:platform)/(:browser)", and we would want
|
399
|
+
# "/(:locale)(/:platform)(/:browser)"
|
400
|
+
|
401
|
+
# reverse "/(", "/((" etc to "(/", "((/" etc
|
402
|
+
path.gsub!(%r{/(\(+)/?}, '\1/')
|
403
|
+
# if a path is all optional segments, change the leading "(/" back to
|
404
|
+
# "/(" so it evaluates to "/" when interpreted with no options.
|
405
|
+
# Unless, however, at least one secondary segment consists of a static
|
406
|
+
# part, ex. "(/:locale)(/pages/:page)"
|
407
|
+
path.sub!(%r{^(\(+)/}, '/\1') if %r{^(\(+[^)]+\))(\(+/:[^)]+\))*$}.match?(path)
|
366
408
|
path
|
367
409
|
end
|
368
410
|
|
@@ -371,24 +413,7 @@ module ActionDispatch
|
|
371
413
|
end
|
372
414
|
|
373
415
|
module Base
|
374
|
-
#
|
375
|
-
#
|
376
|
-
# root to: 'pages#main'
|
377
|
-
#
|
378
|
-
# For options, see +match+, as +root+ uses it internally.
|
379
|
-
#
|
380
|
-
# You can also pass a string which will expand
|
381
|
-
#
|
382
|
-
# root 'pages#main'
|
383
|
-
#
|
384
|
-
# You should put the root route at the top of <tt>config/routes.rb</tt>,
|
385
|
-
# because this means it will be matched first. As this is the most popular route
|
386
|
-
# of most Rails applications, this is beneficial.
|
387
|
-
def root(options = {})
|
388
|
-
match '/', { :as => :root, :via => :get }.merge!(options)
|
389
|
-
end
|
390
|
-
|
391
|
-
# Matches a url pattern to one or more routes.
|
416
|
+
# Matches a URL pattern to one or more routes.
|
392
417
|
#
|
393
418
|
# You should not use the +match+ method in your router
|
394
419
|
# without specifying an HTTP method.
|
@@ -398,7 +423,7 @@ module ActionDispatch
|
|
398
423
|
# # sets :controller, :action and :id in params
|
399
424
|
# match ':controller/:action/:id', via: [:get, :post]
|
400
425
|
#
|
401
|
-
# Note that +:controller+, +:action+ and +:id+ are interpreted as
|
426
|
+
# Note that +:controller+, +:action+ and +:id+ are interpreted as URL
|
402
427
|
# query parameters and thus available through +params+ in an action.
|
403
428
|
#
|
404
429
|
# If you want to expose your action to GET, use +get+ in the router:
|
@@ -435,7 +460,7 @@ module ActionDispatch
|
|
435
460
|
# A pattern can also point to a +Rack+ endpoint i.e. anything that
|
436
461
|
# responds to +call+:
|
437
462
|
#
|
438
|
-
# match 'photos/:id', to:
|
463
|
+
# match 'photos/:id', to: -> (hash) { [200, {}, ["Coming soon"]] }, via: :get
|
439
464
|
# match 'photos/:id', to: PhotoRackApp, via: :get
|
440
465
|
# # Yes, controller actions are just rack endpoints
|
441
466
|
# match 'photos/:id', to: PhotosController.action(:show), via: :get
|
@@ -447,7 +472,7 @@ module ActionDispatch
|
|
447
472
|
#
|
448
473
|
# === Options
|
449
474
|
#
|
450
|
-
# Any options not seen here are passed on as params with the
|
475
|
+
# Any options not seen here are passed on as params with the URL.
|
451
476
|
#
|
452
477
|
# [:controller]
|
453
478
|
# The route's controller.
|
@@ -460,6 +485,31 @@ module ActionDispatch
|
|
460
485
|
# dynamic segment used to generate the routes).
|
461
486
|
# You can access that segment from your controller using
|
462
487
|
# <tt>params[<:param>]</tt>.
|
488
|
+
# In your router:
|
489
|
+
#
|
490
|
+
# resources :users, param: :name
|
491
|
+
#
|
492
|
+
# The +users+ resource here will have the following routes generated for it:
|
493
|
+
#
|
494
|
+
# GET /users(.:format)
|
495
|
+
# POST /users(.:format)
|
496
|
+
# GET /users/new(.:format)
|
497
|
+
# GET /users/:name/edit(.:format)
|
498
|
+
# GET /users/:name(.:format)
|
499
|
+
# PATCH/PUT /users/:name(.:format)
|
500
|
+
# DELETE /users/:name(.:format)
|
501
|
+
#
|
502
|
+
# You can override <tt>ActiveRecord::Base#to_param</tt> of a related
|
503
|
+
# model to construct a URL:
|
504
|
+
#
|
505
|
+
# class User < ActiveRecord::Base
|
506
|
+
# def to_param
|
507
|
+
# name
|
508
|
+
# end
|
509
|
+
# end
|
510
|
+
#
|
511
|
+
# user = User.find_by(name: 'Phusion')
|
512
|
+
# user_path(user) # => "/users/Phusion"
|
463
513
|
#
|
464
514
|
# [:path]
|
465
515
|
# The path prefix for the routes.
|
@@ -487,7 +537,7 @@ module ActionDispatch
|
|
487
537
|
# +call+ or a string representing a controller's action.
|
488
538
|
#
|
489
539
|
# match 'path', to: 'controller#action', via: :get
|
490
|
-
# match 'path', to:
|
540
|
+
# match 'path', to: -> (env) { [200, {}, ["Success!"]] }, via: :get
|
491
541
|
# match 'path', to: RackApp, via: :get
|
492
542
|
#
|
493
543
|
# [:on]
|
@@ -511,16 +561,16 @@ module ActionDispatch
|
|
511
561
|
# Constrains parameters with a hash of regular expressions
|
512
562
|
# or an object that responds to <tt>matches?</tt>. In addition, constraints
|
513
563
|
# other than path can also be specified with any object
|
514
|
-
# that responds to <tt>===</tt> (
|
564
|
+
# that responds to <tt>===</tt> (e.g. String, Array, Range, etc.).
|
515
565
|
#
|
516
566
|
# match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via: :get
|
517
567
|
#
|
518
568
|
# match 'json_only', constraints: { format: 'json' }, via: :get
|
519
569
|
#
|
520
|
-
# class
|
570
|
+
# class PermitList
|
521
571
|
# def matches?(request) request.remote_ip == '1.2.3.4' end
|
522
572
|
# end
|
523
|
-
# match 'path', to: 'c#a', constraints:
|
573
|
+
# match 'path', to: 'c#a', constraints: PermitList.new, via: :get
|
524
574
|
#
|
525
575
|
# See <tt>Scoping#constraints</tt> for more examples with its scope
|
526
576
|
# equivalent.
|
@@ -543,7 +593,7 @@ module ActionDispatch
|
|
543
593
|
# [:format]
|
544
594
|
# Allows you to specify the default value for optional +format+
|
545
595
|
# segment or disable it by supplying +false+.
|
546
|
-
def match(path, options=nil)
|
596
|
+
def match(path, options = nil)
|
547
597
|
end
|
548
598
|
|
549
599
|
# Mount a Rack-based application to be used within the application.
|
@@ -568,17 +618,20 @@ module ActionDispatch
|
|
568
618
|
def mount(app, options = nil)
|
569
619
|
if options
|
570
620
|
path = options.delete(:at)
|
571
|
-
|
572
|
-
unless Hash === app
|
573
|
-
raise ArgumentError, "must be called with mount point"
|
574
|
-
end
|
575
|
-
|
621
|
+
elsif Hash === app
|
576
622
|
options = app
|
577
623
|
app, path = options.find { |k, _| k.respond_to?(:call) }
|
578
624
|
options.delete(app) if app
|
579
625
|
end
|
580
626
|
|
581
|
-
raise "A rack application must be specified" unless
|
627
|
+
raise ArgumentError, "A rack application must be specified" unless app.respond_to?(:call)
|
628
|
+
raise ArgumentError, <<~MSG unless path
|
629
|
+
Must be called with mount point
|
630
|
+
|
631
|
+
mount SomeRackApp, at: "some_route"
|
632
|
+
or
|
633
|
+
mount(SomeRackApp => "some_route")
|
634
|
+
MSG
|
582
635
|
|
583
636
|
rails_app = rails_app? app
|
584
637
|
options[:as] ||= app_name(app, rails_app)
|
@@ -586,7 +639,7 @@ module ActionDispatch
|
|
586
639
|
target_as = name_for_action(options[:as], path)
|
587
640
|
options[:via] ||= :all
|
588
641
|
|
589
|
-
match(path, options.merge(:
|
642
|
+
match(path, options.merge(to: app, anchor: false, format: false))
|
590
643
|
|
591
644
|
define_generate_prefix(app, target_as) if rails_app
|
592
645
|
self
|
@@ -605,7 +658,7 @@ module ActionDispatch
|
|
605
658
|
|
606
659
|
# Query if the following named route was already defined.
|
607
660
|
def has_named_route?(name)
|
608
|
-
@set.named_routes.
|
661
|
+
@set.named_routes.key?(name)
|
609
662
|
end
|
610
663
|
|
611
664
|
private
|
@@ -625,18 +678,31 @@ module ActionDispatch
|
|
625
678
|
def define_generate_prefix(app, name)
|
626
679
|
_route = @set.named_routes.get name
|
627
680
|
_routes = @set
|
628
|
-
|
681
|
+
_url_helpers = @set.url_helpers
|
682
|
+
|
683
|
+
script_namer = ->(options) do
|
684
|
+
prefix_options = options.slice(*_route.segment_keys)
|
685
|
+
prefix_options[:relative_url_root] = ""
|
686
|
+
|
687
|
+
if options[:_recall]
|
688
|
+
prefix_options.reverse_merge!(options[:_recall].slice(*_route.segment_keys))
|
689
|
+
end
|
690
|
+
|
691
|
+
# We must actually delete prefix segment keys to avoid passing them to next url_for.
|
692
|
+
_route.segment_keys.each { |k| options.delete(k) }
|
693
|
+
_url_helpers.public_send("#{name}_path", prefix_options)
|
694
|
+
end
|
695
|
+
|
696
|
+
app.routes.define_mounted_helper(name, script_namer)
|
697
|
+
|
629
698
|
app.routes.extend Module.new {
|
630
699
|
def optimize_routes_generation?; false; end
|
700
|
+
|
631
701
|
define_method :find_script_name do |options|
|
632
702
|
if options.key? :script_name
|
633
703
|
super(options)
|
634
704
|
else
|
635
|
-
|
636
|
-
prefix_options[:relative_url_root] = ''.freeze
|
637
|
-
# we must actually delete prefix segment keys to avoid passing them to next url_for
|
638
|
-
_route.segment_keys.each { |k| options.delete(k) }
|
639
|
-
_routes.url_helpers.send("#{name}_path", prefix_options)
|
705
|
+
script_namer.call(options)
|
640
706
|
end
|
641
707
|
end
|
642
708
|
}
|
@@ -684,6 +750,14 @@ module ActionDispatch
|
|
684
750
|
map_method(:delete, args, &block)
|
685
751
|
end
|
686
752
|
|
753
|
+
# Define a route that only recognizes HTTP OPTIONS.
|
754
|
+
# For supported arguments, see match[rdoc-ref:Base#match]
|
755
|
+
#
|
756
|
+
# options 'carrots', to: 'food#carrots'
|
757
|
+
def options(*args, &block)
|
758
|
+
map_method(:options, args, &block)
|
759
|
+
end
|
760
|
+
|
687
761
|
private
|
688
762
|
def map_method(method, args, &block)
|
689
763
|
options = args.extract_options!
|
@@ -782,7 +856,7 @@ module ActionDispatch
|
|
782
856
|
options = args.extract_options!.dup
|
783
857
|
scope = {}
|
784
858
|
|
785
|
-
options[:path] = args.flatten.join(
|
859
|
+
options[:path] = args.flatten.join("/") if args.any?
|
786
860
|
options[:constraints] ||= {}
|
787
861
|
|
788
862
|
unless nested_scope?
|
@@ -795,21 +869,30 @@ module ActionDispatch
|
|
795
869
|
URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Integer))
|
796
870
|
end
|
797
871
|
|
798
|
-
(options[:defaults]
|
872
|
+
options[:defaults] = defaults.merge(options[:defaults] || {})
|
799
873
|
else
|
800
874
|
block, options[:constraints] = options[:constraints], {}
|
801
875
|
end
|
802
876
|
|
877
|
+
if options.key?(:only) || options.key?(:except)
|
878
|
+
scope[:action_options] = { only: options.delete(:only),
|
879
|
+
except: options.delete(:except) }
|
880
|
+
end
|
881
|
+
|
882
|
+
if options.key? :anchor
|
883
|
+
raise ArgumentError, "anchor is ignored unless passed to `match`"
|
884
|
+
end
|
885
|
+
|
803
886
|
@scope.options.each do |option|
|
804
887
|
if option == :blocks
|
805
888
|
value = block
|
806
889
|
elsif option == :options
|
807
890
|
value = options
|
808
891
|
else
|
809
|
-
value = options.delete(option)
|
892
|
+
value = options.delete(option) { POISON }
|
810
893
|
end
|
811
894
|
|
812
|
-
|
895
|
+
unless POISON == value
|
813
896
|
scope[option] = send("merge_#{option}_scope", @scope[option], value)
|
814
897
|
end
|
815
898
|
end
|
@@ -821,14 +904,18 @@ module ActionDispatch
|
|
821
904
|
@scope = @scope.parent
|
822
905
|
end
|
823
906
|
|
907
|
+
POISON = Object.new # :nodoc:
|
908
|
+
|
824
909
|
# Scopes routes to a specific controller
|
825
910
|
#
|
826
911
|
# controller "food" do
|
827
|
-
# match "bacon", action:
|
912
|
+
# match "bacon", action: :bacon, via: :get
|
828
913
|
# end
|
829
|
-
def controller(controller
|
830
|
-
|
831
|
-
|
914
|
+
def controller(controller)
|
915
|
+
@scope = @scope.new(controller: controller)
|
916
|
+
yield
|
917
|
+
ensure
|
918
|
+
@scope = @scope.parent
|
832
919
|
end
|
833
920
|
|
834
921
|
# Scopes routes to a specific namespace. For example:
|
@@ -874,13 +961,14 @@ module ActionDispatch
|
|
874
961
|
|
875
962
|
defaults = {
|
876
963
|
module: path,
|
877
|
-
path: options.fetch(:path, path),
|
878
964
|
as: options.fetch(:as, path),
|
879
965
|
shallow_path: options.fetch(:path, path),
|
880
966
|
shallow_prefix: options.fetch(:as, path)
|
881
967
|
}
|
882
968
|
|
883
|
-
|
969
|
+
path_scope(options.delete(:path) { path }) do
|
970
|
+
scope(defaults.merge!(options)) { yield }
|
971
|
+
end
|
884
972
|
end
|
885
973
|
|
886
974
|
# === Parameter Restriction
|
@@ -917,7 +1005,7 @@ module ActionDispatch
|
|
917
1005
|
#
|
918
1006
|
# Requests to routes can be constrained based on specific criteria:
|
919
1007
|
#
|
920
|
-
# constraints(
|
1008
|
+
# constraints(-> (req) { /iPhone/.match?(req.env["HTTP_USER_AGENT"]) }) do
|
921
1009
|
# resources :iphones
|
922
1010
|
# end
|
923
1011
|
#
|
@@ -927,7 +1015,7 @@ module ActionDispatch
|
|
927
1015
|
#
|
928
1016
|
# class Iphone
|
929
1017
|
# def self.matches?(request)
|
930
|
-
# request.env["HTTP_USER_AGENT"]
|
1018
|
+
# /iPhone/.match?(request.env["HTTP_USER_AGENT"])
|
931
1019
|
# end
|
932
1020
|
# end
|
933
1021
|
#
|
@@ -939,7 +1027,7 @@ module ActionDispatch
|
|
939
1027
|
# resources :iphones
|
940
1028
|
# end
|
941
1029
|
def constraints(constraints = {})
|
942
|
-
scope(:
|
1030
|
+
scope(constraints: constraints) { yield }
|
943
1031
|
end
|
944
1032
|
|
945
1033
|
# Allows you to set default parameters for a route, such as this:
|
@@ -948,66 +1036,77 @@ module ActionDispatch
|
|
948
1036
|
# end
|
949
1037
|
# Using this, the +:id+ parameter here will default to 'home'.
|
950
1038
|
def defaults(defaults = {})
|
951
|
-
scope(:defaults
|
1039
|
+
@scope = @scope.new(defaults: merge_defaults_scope(@scope[:defaults], defaults))
|
1040
|
+
yield
|
1041
|
+
ensure
|
1042
|
+
@scope = @scope.parent
|
952
1043
|
end
|
953
1044
|
|
954
1045
|
private
|
955
|
-
def merge_path_scope(parent, child)
|
1046
|
+
def merge_path_scope(parent, child)
|
956
1047
|
Mapper.normalize_path("#{parent}/#{child}")
|
957
1048
|
end
|
958
1049
|
|
959
|
-
def merge_shallow_path_scope(parent, child)
|
1050
|
+
def merge_shallow_path_scope(parent, child)
|
960
1051
|
Mapper.normalize_path("#{parent}/#{child}")
|
961
1052
|
end
|
962
1053
|
|
963
|
-
def merge_as_scope(parent, child)
|
1054
|
+
def merge_as_scope(parent, child)
|
964
1055
|
parent ? "#{parent}_#{child}" : child
|
965
1056
|
end
|
966
1057
|
|
967
|
-
def merge_shallow_prefix_scope(parent, child)
|
1058
|
+
def merge_shallow_prefix_scope(parent, child)
|
968
1059
|
parent ? "#{parent}_#{child}" : child
|
969
1060
|
end
|
970
1061
|
|
971
|
-
def merge_module_scope(parent, child)
|
1062
|
+
def merge_module_scope(parent, child)
|
972
1063
|
parent ? "#{parent}/#{child}" : child
|
973
1064
|
end
|
974
1065
|
|
975
|
-
def merge_controller_scope(parent, child)
|
1066
|
+
def merge_controller_scope(parent, child)
|
1067
|
+
child
|
1068
|
+
end
|
1069
|
+
|
1070
|
+
def merge_action_scope(parent, child)
|
976
1071
|
child
|
977
1072
|
end
|
978
1073
|
|
979
|
-
def
|
1074
|
+
def merge_via_scope(parent, child)
|
980
1075
|
child
|
981
1076
|
end
|
982
1077
|
|
983
|
-
def
|
1078
|
+
def merge_format_scope(parent, child)
|
1079
|
+
child
|
1080
|
+
end
|
1081
|
+
|
1082
|
+
def merge_path_names_scope(parent, child)
|
984
1083
|
merge_options_scope(parent, child)
|
985
1084
|
end
|
986
1085
|
|
987
|
-
def merge_constraints_scope(parent, child)
|
1086
|
+
def merge_constraints_scope(parent, child)
|
988
1087
|
merge_options_scope(parent, child)
|
989
1088
|
end
|
990
1089
|
|
991
|
-
def merge_defaults_scope(parent, child)
|
1090
|
+
def merge_defaults_scope(parent, child)
|
992
1091
|
merge_options_scope(parent, child)
|
993
1092
|
end
|
994
1093
|
|
995
|
-
def merge_blocks_scope(parent, child)
|
1094
|
+
def merge_blocks_scope(parent, child)
|
996
1095
|
merged = parent ? parent.dup : []
|
997
1096
|
merged << child if child
|
998
1097
|
merged
|
999
1098
|
end
|
1000
1099
|
|
1001
|
-
def merge_options_scope(parent, child)
|
1002
|
-
(parent || {}).
|
1100
|
+
def merge_options_scope(parent, child)
|
1101
|
+
(parent || {}).merge(child)
|
1003
1102
|
end
|
1004
1103
|
|
1005
|
-
def merge_shallow_scope(parent, child)
|
1104
|
+
def merge_shallow_scope(parent, child)
|
1006
1105
|
child ? true : false
|
1007
1106
|
end
|
1008
1107
|
|
1009
|
-
def
|
1010
|
-
child
|
1108
|
+
def merge_to_scope(parent, child)
|
1109
|
+
child
|
1011
1110
|
end
|
1012
1111
|
end
|
1013
1112
|
|
@@ -1058,27 +1157,44 @@ module ActionDispatch
|
|
1058
1157
|
CANONICAL_ACTIONS = %w(index create new show update destroy)
|
1059
1158
|
|
1060
1159
|
class Resource #:nodoc:
|
1061
|
-
attr_reader :controller, :path, :
|
1160
|
+
attr_reader :controller, :path, :param
|
1161
|
+
|
1162
|
+
def initialize(entities, api_only, shallow, options = {})
|
1163
|
+
if options[:param].to_s.include?(":")
|
1164
|
+
raise ArgumentError, ":param option can't contain colons"
|
1165
|
+
end
|
1062
1166
|
|
1063
|
-
def initialize(entities, options = {})
|
1064
1167
|
@name = entities.to_s
|
1065
1168
|
@path = (options[:path] || @name).to_s
|
1066
1169
|
@controller = (options[:controller] || @name).to_s
|
1067
1170
|
@as = options[:as]
|
1068
1171
|
@param = (options[:param] || :id).to_sym
|
1069
1172
|
@options = options
|
1070
|
-
@shallow =
|
1173
|
+
@shallow = shallow
|
1174
|
+
@api_only = api_only
|
1175
|
+
@only = options.delete :only
|
1176
|
+
@except = options.delete :except
|
1071
1177
|
end
|
1072
1178
|
|
1073
1179
|
def default_actions
|
1074
|
-
|
1180
|
+
if @api_only
|
1181
|
+
[:index, :create, :show, :update, :destroy]
|
1182
|
+
else
|
1183
|
+
[:index, :create, :new, :show, :update, :destroy, :edit]
|
1184
|
+
end
|
1075
1185
|
end
|
1076
1186
|
|
1077
1187
|
def actions
|
1078
|
-
if
|
1079
|
-
Array(
|
1080
|
-
|
1081
|
-
|
1188
|
+
if @except
|
1189
|
+
available_actions - Array(@except).map(&:to_sym)
|
1190
|
+
else
|
1191
|
+
available_actions
|
1192
|
+
end
|
1193
|
+
end
|
1194
|
+
|
1195
|
+
def available_actions
|
1196
|
+
if @only
|
1197
|
+
Array(@only).map(&:to_sym)
|
1082
1198
|
else
|
1083
1199
|
default_actions
|
1084
1200
|
end
|
@@ -1105,7 +1221,7 @@ module ActionDispatch
|
|
1105
1221
|
end
|
1106
1222
|
|
1107
1223
|
def resource_scope
|
1108
|
-
|
1224
|
+
controller
|
1109
1225
|
end
|
1110
1226
|
|
1111
1227
|
alias :collection_scope :path
|
@@ -1128,17 +1244,15 @@ module ActionDispatch
|
|
1128
1244
|
"#{path}/:#{nested_param}"
|
1129
1245
|
end
|
1130
1246
|
|
1131
|
-
def shallow=(value)
|
1132
|
-
@shallow = value
|
1133
|
-
end
|
1134
|
-
|
1135
1247
|
def shallow?
|
1136
1248
|
@shallow
|
1137
1249
|
end
|
1250
|
+
|
1251
|
+
def singleton?; false; end
|
1138
1252
|
end
|
1139
1253
|
|
1140
1254
|
class SingletonResource < Resource #:nodoc:
|
1141
|
-
def initialize(entities, options)
|
1255
|
+
def initialize(entities, api_only, shallow, options)
|
1142
1256
|
super
|
1143
1257
|
@as = nil
|
1144
1258
|
@controller = (options[:controller] || plural).to_s
|
@@ -1146,7 +1260,11 @@ module ActionDispatch
|
|
1146
1260
|
end
|
1147
1261
|
|
1148
1262
|
def default_actions
|
1149
|
-
|
1263
|
+
if @api_only
|
1264
|
+
[:show, :create, :update, :destroy]
|
1265
|
+
else
|
1266
|
+
[:show, :create, :update, :destroy, :new, :edit]
|
1267
|
+
end
|
1150
1268
|
end
|
1151
1269
|
|
1152
1270
|
def plural
|
@@ -1162,6 +1280,8 @@ module ActionDispatch
|
|
1162
1280
|
|
1163
1281
|
alias :member_scope :path
|
1164
1282
|
alias :nested_scope :path
|
1283
|
+
|
1284
|
+
def singleton?; true; end
|
1165
1285
|
end
|
1166
1286
|
|
1167
1287
|
def resources_path_names(options)
|
@@ -1176,19 +1296,19 @@ module ActionDispatch
|
|
1176
1296
|
#
|
1177
1297
|
# resource :profile
|
1178
1298
|
#
|
1179
|
-
# creates six different routes in your application, all mapping to
|
1299
|
+
# This creates six different routes in your application, all mapping to
|
1180
1300
|
# the +Profiles+ controller (note that the controller is named after
|
1181
1301
|
# the plural):
|
1182
1302
|
#
|
1183
1303
|
# GET /profile/new
|
1184
|
-
# POST /profile
|
1185
1304
|
# GET /profile
|
1186
1305
|
# GET /profile/edit
|
1187
1306
|
# PATCH/PUT /profile
|
1188
1307
|
# DELETE /profile
|
1308
|
+
# POST /profile
|
1189
1309
|
#
|
1190
1310
|
# === Options
|
1191
|
-
# Takes same options as
|
1311
|
+
# Takes same options as resources[rdoc-ref:#resources]
|
1192
1312
|
def resource(*resources, &block)
|
1193
1313
|
options = resources.extract_options!.dup
|
1194
1314
|
|
@@ -1196,20 +1316,23 @@ module ActionDispatch
|
|
1196
1316
|
return self
|
1197
1317
|
end
|
1198
1318
|
|
1199
|
-
|
1200
|
-
|
1319
|
+
with_scope_level(:resource) do
|
1320
|
+
options = apply_action_options options
|
1321
|
+
resource_scope(SingletonResource.new(resources.pop, api_only?, @scope[:shallow], options)) do
|
1322
|
+
yield if block_given?
|
1201
1323
|
|
1202
|
-
|
1324
|
+
concerns(options[:concerns]) if options[:concerns]
|
1203
1325
|
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1326
|
+
new do
|
1327
|
+
get :new
|
1328
|
+
end if parent_resource.actions.include?(:new)
|
1207
1329
|
|
1208
|
-
|
1209
|
-
get :new
|
1210
|
-
end if parent_resource.actions.include?(:new)
|
1330
|
+
set_member_mappings_for_resource
|
1211
1331
|
|
1212
|
-
|
1332
|
+
collection do
|
1333
|
+
post :create
|
1334
|
+
end if parent_resource.actions.include?(:create)
|
1335
|
+
end
|
1213
1336
|
end
|
1214
1337
|
|
1215
1338
|
self
|
@@ -1250,7 +1373,7 @@ module ActionDispatch
|
|
1250
1373
|
# DELETE /photos/:photo_id/comments/:id
|
1251
1374
|
#
|
1252
1375
|
# === Options
|
1253
|
-
# Takes same options as
|
1376
|
+
# Takes same options as match[rdoc-ref:Base#match] as well as:
|
1254
1377
|
#
|
1255
1378
|
# [:path_names]
|
1256
1379
|
# Allows you to change the segment component of the +edit+ and +new+ actions.
|
@@ -1258,14 +1381,14 @@ module ActionDispatch
|
|
1258
1381
|
#
|
1259
1382
|
# resources :posts, path_names: { new: "brand_new" }
|
1260
1383
|
#
|
1261
|
-
# The above example will now change /posts/new to /posts/brand_new
|
1384
|
+
# The above example will now change /posts/new to /posts/brand_new.
|
1262
1385
|
#
|
1263
1386
|
# [:path]
|
1264
1387
|
# Allows you to change the path prefix for the resource.
|
1265
1388
|
#
|
1266
1389
|
# resources :posts, path: 'postings'
|
1267
1390
|
#
|
1268
|
-
# The resource and all segments will now route to /postings instead of /posts
|
1391
|
+
# The resource and all segments will now route to /postings instead of /posts.
|
1269
1392
|
#
|
1270
1393
|
# [:only]
|
1271
1394
|
# Only generate routes for the given actions.
|
@@ -1298,6 +1421,8 @@ module ActionDispatch
|
|
1298
1421
|
# as a comment on a blog post like <tt>/posts/a-long-permalink/comments/1234</tt>
|
1299
1422
|
# to be shortened to just <tt>/comments/1234</tt>.
|
1300
1423
|
#
|
1424
|
+
# Set <tt>shallow: false</tt> on a child resource to ignore a parent's shallow parameter.
|
1425
|
+
#
|
1301
1426
|
# [:shallow_path]
|
1302
1427
|
# Prefixes nested shallow routes with the specified path.
|
1303
1428
|
#
|
@@ -1340,6 +1465,9 @@ module ActionDispatch
|
|
1340
1465
|
# Allows you to specify the default value for optional +format+
|
1341
1466
|
# segment or disable it by supplying +false+.
|
1342
1467
|
#
|
1468
|
+
# [:param]
|
1469
|
+
# Allows you to override the default param name of +:id+ in the URL.
|
1470
|
+
#
|
1343
1471
|
# === Examples
|
1344
1472
|
#
|
1345
1473
|
# # routes call <tt>Admin::PostsController</tt>
|
@@ -1354,21 +1482,24 @@ module ActionDispatch
|
|
1354
1482
|
return self
|
1355
1483
|
end
|
1356
1484
|
|
1357
|
-
|
1358
|
-
|
1485
|
+
with_scope_level(:resources) do
|
1486
|
+
options = apply_action_options options
|
1487
|
+
resource_scope(Resource.new(resources.pop, api_only?, @scope[:shallow], options)) do
|
1488
|
+
yield if block_given?
|
1359
1489
|
|
1360
|
-
|
1490
|
+
concerns(options[:concerns]) if options[:concerns]
|
1361
1491
|
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1365
|
-
|
1492
|
+
collection do
|
1493
|
+
get :index if parent_resource.actions.include?(:index)
|
1494
|
+
post :create if parent_resource.actions.include?(:create)
|
1495
|
+
end
|
1366
1496
|
|
1367
|
-
|
1368
|
-
|
1369
|
-
|
1497
|
+
new do
|
1498
|
+
get :new
|
1499
|
+
end if parent_resource.actions.include?(:new)
|
1370
1500
|
|
1371
|
-
|
1501
|
+
set_member_mappings_for_resource
|
1502
|
+
end
|
1372
1503
|
end
|
1373
1504
|
|
1374
1505
|
self
|
@@ -1392,7 +1523,7 @@ module ActionDispatch
|
|
1392
1523
|
end
|
1393
1524
|
|
1394
1525
|
with_scope_level(:collection) do
|
1395
|
-
|
1526
|
+
path_scope(parent_resource.collection_scope) do
|
1396
1527
|
yield
|
1397
1528
|
end
|
1398
1529
|
end
|
@@ -1416,9 +1547,11 @@ module ActionDispatch
|
|
1416
1547
|
|
1417
1548
|
with_scope_level(:member) do
|
1418
1549
|
if shallow?
|
1419
|
-
shallow_scope
|
1550
|
+
shallow_scope {
|
1551
|
+
path_scope(parent_resource.member_scope) { yield }
|
1552
|
+
}
|
1420
1553
|
else
|
1421
|
-
|
1554
|
+
path_scope(parent_resource.member_scope) { yield }
|
1422
1555
|
end
|
1423
1556
|
end
|
1424
1557
|
end
|
@@ -1429,7 +1562,7 @@ module ActionDispatch
|
|
1429
1562
|
end
|
1430
1563
|
|
1431
1564
|
with_scope_level(:new) do
|
1432
|
-
|
1565
|
+
path_scope(parent_resource.new_scope(action_path(:new))) do
|
1433
1566
|
yield
|
1434
1567
|
end
|
1435
1568
|
end
|
@@ -1442,14 +1575,20 @@ module ActionDispatch
|
|
1442
1575
|
|
1443
1576
|
with_scope_level(:nested) do
|
1444
1577
|
if shallow? && shallow_nesting_depth >= 1
|
1445
|
-
shallow_scope
|
1578
|
+
shallow_scope do
|
1579
|
+
path_scope(parent_resource.nested_scope) do
|
1580
|
+
scope(nested_options) { yield }
|
1581
|
+
end
|
1582
|
+
end
|
1446
1583
|
else
|
1447
|
-
|
1584
|
+
path_scope(parent_resource.nested_scope) do
|
1585
|
+
scope(nested_options) { yield }
|
1586
|
+
end
|
1448
1587
|
end
|
1449
1588
|
end
|
1450
1589
|
end
|
1451
1590
|
|
1452
|
-
# See ActionDispatch::Routing::Mapper::Scoping#namespace
|
1591
|
+
# See ActionDispatch::Routing::Mapper::Scoping#namespace.
|
1453
1592
|
def namespace(path, options = {})
|
1454
1593
|
if resource_scope?
|
1455
1594
|
nested { super }
|
@@ -1459,28 +1598,50 @@ module ActionDispatch
|
|
1459
1598
|
end
|
1460
1599
|
|
1461
1600
|
def shallow
|
1462
|
-
scope(:
|
1463
|
-
|
1464
|
-
|
1601
|
+
@scope = @scope.new(shallow: true)
|
1602
|
+
yield
|
1603
|
+
ensure
|
1604
|
+
@scope = @scope.parent
|
1465
1605
|
end
|
1466
1606
|
|
1467
1607
|
def shallow?
|
1468
|
-
parent_resource.
|
1608
|
+
!parent_resource.singleton? && @scope[:shallow]
|
1469
1609
|
end
|
1470
1610
|
|
1471
|
-
|
1472
|
-
|
1473
|
-
|
1474
|
-
|
1611
|
+
def draw(name)
|
1612
|
+
path = @draw_paths.find do |_path|
|
1613
|
+
File.exist? "#{_path}/#{name}.rb"
|
1614
|
+
end
|
1615
|
+
|
1616
|
+
unless path
|
1617
|
+
msg = "Your router tried to #draw the external file #{name}.rb,\n" \
|
1618
|
+
"but the file was not found in:\n\n"
|
1619
|
+
msg += @draw_paths.map { |_path| " * #{_path}" }.join("\n")
|
1620
|
+
raise ArgumentError, msg
|
1621
|
+
end
|
1622
|
+
|
1623
|
+
route_path = "#{path}/#{name}.rb"
|
1624
|
+
instance_eval(File.read(route_path), route_path.to_s)
|
1625
|
+
end
|
1626
|
+
|
1627
|
+
# Matches a URL pattern to one or more routes.
|
1628
|
+
# For more information, see match[rdoc-ref:Base#match].
|
1629
|
+
#
|
1630
|
+
# match 'path' => 'controller#action', via: :patch
|
1631
|
+
# match 'path', to: 'controller#action', via: :post
|
1632
|
+
# match 'path', 'otherpath', on: :member, via: :get
|
1633
|
+
def match(path, *rest, &block)
|
1475
1634
|
if rest.empty? && Hash === path
|
1476
1635
|
options = path
|
1477
1636
|
path, to = options.find { |name, _value| name.is_a?(String) }
|
1478
1637
|
|
1638
|
+
raise ArgumentError, "Route path not specified" if path.nil?
|
1639
|
+
|
1479
1640
|
case to
|
1480
1641
|
when Symbol
|
1481
1642
|
options[:action] = to
|
1482
1643
|
when String
|
1483
|
-
if to
|
1644
|
+
if /#/.match?(to)
|
1484
1645
|
options[:to] = to
|
1485
1646
|
else
|
1486
1647
|
options[:controller] = to
|
@@ -1496,77 +1657,30 @@ module ActionDispatch
|
|
1496
1657
|
paths = [path] + rest
|
1497
1658
|
end
|
1498
1659
|
|
1499
|
-
|
1500
|
-
|
1501
|
-
if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
|
1502
|
-
raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
|
1503
|
-
end
|
1504
|
-
|
1505
|
-
if @scope[:controller] && @scope[:action]
|
1506
|
-
options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
|
1507
|
-
end
|
1508
|
-
|
1509
|
-
paths.each do |_path|
|
1510
|
-
route_options = options.dup
|
1511
|
-
route_options[:path] ||= _path if _path.is_a?(String)
|
1512
|
-
|
1513
|
-
path_without_format = _path.to_s.sub(/\(\.:format\)$/, '')
|
1514
|
-
if using_match_shorthand?(path_without_format, route_options)
|
1515
|
-
route_options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
|
1516
|
-
route_options[:to].tr!("-", "_")
|
1517
|
-
end
|
1518
|
-
|
1519
|
-
decomposed_match(_path, route_options)
|
1520
|
-
end
|
1521
|
-
self
|
1522
|
-
end
|
1523
|
-
|
1524
|
-
def using_match_shorthand?(path, options)
|
1525
|
-
path && (options[:to] || options[:action]).nil? && path =~ %r{^/?[-\w]+/[-\w/]+$}
|
1526
|
-
end
|
1527
|
-
|
1528
|
-
def decomposed_match(path, options) # :nodoc:
|
1529
|
-
if on = options.delete(:on)
|
1530
|
-
send(on) { decomposed_match(path, options) }
|
1531
|
-
else
|
1532
|
-
case @scope.scope_level
|
1533
|
-
when :resources
|
1534
|
-
nested { decomposed_match(path, options) }
|
1535
|
-
when :resource
|
1536
|
-
member { decomposed_match(path, options) }
|
1537
|
-
else
|
1538
|
-
add_route(path, options)
|
1539
|
-
end
|
1540
|
-
end
|
1541
|
-
end
|
1542
|
-
|
1543
|
-
def add_route(action, options) # :nodoc:
|
1544
|
-
path = path_for_action(action, options.delete(:path))
|
1545
|
-
raise ArgumentError, "path is required" if path.blank?
|
1546
|
-
|
1547
|
-
action = action.to_s.dup
|
1548
|
-
|
1549
|
-
if action =~ /^[\w\-\/]+$/
|
1550
|
-
options[:action] ||= action.tr('-', '_') unless action.include?("/")
|
1660
|
+
if options.key?(:defaults)
|
1661
|
+
defaults(options.delete(:defaults)) { map_match(paths, options, &block) }
|
1551
1662
|
else
|
1552
|
-
|
1663
|
+
map_match(paths, options, &block)
|
1553
1664
|
end
|
1554
|
-
|
1555
|
-
as = if !options.fetch(:as, true) # if it's set to nil or false
|
1556
|
-
options.delete(:as)
|
1557
|
-
else
|
1558
|
-
name_for_action(options.delete(:as), action)
|
1559
|
-
end
|
1560
|
-
|
1561
|
-
mapping = Mapping.build(@scope, @set, URI.parser.escape(path), as, options)
|
1562
|
-
app, conditions, requirements, defaults, as, anchor = mapping.to_route
|
1563
|
-
@set.add_route(app, conditions, requirements, defaults, as, anchor)
|
1564
1665
|
end
|
1565
1666
|
|
1566
|
-
|
1667
|
+
# You can specify what Rails should route "/" to with the root method:
|
1668
|
+
#
|
1669
|
+
# root to: 'pages#main'
|
1670
|
+
#
|
1671
|
+
# For options, see +match+, as +root+ uses it internally.
|
1672
|
+
#
|
1673
|
+
# You can also pass a string which will expand
|
1674
|
+
#
|
1675
|
+
# root 'pages#main'
|
1676
|
+
#
|
1677
|
+
# You should put the root route at the top of <tt>config/routes.rb</tt>,
|
1678
|
+
# because this means it will be matched first. As this is the most popular route
|
1679
|
+
# of most Rails applications, this is beneficial.
|
1680
|
+
def root(path, options = {})
|
1567
1681
|
if path.is_a?(String)
|
1568
1682
|
options[:to] = path
|
1569
|
-
elsif path.is_a?(Hash)
|
1683
|
+
elsif path.is_a?(Hash) && options.empty?
|
1570
1684
|
options = path
|
1571
1685
|
else
|
1572
1686
|
raise ArgumentError, "must be called with a path and/or options"
|
@@ -1574,36 +1688,36 @@ module ActionDispatch
|
|
1574
1688
|
|
1575
1689
|
if @scope.resources?
|
1576
1690
|
with_scope_level(:root) do
|
1577
|
-
|
1578
|
-
|
1691
|
+
path_scope(parent_resource.path) do
|
1692
|
+
match_root_route(options)
|
1579
1693
|
end
|
1580
1694
|
end
|
1581
1695
|
else
|
1582
|
-
|
1696
|
+
match_root_route(options)
|
1583
1697
|
end
|
1584
1698
|
end
|
1585
1699
|
|
1586
|
-
|
1587
|
-
|
1588
|
-
def parent_resource #:nodoc:
|
1700
|
+
private
|
1701
|
+
def parent_resource
|
1589
1702
|
@scope[:scope_level_resource]
|
1590
1703
|
end
|
1591
1704
|
|
1592
|
-
def apply_common_behavior_for(method, resources, options, &block)
|
1705
|
+
def apply_common_behavior_for(method, resources, options, &block)
|
1593
1706
|
if resources.length > 1
|
1594
|
-
resources.each { |r|
|
1707
|
+
resources.each { |r| public_send(method, r, options, &block) }
|
1595
1708
|
return true
|
1596
1709
|
end
|
1597
1710
|
|
1598
|
-
if options
|
1711
|
+
if options[:shallow]
|
1712
|
+
options.delete(:shallow)
|
1599
1713
|
shallow do
|
1600
|
-
|
1714
|
+
public_send(method, resources.pop, options, &block)
|
1601
1715
|
end
|
1602
1716
|
return true
|
1603
1717
|
end
|
1604
1718
|
|
1605
1719
|
if resource_scope?
|
1606
|
-
nested {
|
1720
|
+
nested { public_send(method, resources.pop, options, &block) }
|
1607
1721
|
return true
|
1608
1722
|
end
|
1609
1723
|
|
@@ -1614,76 +1728,56 @@ module ActionDispatch
|
|
1614
1728
|
scope_options = options.slice!(*RESOURCE_OPTIONS)
|
1615
1729
|
unless scope_options.empty?
|
1616
1730
|
scope(scope_options) do
|
1617
|
-
|
1731
|
+
public_send(method, resources.pop, options, &block)
|
1618
1732
|
end
|
1619
1733
|
return true
|
1620
1734
|
end
|
1621
1735
|
|
1622
|
-
unless action_options?(options)
|
1623
|
-
options.merge!(scope_action_options) if scope_action_options?
|
1624
|
-
end
|
1625
|
-
|
1626
1736
|
false
|
1627
1737
|
end
|
1628
1738
|
|
1629
|
-
def
|
1630
|
-
options
|
1739
|
+
def apply_action_options(options)
|
1740
|
+
return options if action_options? options
|
1741
|
+
options.merge scope_action_options
|
1631
1742
|
end
|
1632
1743
|
|
1633
|
-
def
|
1634
|
-
|
1744
|
+
def action_options?(options)
|
1745
|
+
options[:only] || options[:except]
|
1635
1746
|
end
|
1636
1747
|
|
1637
|
-
def scope_action_options
|
1638
|
-
@scope[:
|
1748
|
+
def scope_action_options
|
1749
|
+
@scope[:action_options] || {}
|
1639
1750
|
end
|
1640
1751
|
|
1641
|
-
def resource_scope?
|
1752
|
+
def resource_scope?
|
1642
1753
|
@scope.resource_scope?
|
1643
1754
|
end
|
1644
1755
|
|
1645
|
-
def resource_method_scope?
|
1756
|
+
def resource_method_scope?
|
1646
1757
|
@scope.resource_method_scope?
|
1647
1758
|
end
|
1648
1759
|
|
1649
|
-
def nested_scope?
|
1760
|
+
def nested_scope?
|
1650
1761
|
@scope.nested?
|
1651
1762
|
end
|
1652
1763
|
|
1653
|
-
def
|
1654
|
-
begin
|
1655
|
-
@scope = @scope.new(:as => nil, :path => nil)
|
1656
|
-
|
1657
|
-
with_scope_level(:exclusive) do
|
1658
|
-
yield
|
1659
|
-
end
|
1660
|
-
ensure
|
1661
|
-
@scope = @scope.parent
|
1662
|
-
end
|
1663
|
-
end
|
1664
|
-
|
1665
|
-
def with_scope_level(kind)
|
1764
|
+
def with_scope_level(kind) # :doc:
|
1666
1765
|
@scope = @scope.new_level(kind)
|
1667
1766
|
yield
|
1668
1767
|
ensure
|
1669
1768
|
@scope = @scope.parent
|
1670
1769
|
end
|
1671
1770
|
|
1672
|
-
def resource_scope(
|
1673
|
-
|
1674
|
-
@scope = @scope.new(:scope_level_resource => resource)
|
1675
|
-
@nesting.push(resource)
|
1771
|
+
def resource_scope(resource)
|
1772
|
+
@scope = @scope.new(scope_level_resource: resource)
|
1676
1773
|
|
1677
|
-
|
1678
|
-
scope(parent_resource.resource_scope) { yield }
|
1679
|
-
end
|
1774
|
+
controller(resource.resource_scope) { yield }
|
1680
1775
|
ensure
|
1681
|
-
@nesting.pop
|
1682
1776
|
@scope = @scope.parent
|
1683
1777
|
end
|
1684
1778
|
|
1685
|
-
def nested_options
|
1686
|
-
options = { :
|
1779
|
+
def nested_options
|
1780
|
+
options = { as: parent_resource.member_name }
|
1687
1781
|
options[:constraints] = {
|
1688
1782
|
parent_resource.nested_param => param_constraint
|
1689
1783
|
} if param_constraint?
|
@@ -1691,62 +1785,61 @@ module ActionDispatch
|
|
1691
1785
|
options
|
1692
1786
|
end
|
1693
1787
|
|
1694
|
-
def
|
1695
|
-
@
|
1788
|
+
def shallow_nesting_depth
|
1789
|
+
@scope.find_all { |node|
|
1790
|
+
node.frame[:scope_level_resource]
|
1791
|
+
}.count { |node| node.frame[:scope_level_resource].shallow? }
|
1696
1792
|
end
|
1697
1793
|
|
1698
|
-
def
|
1699
|
-
@nesting.select(&:shallow?).size
|
1700
|
-
end
|
1701
|
-
|
1702
|
-
def param_constraint? #:nodoc:
|
1794
|
+
def param_constraint?
|
1703
1795
|
@scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp)
|
1704
1796
|
end
|
1705
1797
|
|
1706
|
-
def param_constraint
|
1798
|
+
def param_constraint
|
1707
1799
|
@scope[:constraints][parent_resource.param]
|
1708
1800
|
end
|
1709
1801
|
|
1710
|
-
def canonical_action?(action)
|
1802
|
+
def canonical_action?(action)
|
1711
1803
|
resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
|
1712
1804
|
end
|
1713
1805
|
|
1714
|
-
def shallow_scope
|
1715
|
-
scope = { :
|
1716
|
-
:
|
1806
|
+
def shallow_scope
|
1807
|
+
scope = { as: @scope[:shallow_prefix],
|
1808
|
+
path: @scope[:shallow_path] }
|
1717
1809
|
@scope = @scope.new scope
|
1718
1810
|
|
1719
|
-
|
1811
|
+
yield
|
1720
1812
|
ensure
|
1721
1813
|
@scope = @scope.parent
|
1722
1814
|
end
|
1723
1815
|
|
1724
|
-
def path_for_action(action, path)
|
1725
|
-
|
1816
|
+
def path_for_action(action, path)
|
1817
|
+
return "#{@scope[:path]}/#{path}" if path
|
1818
|
+
|
1819
|
+
if canonical_action?(action)
|
1726
1820
|
@scope[:path].to_s
|
1727
1821
|
else
|
1728
|
-
"#{@scope[:path]}/#{action_path(action
|
1822
|
+
"#{@scope[:path]}/#{action_path(action)}"
|
1729
1823
|
end
|
1730
1824
|
end
|
1731
1825
|
|
1732
|
-
def action_path(name
|
1733
|
-
name
|
1734
|
-
path || @scope[:path_names][name] || name.to_s
|
1826
|
+
def action_path(name)
|
1827
|
+
@scope[:path_names][name.to_sym] || name
|
1735
1828
|
end
|
1736
1829
|
|
1737
|
-
def prefix_name_for_action(as, action)
|
1830
|
+
def prefix_name_for_action(as, action)
|
1738
1831
|
if as
|
1739
1832
|
prefix = as
|
1740
1833
|
elsif !canonical_action?(action)
|
1741
1834
|
prefix = action
|
1742
1835
|
end
|
1743
1836
|
|
1744
|
-
if prefix && prefix !=
|
1745
|
-
Mapper.normalize_name prefix.to_s.tr(
|
1837
|
+
if prefix && prefix != "/" && !prefix.empty?
|
1838
|
+
Mapper.normalize_name prefix.to_s.tr("-", "_")
|
1746
1839
|
end
|
1747
1840
|
end
|
1748
1841
|
|
1749
|
-
def name_for_action(as, action)
|
1842
|
+
def name_for_action(as, action)
|
1750
1843
|
prefix = prefix_name_for_action(as, action)
|
1751
1844
|
name_prefix = @scope[:as]
|
1752
1845
|
|
@@ -1757,21 +1850,22 @@ module ActionDispatch
|
|
1757
1850
|
member_name = parent_resource.member_name
|
1758
1851
|
end
|
1759
1852
|
|
1760
|
-
|
1853
|
+
action_name = @scope.action_name(name_prefix, prefix, collection_name, member_name)
|
1854
|
+
candidate = action_name.select(&:present?).join("_")
|
1761
1855
|
|
1762
|
-
|
1856
|
+
unless candidate.empty?
|
1763
1857
|
# If a name was not explicitly given, we check if it is valid
|
1764
1858
|
# and return nil in case it isn't. Otherwise, we pass the invalid name
|
1765
1859
|
# forward so the underlying router engine treats it and raises an exception.
|
1766
1860
|
if as.nil?
|
1767
|
-
candidate unless candidate
|
1861
|
+
candidate unless !candidate.match?(/\A[_a-z]/i) || has_named_route?(candidate)
|
1768
1862
|
else
|
1769
1863
|
candidate
|
1770
1864
|
end
|
1771
1865
|
end
|
1772
1866
|
end
|
1773
1867
|
|
1774
|
-
def set_member_mappings_for_resource
|
1868
|
+
def set_member_mappings_for_resource # :doc:
|
1775
1869
|
member do
|
1776
1870
|
get :edit if parent_resource.actions.include?(:edit)
|
1777
1871
|
get :show if parent_resource.actions.include?(:show)
|
@@ -1782,6 +1876,120 @@ module ActionDispatch
|
|
1782
1876
|
delete :destroy if parent_resource.actions.include?(:destroy)
|
1783
1877
|
end
|
1784
1878
|
end
|
1879
|
+
|
1880
|
+
def api_only? # :doc:
|
1881
|
+
@set.api_only?
|
1882
|
+
end
|
1883
|
+
|
1884
|
+
def path_scope(path)
|
1885
|
+
@scope = @scope.new(path: merge_path_scope(@scope[:path], path))
|
1886
|
+
yield
|
1887
|
+
ensure
|
1888
|
+
@scope = @scope.parent
|
1889
|
+
end
|
1890
|
+
|
1891
|
+
def map_match(paths, options)
|
1892
|
+
if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
|
1893
|
+
raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
|
1894
|
+
end
|
1895
|
+
|
1896
|
+
if @scope[:to]
|
1897
|
+
options[:to] ||= @scope[:to]
|
1898
|
+
end
|
1899
|
+
|
1900
|
+
if @scope[:controller] && @scope[:action]
|
1901
|
+
options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
|
1902
|
+
end
|
1903
|
+
|
1904
|
+
controller = options.delete(:controller) || @scope[:controller]
|
1905
|
+
option_path = options.delete :path
|
1906
|
+
to = options.delete :to
|
1907
|
+
via = Mapping.check_via Array(options.delete(:via) {
|
1908
|
+
@scope[:via]
|
1909
|
+
})
|
1910
|
+
formatted = options.delete(:format) { @scope[:format] }
|
1911
|
+
anchor = options.delete(:anchor) { true }
|
1912
|
+
options_constraints = options.delete(:constraints) || {}
|
1913
|
+
|
1914
|
+
path_types = paths.group_by(&:class)
|
1915
|
+
(path_types[String] || []).each do |_path|
|
1916
|
+
route_options = options.dup
|
1917
|
+
if _path && option_path
|
1918
|
+
raise ArgumentError, "Ambiguous route definition. Both :path and the route path were specified as strings."
|
1919
|
+
end
|
1920
|
+
to = get_to_from_path(_path, to, route_options[:action])
|
1921
|
+
decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints)
|
1922
|
+
end
|
1923
|
+
|
1924
|
+
(path_types[Symbol] || []).each do |action|
|
1925
|
+
route_options = options.dup
|
1926
|
+
decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints)
|
1927
|
+
end
|
1928
|
+
|
1929
|
+
self
|
1930
|
+
end
|
1931
|
+
|
1932
|
+
def get_to_from_path(path, to, action)
|
1933
|
+
return to if to || action
|
1934
|
+
|
1935
|
+
path_without_format = path.sub(/\(\.:format\)$/, "")
|
1936
|
+
if using_match_shorthand?(path_without_format)
|
1937
|
+
path_without_format.delete_prefix("/").sub(%r{/([^/]*)$}, '#\1').tr("-", "_")
|
1938
|
+
else
|
1939
|
+
nil
|
1940
|
+
end
|
1941
|
+
end
|
1942
|
+
|
1943
|
+
def using_match_shorthand?(path)
|
1944
|
+
%r{^/?[-\w]+/[-\w/]+$}.match?(path)
|
1945
|
+
end
|
1946
|
+
|
1947
|
+
def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
|
1948
|
+
if on = options.delete(:on)
|
1949
|
+
send(on) { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
|
1950
|
+
else
|
1951
|
+
case @scope.scope_level
|
1952
|
+
when :resources
|
1953
|
+
nested { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
|
1954
|
+
when :resource
|
1955
|
+
member { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
|
1956
|
+
else
|
1957
|
+
add_route(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
|
1958
|
+
end
|
1959
|
+
end
|
1960
|
+
end
|
1961
|
+
|
1962
|
+
def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints)
|
1963
|
+
path = path_for_action(action, _path)
|
1964
|
+
raise ArgumentError, "path is required" if path.blank?
|
1965
|
+
|
1966
|
+
action = action.to_s
|
1967
|
+
|
1968
|
+
default_action = options.delete(:action) || @scope[:action]
|
1969
|
+
|
1970
|
+
if /^[\w\-\/]+$/.match?(action)
|
1971
|
+
default_action ||= action.tr("-", "_") unless action.include?("/")
|
1972
|
+
else
|
1973
|
+
action = nil
|
1974
|
+
end
|
1975
|
+
|
1976
|
+
as = if !options.fetch(:as, true) # if it's set to nil or false
|
1977
|
+
options.delete(:as)
|
1978
|
+
else
|
1979
|
+
name_for_action(options.delete(:as), action)
|
1980
|
+
end
|
1981
|
+
|
1982
|
+
path = Mapping.normalize_path URI::DEFAULT_PARSER.escape(path), formatted
|
1983
|
+
ast = Journey::Parser.parse path
|
1984
|
+
|
1985
|
+
mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
|
1986
|
+
@set.add_route(mapping, as)
|
1987
|
+
end
|
1988
|
+
|
1989
|
+
def match_root_route(options)
|
1990
|
+
args = ["/", { as: :root, via: :get }.merge(options)]
|
1991
|
+
match(*args)
|
1992
|
+
end
|
1785
1993
|
end
|
1786
1994
|
|
1787
1995
|
# Routing Concerns allow you to declare common routes that can be reused
|
@@ -1872,7 +2080,7 @@ module ActionDispatch
|
|
1872
2080
|
# concerns :commentable
|
1873
2081
|
# end
|
1874
2082
|
#
|
1875
|
-
#
|
2083
|
+
# Concerns also work in any routes helper that you want to use:
|
1876
2084
|
#
|
1877
2085
|
# namespace :posts do
|
1878
2086
|
# concerns :commentable
|
@@ -1889,17 +2097,131 @@ module ActionDispatch
|
|
1889
2097
|
end
|
1890
2098
|
end
|
1891
2099
|
|
2100
|
+
module CustomUrls
|
2101
|
+
# Define custom URL helpers that will be added to the application's
|
2102
|
+
# routes. This allows you to override and/or replace the default behavior
|
2103
|
+
# of routing helpers, e.g:
|
2104
|
+
#
|
2105
|
+
# direct :homepage do
|
2106
|
+
# "https://rubyonrails.org"
|
2107
|
+
# end
|
2108
|
+
#
|
2109
|
+
# direct :commentable do |model|
|
2110
|
+
# [ model, anchor: model.dom_id ]
|
2111
|
+
# end
|
2112
|
+
#
|
2113
|
+
# direct :main do
|
2114
|
+
# { controller: "pages", action: "index", subdomain: "www" }
|
2115
|
+
# end
|
2116
|
+
#
|
2117
|
+
# The return value from the block passed to +direct+ must be a valid set of
|
2118
|
+
# arguments for +url_for+ which will actually build the URL string. This can
|
2119
|
+
# be one of the following:
|
2120
|
+
#
|
2121
|
+
# * A string, which is treated as a generated URL
|
2122
|
+
# * A hash, e.g. <tt>{ controller: "pages", action: "index" }</tt>
|
2123
|
+
# * An array, which is passed to +polymorphic_url+
|
2124
|
+
# * An Active Model instance
|
2125
|
+
# * An Active Model class
|
2126
|
+
#
|
2127
|
+
# NOTE: Other URL helpers can be called in the block but be careful not to invoke
|
2128
|
+
# your custom URL helper again otherwise it will result in a stack overflow error.
|
2129
|
+
#
|
2130
|
+
# You can also specify default options that will be passed through to
|
2131
|
+
# your URL helper definition, e.g:
|
2132
|
+
#
|
2133
|
+
# direct :browse, page: 1, size: 10 do |options|
|
2134
|
+
# [ :products, options.merge(params.permit(:page, :size).to_h.symbolize_keys) ]
|
2135
|
+
# end
|
2136
|
+
#
|
2137
|
+
# In this instance the +params+ object comes from the context in which the
|
2138
|
+
# block is executed, e.g. generating a URL inside a controller action or a view.
|
2139
|
+
# If the block is executed where there isn't a +params+ object such as this:
|
2140
|
+
#
|
2141
|
+
# Rails.application.routes.url_helpers.browse_path
|
2142
|
+
#
|
2143
|
+
# then it will raise a +NameError+. Because of this you need to be aware of the
|
2144
|
+
# context in which you will use your custom URL helper when defining it.
|
2145
|
+
#
|
2146
|
+
# NOTE: The +direct+ method can't be used inside of a scope block such as
|
2147
|
+
# +namespace+ or +scope+ and will raise an error if it detects that it is.
|
2148
|
+
def direct(name, options = {}, &block)
|
2149
|
+
unless @scope.root?
|
2150
|
+
raise RuntimeError, "The direct method can't be used inside a routes scope block"
|
2151
|
+
end
|
2152
|
+
|
2153
|
+
@set.add_url_helper(name, options, &block)
|
2154
|
+
end
|
2155
|
+
|
2156
|
+
# Define custom polymorphic mappings of models to URLs. This alters the
|
2157
|
+
# behavior of +polymorphic_url+ and consequently the behavior of
|
2158
|
+
# +link_to+ and +form_for+ when passed a model instance, e.g:
|
2159
|
+
#
|
2160
|
+
# resource :basket
|
2161
|
+
#
|
2162
|
+
# resolve "Basket" do
|
2163
|
+
# [:basket]
|
2164
|
+
# end
|
2165
|
+
#
|
2166
|
+
# This will now generate "/basket" when a +Basket+ instance is passed to
|
2167
|
+
# +link_to+ or +form_for+ instead of the standard "/baskets/:id".
|
2168
|
+
#
|
2169
|
+
# NOTE: This custom behavior only applies to simple polymorphic URLs where
|
2170
|
+
# a single model instance is passed and not more complicated forms, e.g:
|
2171
|
+
#
|
2172
|
+
# # config/routes.rb
|
2173
|
+
# resource :profile
|
2174
|
+
# namespace :admin do
|
2175
|
+
# resources :users
|
2176
|
+
# end
|
2177
|
+
#
|
2178
|
+
# resolve("User") { [:profile] }
|
2179
|
+
#
|
2180
|
+
# # app/views/application/_menu.html.erb
|
2181
|
+
# link_to "Profile", @current_user
|
2182
|
+
# link_to "Profile", [:admin, @current_user]
|
2183
|
+
#
|
2184
|
+
# The first +link_to+ will generate "/profile" but the second will generate
|
2185
|
+
# the standard polymorphic URL of "/admin/users/1".
|
2186
|
+
#
|
2187
|
+
# You can pass options to a polymorphic mapping - the arity for the block
|
2188
|
+
# needs to be two as the instance is passed as the first argument, e.g:
|
2189
|
+
#
|
2190
|
+
# resolve "Basket", anchor: "items" do |basket, options|
|
2191
|
+
# [:basket, options]
|
2192
|
+
# end
|
2193
|
+
#
|
2194
|
+
# This generates the URL "/basket#items" because when the last item in an
|
2195
|
+
# array passed to +polymorphic_url+ is a hash then it's treated as options
|
2196
|
+
# to the URL helper that gets called.
|
2197
|
+
#
|
2198
|
+
# NOTE: The +resolve+ method can't be used inside of a scope block such as
|
2199
|
+
# +namespace+ or +scope+ and will raise an error if it detects that it is.
|
2200
|
+
def resolve(*args, &block)
|
2201
|
+
unless @scope.root?
|
2202
|
+
raise RuntimeError, "The resolve method can't be used inside a routes scope block"
|
2203
|
+
end
|
2204
|
+
|
2205
|
+
options = args.extract_options!
|
2206
|
+
args = args.flatten(1)
|
2207
|
+
|
2208
|
+
args.each do |klass|
|
2209
|
+
@set.add_polymorphic_mapping(klass, options, &block)
|
2210
|
+
end
|
2211
|
+
end
|
2212
|
+
end
|
2213
|
+
|
1892
2214
|
class Scope # :nodoc:
|
1893
2215
|
OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
|
1894
2216
|
:controller, :action, :path_names, :constraints,
|
1895
|
-
:shallow, :blocks, :defaults, :options]
|
2217
|
+
:shallow, :blocks, :defaults, :via, :format, :options, :to]
|
1896
2218
|
|
1897
2219
|
RESOURCE_SCOPES = [:resource, :resources]
|
1898
2220
|
RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
|
1899
2221
|
|
1900
2222
|
attr_reader :parent, :scope_level
|
1901
2223
|
|
1902
|
-
def initialize(hash, parent =
|
2224
|
+
def initialize(hash, parent = NULL, scope_level = nil)
|
1903
2225
|
@hash = hash
|
1904
2226
|
@parent = parent
|
1905
2227
|
@scope_level = scope_level
|
@@ -1909,6 +2231,14 @@ module ActionDispatch
|
|
1909
2231
|
scope_level == :nested
|
1910
2232
|
end
|
1911
2233
|
|
2234
|
+
def null?
|
2235
|
+
@hash.nil? && @parent.nil?
|
2236
|
+
end
|
2237
|
+
|
2238
|
+
def root?
|
2239
|
+
@parent.null?
|
2240
|
+
end
|
2241
|
+
|
1912
2242
|
def resources?
|
1913
2243
|
scope_level == :resources
|
1914
2244
|
end
|
@@ -1947,27 +2277,34 @@ module ActionDispatch
|
|
1947
2277
|
end
|
1948
2278
|
|
1949
2279
|
def new_level(level)
|
1950
|
-
self.class.new(
|
1951
|
-
end
|
1952
|
-
|
1953
|
-
def fetch(key, &block)
|
1954
|
-
@hash.fetch(key, &block)
|
2280
|
+
self.class.new(frame, self, level)
|
1955
2281
|
end
|
1956
2282
|
|
1957
2283
|
def [](key)
|
1958
|
-
|
2284
|
+
scope = find { |node| node.frame.key? key }
|
2285
|
+
scope && scope.frame[key]
|
1959
2286
|
end
|
1960
2287
|
|
1961
|
-
|
1962
|
-
|
2288
|
+
include Enumerable
|
2289
|
+
|
2290
|
+
def each
|
2291
|
+
node = self
|
2292
|
+
until node.equal? NULL
|
2293
|
+
yield node
|
2294
|
+
node = node.parent
|
2295
|
+
end
|
1963
2296
|
end
|
2297
|
+
|
2298
|
+
def frame; @hash; end
|
2299
|
+
|
2300
|
+
NULL = Scope.new(nil, nil)
|
1964
2301
|
end
|
1965
2302
|
|
1966
2303
|
def initialize(set) #:nodoc:
|
1967
2304
|
@set = set
|
1968
|
-
@
|
2305
|
+
@draw_paths = set.draw_paths
|
2306
|
+
@scope = Scope.new(path_names: @set.resources_path_names)
|
1969
2307
|
@concerns = {}
|
1970
|
-
@nesting = []
|
1971
2308
|
end
|
1972
2309
|
|
1973
2310
|
include Base
|
@@ -1976,6 +2313,7 @@ module ActionDispatch
|
|
1976
2313
|
include Scoping
|
1977
2314
|
include Concerns
|
1978
2315
|
include Resources
|
2316
|
+
include CustomUrls
|
1979
2317
|
end
|
1980
2318
|
end
|
1981
2319
|
end
|