actionpack 4.2.10 → 7.2.0.rc1
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 +5 -5
- data/CHANGELOG.md +86 -600
- data/MIT-LICENSE +1 -1
- data/README.rdoc +13 -14
- data/lib/abstract_controller/asset_paths.rb +5 -1
- data/lib/abstract_controller/base.rb +166 -136
- data/lib/abstract_controller/caching/fragments.rb +149 -0
- data/lib/abstract_controller/caching.rb +68 -0
- data/lib/abstract_controller/callbacks.rb +126 -57
- data/lib/abstract_controller/collector.rb +13 -15
- data/lib/abstract_controller/deprecator.rb +9 -0
- data/lib/abstract_controller/error.rb +8 -0
- data/lib/abstract_controller/helpers.rb +181 -132
- data/lib/abstract_controller/logger.rb +5 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +10 -3
- data/lib/abstract_controller/rendering.rb +56 -56
- data/lib/abstract_controller/translation.rb +29 -15
- data/lib/abstract_controller/url_for.rb +15 -11
- data/lib/abstract_controller.rb +21 -5
- data/lib/action_controller/api/api_rendering.rb +18 -0
- data/lib/action_controller/api.rb +154 -0
- data/lib/action_controller/base.rb +219 -155
- data/lib/action_controller/caching.rb +28 -68
- data/lib/action_controller/deprecator.rb +9 -0
- data/lib/action_controller/form_builder.rb +55 -0
- data/lib/action_controller/log_subscriber.rb +35 -22
- data/lib/action_controller/metal/allow_browser.rb +119 -0
- data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
- data/lib/action_controller/metal/conditional_get.rb +259 -122
- data/lib/action_controller/metal/content_security_policy.rb +86 -0
- data/lib/action_controller/metal/cookies.rb +9 -5
- data/lib/action_controller/metal/data_streaming.rb +87 -104
- data/lib/action_controller/metal/default_headers.rb +21 -0
- data/lib/action_controller/metal/etag_with_flash.rb +22 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +35 -26
- data/lib/action_controller/metal/exceptions.rb +71 -24
- data/lib/action_controller/metal/flash.rb +26 -19
- data/lib/action_controller/metal/head.rb +45 -36
- data/lib/action_controller/metal/helpers.rb +80 -64
- data/lib/action_controller/metal/http_authentication.rb +297 -244
- data/lib/action_controller/metal/implicit_render.rb +57 -9
- data/lib/action_controller/metal/instrumentation.rb +76 -64
- data/lib/action_controller/metal/live.rb +238 -176
- data/lib/action_controller/metal/logging.rb +22 -0
- data/lib/action_controller/metal/mime_responds.rb +177 -166
- data/lib/action_controller/metal/parameter_encoding.rb +84 -0
- data/lib/action_controller/metal/params_wrapper.rb +145 -118
- data/lib/action_controller/metal/permissions_policy.rb +38 -0
- data/lib/action_controller/metal/rate_limiting.rb +62 -0
- data/lib/action_controller/metal/redirecting.rb +203 -64
- data/lib/action_controller/metal/renderers.rb +108 -65
- data/lib/action_controller/metal/rendering.rb +216 -56
- data/lib/action_controller/metal/request_forgery_protection.rb +496 -163
- data/lib/action_controller/metal/rescue.rb +19 -21
- data/lib/action_controller/metal/streaming.rb +179 -138
- data/lib/action_controller/metal/strong_parameters.rb +1058 -382
- data/lib/action_controller/metal/testing.rb +11 -17
- data/lib/action_controller/metal/url_for.rb +37 -21
- data/lib/action_controller/metal.rb +236 -138
- data/lib/action_controller/railtie.rb +89 -11
- data/lib/action_controller/railties/helpers.rb +5 -1
- data/lib/action_controller/renderer.rb +161 -0
- data/lib/action_controller/template_assertions.rb +13 -0
- data/lib/action_controller/test_case.rb +425 -497
- data/lib/action_controller.rb +44 -22
- data/lib/action_dispatch/constants.rb +34 -0
- data/lib/action_dispatch/deprecator.rb +9 -0
- data/lib/action_dispatch/http/cache.rb +119 -63
- data/lib/action_dispatch/http/content_disposition.rb +47 -0
- data/lib/action_dispatch/http/content_security_policy.rb +364 -0
- data/lib/action_dispatch/http/filter_parameters.rb +36 -34
- data/lib/action_dispatch/http/filter_redirect.rb +24 -12
- data/lib/action_dispatch/http/headers.rb +66 -31
- data/lib/action_dispatch/http/mime_negotiation.rb +106 -75
- data/lib/action_dispatch/http/mime_type.rb +196 -136
- data/lib/action_dispatch/http/mime_types.rb +25 -7
- data/lib/action_dispatch/http/parameters.rb +97 -45
- data/lib/action_dispatch/http/permissions_policy.rb +187 -0
- data/lib/action_dispatch/http/rack_cache.rb +6 -0
- data/lib/action_dispatch/http/request.rb +299 -170
- data/lib/action_dispatch/http/response.rb +311 -160
- data/lib/action_dispatch/http/upload.rb +52 -23
- data/lib/action_dispatch/http/url.rb +201 -125
- data/lib/action_dispatch/journey/formatter.rb +110 -50
- data/lib/action_dispatch/journey/gtg/builder.rb +37 -50
- data/lib/action_dispatch/journey/gtg/simulator.rb +20 -17
- data/lib/action_dispatch/journey/gtg/transition_table.rb +96 -36
- data/lib/action_dispatch/journey/nfa/dot.rb +5 -14
- data/lib/action_dispatch/journey/nodes/node.rb +100 -20
- data/lib/action_dispatch/journey/parser.rb +19 -17
- data/lib/action_dispatch/journey/parser.y +4 -3
- data/lib/action_dispatch/journey/parser_extras.rb +14 -4
- data/lib/action_dispatch/journey/path/pattern.rb +79 -63
- data/lib/action_dispatch/journey/route.rb +108 -44
- data/lib/action_dispatch/journey/router/utils.rb +41 -29
- data/lib/action_dispatch/journey/router.rb +64 -57
- data/lib/action_dispatch/journey/routes.rb +23 -21
- data/lib/action_dispatch/journey/scanner.rb +28 -17
- data/lib/action_dispatch/journey/visitors.rb +100 -54
- 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 +7 -5
- data/lib/action_dispatch/log_subscriber.rb +25 -0
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
- data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
- data/lib/action_dispatch/middleware/callbacks.rb +7 -6
- data/lib/action_dispatch/middleware/cookies.rb +471 -328
- data/lib/action_dispatch/middleware/debug_exceptions.rb +149 -66
- data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
- data/lib/action_dispatch/middleware/debug_view.rb +73 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +275 -73
- data/lib/action_dispatch/middleware/executor.rb +32 -0
- data/lib/action_dispatch/middleware/flash.rb +143 -101
- data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +36 -27
- data/lib/action_dispatch/middleware/reloader.rb +10 -92
- data/lib/action_dispatch/middleware/remote_ip.rb +133 -107
- data/lib/action_dispatch/middleware/request_id.rb +29 -15
- data/lib/action_dispatch/middleware/server_timing.rb +78 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +49 -27
- data/lib/action_dispatch/middleware/session/cache_store.rb +33 -16
- data/lib/action_dispatch/middleware/session/cookie_store.rb +86 -80
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +15 -3
- data/lib/action_dispatch/middleware/show_exceptions.rb +66 -36
- data/lib/action_dispatch/middleware/ssl.rb +134 -36
- data/lib/action_dispatch/middleware/stack.rb +109 -44
- data/lib/action_dispatch/middleware/static.rb +159 -90
- 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 +7 -24
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +26 -7
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +139 -15
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -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 +6 -6
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +7 -7
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +9 -9
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +7 -4
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +125 -93
- data/lib/action_dispatch/railtie.rb +44 -16
- data/lib/action_dispatch/request/session.rb +159 -69
- data/lib/action_dispatch/request/utils.rb +97 -23
- data/lib/action_dispatch/routing/endpoint.rb +11 -2
- data/lib/action_dispatch/routing/inspector.rb +195 -106
- data/lib/action_dispatch/routing/mapper.rb +1338 -955
- data/lib/action_dispatch/routing/polymorphic_routes.rb +234 -201
- data/lib/action_dispatch/routing/redirection.rb +78 -51
- data/lib/action_dispatch/routing/route_set.rb +460 -374
- data/lib/action_dispatch/routing/routes_proxy.rb +36 -12
- data/lib/action_dispatch/routing/url_for.rb +172 -124
- data/lib/action_dispatch/routing.rb +159 -158
- data/lib/action_dispatch/system_test_case.rb +206 -0
- data/lib/action_dispatch/system_testing/browser.rb +84 -0
- data/lib/action_dispatch/system_testing/driver.rb +85 -0
- data/lib/action_dispatch/system_testing/server.rb +33 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
- data/lib/action_dispatch/testing/assertion_response.rb +48 -0
- data/lib/action_dispatch/testing/assertions/response.rb +71 -39
- data/lib/action_dispatch/testing/assertions/routing.rb +228 -103
- data/lib/action_dispatch/testing/assertions.rb +9 -6
- data/lib/action_dispatch/testing/integration.rb +486 -306
- data/lib/action_dispatch/testing/request_encoder.rb +60 -0
- data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
- data/lib/action_dispatch/testing/test_process.rb +35 -22
- data/lib/action_dispatch/testing/test_request.rb +29 -34
- data/lib/action_dispatch/testing/test_response.rb +48 -15
- data/lib/action_dispatch.rb +82 -40
- data/lib/action_pack/gem_version.rb +8 -4
- data/lib/action_pack/version.rb +6 -2
- data/lib/action_pack.rb +21 -18
- metadata +146 -56
- data/lib/action_controller/caching/fragments.rb +0 -103
- 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/middleware/templates/rescues/_source.erb +0 -27
- 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,53 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require 'active_support/deprecation'
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
require "active_support/core_ext/hash/slice"
|
6
|
+
require "active_support/core_ext/enumerable"
|
7
|
+
require "active_support/core_ext/array/extract_options"
|
8
|
+
require "active_support/core_ext/regexp"
|
9
|
+
require "action_dispatch/routing/redirection"
|
10
|
+
require "action_dispatch/routing/endpoint"
|
12
11
|
|
13
12
|
module ActionDispatch
|
14
13
|
module Routing
|
15
14
|
class Mapper
|
15
|
+
class BacktraceCleaner < ActiveSupport::BacktraceCleaner # :nodoc:
|
16
|
+
def initialize
|
17
|
+
super
|
18
|
+
remove_silencers!
|
19
|
+
add_core_silencer
|
20
|
+
add_stdlib_silencer
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
16
24
|
URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
|
17
25
|
|
18
|
-
|
26
|
+
cattr_accessor :route_source_locations, instance_accessor: false, default: false
|
27
|
+
cattr_accessor :backtrace_cleaner, instance_accessor: false, default: BacktraceCleaner.new
|
28
|
+
|
29
|
+
class Constraints < Routing::Endpoint # :nodoc:
|
19
30
|
attr_reader :app, :constraints
|
20
31
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
#
|
32
|
+
SERVE = ->(app, req) { app.serve req }
|
33
|
+
CALL = ->(app, req) { app.call req.env }
|
34
|
+
|
35
|
+
def initialize(app, constraints, strategy)
|
36
|
+
# Unwrap Constraints objects. I don't actually think it's possible to pass a
|
37
|
+
# Constraints object to this constructor, but there were multiple places that
|
38
|
+
# kept testing children of this object. I **think** they were just being
|
39
|
+
# defensive, but I have no idea.
|
26
40
|
if app.is_a?(self.class)
|
27
41
|
constraints += app.constraints
|
28
42
|
app = app.app
|
29
43
|
end
|
30
44
|
|
31
|
-
@
|
45
|
+
@strategy = strategy
|
32
46
|
|
33
47
|
@app, @constraints, = app, constraints
|
34
48
|
end
|
35
49
|
|
36
|
-
def dispatcher?; @
|
50
|
+
def dispatcher?; @strategy == SERVE; end
|
37
51
|
|
38
52
|
def matches?(req)
|
39
53
|
@constraints.all? do |constraint|
|
@@ -43,136 +57,192 @@ module ActionDispatch
|
|
43
57
|
end
|
44
58
|
|
45
59
|
def serve(req)
|
46
|
-
return [ 404, {
|
60
|
+
return [ 404, { Constants::X_CASCADE => "pass" }, [] ] unless matches?(req)
|
47
61
|
|
48
|
-
|
49
|
-
@app.serve req
|
50
|
-
else
|
51
|
-
@app.call req.env
|
52
|
-
end
|
62
|
+
@strategy.call @app, req
|
53
63
|
end
|
54
64
|
|
55
65
|
private
|
56
66
|
def constraint_args(constraint, request)
|
57
|
-
|
67
|
+
arity = if constraint.respond_to?(:arity)
|
68
|
+
constraint.arity
|
69
|
+
else
|
70
|
+
constraint.method(:call).arity
|
71
|
+
end
|
72
|
+
|
73
|
+
if arity < 1
|
74
|
+
[]
|
75
|
+
elsif arity == 1
|
76
|
+
[request]
|
77
|
+
else
|
78
|
+
[request.path_parameters, request]
|
79
|
+
end
|
58
80
|
end
|
59
81
|
end
|
60
82
|
|
61
|
-
class Mapping
|
83
|
+
class Mapping # :nodoc:
|
62
84
|
ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
|
63
85
|
OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z}
|
64
86
|
|
65
|
-
attr_reader :requirements, :
|
66
|
-
|
87
|
+
attr_reader :path, :requirements, :defaults, :to, :default_controller,
|
88
|
+
:default_action, :required_defaults, :ast, :scope_options
|
89
|
+
|
90
|
+
def self.build(scope, set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
|
91
|
+
scope_params = {
|
92
|
+
blocks: scope[:blocks] || [],
|
93
|
+
constraints: scope[:constraints] || {},
|
94
|
+
defaults: (scope[:defaults] || {}).dup,
|
95
|
+
module: scope[:module],
|
96
|
+
options: scope[:options] || {}
|
97
|
+
}
|
98
|
+
|
99
|
+
new set: set, ast: ast, controller: controller, default_action: default_action,
|
100
|
+
to: to, formatted: formatted, via: via, options_constraints: options_constraints,
|
101
|
+
anchor: anchor, scope_params: scope_params, options: scope_params[:options].merge(options)
|
102
|
+
end
|
67
103
|
|
68
|
-
def self.
|
69
|
-
|
104
|
+
def self.check_via(via)
|
105
|
+
if via.empty?
|
106
|
+
msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
|
107
|
+
"If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
|
108
|
+
"If you want to expose your action to GET, use `get` in the router:\n" \
|
109
|
+
" Instead of: match \"controller#action\"\n" \
|
110
|
+
" Do: get \"controller#action\""
|
111
|
+
raise ArgumentError, msg
|
112
|
+
end
|
113
|
+
via
|
114
|
+
end
|
70
115
|
|
71
|
-
|
72
|
-
|
73
|
-
options.delete :shallow_path
|
74
|
-
options.delete :shallow_prefix
|
75
|
-
options.delete :shallow
|
116
|
+
def self.normalize_path(path, format)
|
117
|
+
path = Mapper.normalize_path(path)
|
76
118
|
|
77
|
-
|
119
|
+
if format == true
|
120
|
+
"#{path}.:format"
|
121
|
+
elsif optional_format?(path, format)
|
122
|
+
"#{path}(.:format)"
|
123
|
+
else
|
124
|
+
path
|
125
|
+
end
|
126
|
+
end
|
78
127
|
|
79
|
-
|
128
|
+
def self.optional_format?(path, format)
|
129
|
+
format != false && !path.match?(OPTIONAL_FORMAT_REGEX)
|
80
130
|
end
|
81
131
|
|
82
|
-
def initialize(
|
83
|
-
@
|
84
|
-
@
|
85
|
-
@
|
132
|
+
def initialize(set:, ast:, controller:, default_action:, to:, formatted:, via:, options_constraints:, anchor:, scope_params:, options:)
|
133
|
+
@defaults = scope_params[:defaults]
|
134
|
+
@set = set
|
135
|
+
@to = intern(to)
|
136
|
+
@default_controller = intern(controller)
|
137
|
+
@default_action = intern(default_action)
|
138
|
+
@anchor = anchor
|
139
|
+
@via = via
|
140
|
+
@internal = options.delete(:internal)
|
141
|
+
@scope_options = scope_params[:options]
|
142
|
+
ast = Journey::Ast.new(ast, formatted)
|
86
143
|
|
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
|
144
|
+
options = ast.wildcard_options.merge!(options)
|
92
145
|
|
93
|
-
|
94
|
-
via = Array(options.delete(:via) { [] })
|
95
|
-
options_constraints = options.delete :constraints
|
146
|
+
options = normalize_options!(options, ast.path_params, scope_params[:module])
|
96
147
|
|
97
|
-
|
98
|
-
ast = path_ast path
|
99
|
-
path_params = path_params ast
|
148
|
+
split_options = constraints(options, ast.path_params)
|
100
149
|
|
101
|
-
|
150
|
+
constraints = scope_params[:constraints].merge Hash[split_options[:constraints] || []]
|
102
151
|
|
152
|
+
if options_constraints.is_a?(Hash)
|
153
|
+
@defaults = Hash[options_constraints.find_all { |key, default|
|
154
|
+
URL_OPTIONS.include?(key) && (String === default || Integer === default)
|
155
|
+
}].merge @defaults
|
156
|
+
@blocks = scope_params[:blocks]
|
157
|
+
constraints.merge! options_constraints
|
158
|
+
else
|
159
|
+
@blocks = blocks(options_constraints)
|
160
|
+
end
|
103
161
|
|
104
|
-
split_constraints
|
105
|
-
|
162
|
+
requirements, conditions = split_constraints ast.path_params, constraints
|
163
|
+
verify_regexp_requirements requirements, ast.wildcard_options
|
106
164
|
|
107
|
-
|
165
|
+
formats = normalize_format(formatted)
|
108
166
|
|
109
|
-
@
|
167
|
+
@requirements = formats[:requirements].merge Hash[requirements]
|
168
|
+
@conditions = Hash[conditions]
|
169
|
+
@defaults = formats[:defaults].merge(@defaults).merge(normalize_defaults(options))
|
110
170
|
|
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
|
171
|
+
if ast.path_params.include?(:action) && !@requirements.key?(:action)
|
172
|
+
@defaults[:action] ||= "index"
|
118
173
|
end
|
119
174
|
|
120
|
-
|
175
|
+
@required_defaults = (split_options[:required_defaults] || []).map(&:first)
|
121
176
|
|
122
|
-
|
123
|
-
@
|
177
|
+
ast.requirements = @requirements
|
178
|
+
@path = Journey::Path::Pattern.new(ast, @requirements, JOINED_SEPARATORS, @anchor)
|
179
|
+
end
|
180
|
+
|
181
|
+
JOINED_SEPARATORS = SEPARATORS.join # :nodoc:
|
124
182
|
|
125
|
-
|
126
|
-
|
183
|
+
def make_route(name, precedence)
|
184
|
+
Journey::Route.new(name: name, app: application, path: path, constraints: conditions,
|
185
|
+
required_defaults: required_defaults, defaults: defaults,
|
186
|
+
request_method_match: request_method, precedence: precedence,
|
187
|
+
scope_options: scope_options, internal: @internal, source_location: route_source_location)
|
127
188
|
end
|
128
189
|
|
129
|
-
def
|
130
|
-
|
190
|
+
def application
|
191
|
+
app(@blocks)
|
131
192
|
end
|
132
193
|
|
133
|
-
|
194
|
+
def conditions
|
195
|
+
build_conditions @conditions, @set.request_class
|
196
|
+
end
|
134
197
|
|
135
|
-
|
136
|
-
|
198
|
+
def build_conditions(current_conditions, request_class)
|
199
|
+
conditions = current_conditions.dup
|
137
200
|
|
138
|
-
|
139
|
-
|
140
|
-
elsif optional_format?(path, format)
|
141
|
-
"#{path}(.:format)"
|
142
|
-
else
|
143
|
-
path
|
144
|
-
end
|
201
|
+
conditions.keep_if do |k, _|
|
202
|
+
request_class.public_method_defined?(k)
|
145
203
|
end
|
204
|
+
end
|
205
|
+
private :build_conditions
|
146
206
|
|
147
|
-
|
148
|
-
|
149
|
-
|
207
|
+
def request_method
|
208
|
+
@via.map { |x| Journey::Route.verb_matcher(x) }
|
209
|
+
end
|
210
|
+
private :request_method
|
150
211
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
path_ast.grep(Journey::Nodes::Star) do |node|
|
156
|
-
options[node.name.to_sym] ||= /.+?/
|
157
|
-
end
|
158
|
-
end
|
212
|
+
private
|
213
|
+
def intern(object)
|
214
|
+
object.is_a?(String) ? -object : object
|
215
|
+
end
|
159
216
|
|
217
|
+
def normalize_options!(options, path_params, modyoule)
|
160
218
|
if path_params.include?(:controller)
|
161
219
|
raise ArgumentError, ":controller segment is not allowed within a namespace block" if modyoule
|
162
220
|
|
163
221
|
# Add a default constraint for :controller path segments that matches namespaced
|
164
222
|
# controllers with default routes like :controller/:action/:id(.:format), e.g:
|
165
223
|
# GET /admin/products/show/1
|
166
|
-
#
|
224
|
+
# # > { controller: 'admin/products', action: 'show', id: '1' }
|
167
225
|
options[:controller] ||= /.+?/
|
168
226
|
end
|
169
227
|
|
170
|
-
if to.respond_to? :call
|
228
|
+
if to.respond_to?(:action) || to.respond_to?(:call)
|
171
229
|
options
|
172
230
|
else
|
173
|
-
|
174
|
-
|
175
|
-
|
231
|
+
if to.nil?
|
232
|
+
controller = default_controller
|
233
|
+
action = default_action
|
234
|
+
elsif to.is_a?(String)
|
235
|
+
if to.include?("#")
|
236
|
+
to_endpoint = to.split("#").map!(&:-@)
|
237
|
+
controller = to_endpoint[0]
|
238
|
+
action = to_endpoint[1]
|
239
|
+
else
|
240
|
+
controller = default_controller
|
241
|
+
action = to
|
242
|
+
end
|
243
|
+
else
|
244
|
+
raise ArgumentError, ":to must respond to `action` or `call`, or it must be a String that includes '#', or the controller should be implicit"
|
245
|
+
end
|
176
246
|
|
177
247
|
controller = add_controller_module(controller, modyoule)
|
178
248
|
|
@@ -181,82 +251,64 @@ module ActionDispatch
|
|
181
251
|
end
|
182
252
|
|
183
253
|
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
|
254
|
+
constraints.partition do |key, requirement|
|
255
|
+
path_params.include?(key) || key == :controller
|
191
256
|
end
|
192
257
|
end
|
193
258
|
|
194
|
-
def normalize_format
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
259
|
+
def normalize_format(formatted)
|
260
|
+
case formatted
|
261
|
+
when true
|
262
|
+
{ requirements: { format: /.+/ },
|
263
|
+
defaults: {} }
|
264
|
+
when Regexp
|
265
|
+
{ requirements: { format: formatted },
|
266
|
+
defaults: { format: nil } }
|
267
|
+
when String
|
268
|
+
{ requirements: { format: Regexp.compile(formatted) },
|
269
|
+
defaults: { format: formatted } }
|
270
|
+
else
|
271
|
+
{ requirements: {}, defaults: {} }
|
203
272
|
end
|
204
273
|
end
|
205
274
|
|
206
|
-
def
|
207
|
-
|
208
|
-
|
209
|
-
end
|
210
|
-
|
211
|
-
if requirement.multiline?
|
212
|
-
raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
|
213
|
-
end
|
214
|
-
end
|
275
|
+
def verify_regexp_requirements(requirements, wildcard_options)
|
276
|
+
requirements.each do |requirement, regex|
|
277
|
+
next unless regex.is_a? Regexp
|
215
278
|
|
216
|
-
|
217
|
-
|
218
|
-
unless Regexp === default
|
219
|
-
@defaults[key] = default
|
279
|
+
if ANCHOR_CHARACTERS_REGEX.match?(regex.source)
|
280
|
+
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
|
220
281
|
end
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
def verify_callable_constraint(callable_constraint)
|
225
|
-
unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?)
|
226
|
-
raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?"
|
227
|
-
end
|
228
|
-
end
|
229
282
|
|
230
|
-
|
231
|
-
|
283
|
+
if regex.multiline?
|
284
|
+
next if wildcard_options.key?(requirement)
|
232
285
|
|
233
|
-
|
234
|
-
|
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
|
286
|
+
raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{regex.inspect}"
|
287
|
+
end
|
240
288
|
end
|
289
|
+
end
|
241
290
|
|
242
|
-
|
291
|
+
def normalize_defaults(options)
|
292
|
+
Hash[options.reject { |_, default| Regexp === default }]
|
243
293
|
end
|
244
294
|
|
245
295
|
def app(blocks)
|
246
|
-
if to.respond_to?(:
|
247
|
-
|
296
|
+
if to.respond_to?(:action)
|
297
|
+
Routing::RouteSet::StaticDispatcher.new to
|
298
|
+
elsif to.respond_to?(:call)
|
299
|
+
Constraints.new(to, blocks, Constraints::CALL)
|
248
300
|
elsif blocks.any?
|
249
|
-
Constraints.new(dispatcher(defaults), blocks,
|
301
|
+
Constraints.new(dispatcher(defaults.key?(:controller)), blocks, Constraints::SERVE)
|
250
302
|
else
|
251
|
-
dispatcher(defaults)
|
303
|
+
dispatcher(defaults.key?(:controller))
|
252
304
|
end
|
253
305
|
end
|
254
306
|
|
255
307
|
def check_controller_and_action(path_params, controller, action)
|
256
308
|
hash = check_part(:controller, controller, path_params, {}) do |part|
|
257
309
|
translate_controller(part) {
|
258
|
-
message = "'#{part}' is not a supported controller name. This can lead to potential routing problems."
|
259
|
-
message << " See
|
310
|
+
message = +"'#{part}' is not a supported controller name. This can lead to potential routing problems."
|
311
|
+
message << " See https://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
|
260
312
|
|
261
313
|
raise ArgumentError, message
|
262
314
|
}
|
@@ -279,34 +331,12 @@ module ActionDispatch
|
|
279
331
|
hash
|
280
332
|
end
|
281
333
|
|
282
|
-
def split_to(to)
|
283
|
-
case to
|
284
|
-
when Symbol
|
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]
|
299
|
-
else
|
300
|
-
[]
|
301
|
-
end
|
302
|
-
end
|
303
|
-
|
304
334
|
def add_controller_module(controller, modyoule)
|
305
335
|
if modyoule && !controller.is_a?(Regexp)
|
306
|
-
if controller
|
307
|
-
controller[1..-1]
|
336
|
+
if controller&.start_with?("/")
|
337
|
+
-controller[1..-1]
|
308
338
|
else
|
309
|
-
[modyoule, controller].compact.join("/")
|
339
|
+
-[modyoule, controller].compact.join("/")
|
310
340
|
end
|
311
341
|
else
|
312
342
|
controller
|
@@ -315,54 +345,84 @@ module ActionDispatch
|
|
315
345
|
|
316
346
|
def translate_controller(controller)
|
317
347
|
return controller if Regexp === controller
|
318
|
-
return controller.to_s if
|
348
|
+
return controller.to_s if /\A[a-z_0-9][a-z_0-9\/]*\z/.match?(controller)
|
319
349
|
|
320
350
|
yield
|
321
351
|
end
|
322
352
|
|
323
|
-
def blocks(
|
324
|
-
|
325
|
-
|
326
|
-
[options_constraints]
|
327
|
-
else
|
328
|
-
scope_blocks || []
|
353
|
+
def blocks(callable_constraint)
|
354
|
+
unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?)
|
355
|
+
raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?"
|
329
356
|
end
|
357
|
+
[callable_constraint]
|
330
358
|
end
|
331
359
|
|
332
360
|
def constraints(options, path_params)
|
333
|
-
|
334
|
-
required_defaults = []
|
335
|
-
options.each_pair do |key, option|
|
361
|
+
options.group_by do |key, option|
|
336
362
|
if Regexp === option
|
337
|
-
constraints
|
363
|
+
:constraints
|
338
364
|
else
|
339
|
-
|
365
|
+
if path_params.include?(key)
|
366
|
+
:path_params
|
367
|
+
else
|
368
|
+
:required_defaults
|
369
|
+
end
|
340
370
|
end
|
341
371
|
end
|
342
|
-
@conditions[:required_defaults] = required_defaults
|
343
|
-
constraints
|
344
372
|
end
|
345
373
|
|
346
|
-
def
|
347
|
-
|
374
|
+
def dispatcher(raise_on_name_error)
|
375
|
+
Routing::RouteSet::Dispatcher.new raise_on_name_error
|
348
376
|
end
|
349
377
|
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
378
|
+
if Thread.respond_to?(:each_caller_location)
|
379
|
+
def route_source_location
|
380
|
+
if Mapper.route_source_locations
|
381
|
+
action_dispatch_dir = File.expand_path("..", __dir__)
|
382
|
+
Thread.each_caller_location do |location|
|
383
|
+
next if location.path.start_with?(action_dispatch_dir)
|
384
|
+
|
385
|
+
cleaned_path = Mapper.backtrace_cleaner.clean_frame(location.path)
|
386
|
+
next if cleaned_path.nil?
|
387
|
+
|
388
|
+
return "#{cleaned_path}:#{location.lineno}"
|
389
|
+
end
|
390
|
+
nil
|
391
|
+
end
|
392
|
+
end
|
393
|
+
else
|
394
|
+
def route_source_location
|
395
|
+
if Mapper.route_source_locations
|
396
|
+
action_dispatch_dir = File.expand_path("..", __dir__)
|
397
|
+
caller_locations.each do |location|
|
398
|
+
next if location.path.start_with?(action_dispatch_dir)
|
399
|
+
|
400
|
+
cleaned_path = Mapper.backtrace_cleaner.clean_frame(location.path)
|
401
|
+
next if cleaned_path.nil?
|
354
402
|
|
355
|
-
|
356
|
-
|
403
|
+
return "#{cleaned_path}:#{location.lineno}"
|
404
|
+
end
|
405
|
+
nil
|
406
|
+
end
|
407
|
+
end
|
357
408
|
end
|
358
409
|
end
|
359
410
|
|
360
|
-
# Invokes Journey::Router::Utils.normalize_path
|
361
|
-
#
|
362
|
-
#
|
411
|
+
# Invokes Journey::Router::Utils.normalize_path, then ensures that /(:locale)
|
412
|
+
# becomes (/:locale). Except for root cases, where the former is the correct
|
413
|
+
# one.
|
363
414
|
def self.normalize_path(path)
|
364
415
|
path = Journey::Router::Utils.normalize_path(path)
|
365
|
-
|
416
|
+
|
417
|
+
# the path for a root URL at this point can be something like
|
418
|
+
# "/(/:locale)(/:platform)/(:browser)", and we would want
|
419
|
+
# "/(:locale)(/:platform)(/:browser)" reverse "/(", "/((" etc to "(/", "((/" etc
|
420
|
+
path.gsub!(%r{/(\(+)/?}, '\1/')
|
421
|
+
# if a path is all optional segments, change the leading "(/" back to "/(" so it
|
422
|
+
# evaluates to "/" when interpreted with no options. Unless, however, at least
|
423
|
+
# one secondary segment consists of a static part, ex.
|
424
|
+
# "(/:locale)(/pages/:page)"
|
425
|
+
path.sub!(%r{^(\(+)/}, '/\1') if %r{^(\(+[^)]+\))(\(+/:[^)]+\))*$}.match?(path)
|
366
426
|
path
|
367
427
|
end
|
368
428
|
|
@@ -371,214 +431,221 @@ module ActionDispatch
|
|
371
431
|
end
|
372
432
|
|
373
433
|
module Base
|
374
|
-
#
|
434
|
+
# Matches a URL pattern to one or more routes.
|
375
435
|
#
|
376
|
-
#
|
436
|
+
# You should not use the `match` method in your router without specifying an
|
437
|
+
# HTTP method.
|
377
438
|
#
|
378
|
-
#
|
439
|
+
# If you want to expose your action to both GET and POST, use:
|
379
440
|
#
|
380
|
-
#
|
441
|
+
# # sets :controller, :action, and :id in params
|
442
|
+
# match ':controller/:action/:id', via: [:get, :post]
|
381
443
|
#
|
382
|
-
#
|
444
|
+
# Note that `:controller`, `:action`, and `:id` are interpreted as URL query
|
445
|
+
# parameters and thus available through `params` in an action.
|
383
446
|
#
|
384
|
-
#
|
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.
|
447
|
+
# If you want to expose your action to GET, use `get` in the router:
|
392
448
|
#
|
393
|
-
#
|
394
|
-
# without specifying an HTTP method.
|
449
|
+
# Instead of:
|
395
450
|
#
|
396
|
-
#
|
451
|
+
# match ":controller/:action/:id"
|
452
|
+
#
|
453
|
+
# Do:
|
397
454
|
#
|
398
|
-
#
|
399
|
-
# match ':controller/:action/:id', via: [:get, :post]
|
455
|
+
# get ":controller/:action/:id"
|
400
456
|
#
|
401
|
-
#
|
402
|
-
#
|
457
|
+
# Two of these symbols are special, `:controller` maps to the controller and
|
458
|
+
# `:action` to the controller's action. A pattern can also map wildcard segments
|
459
|
+
# (globs) to params:
|
403
460
|
#
|
404
|
-
#
|
461
|
+
# get 'songs/*category/:title', to: 'songs#show'
|
405
462
|
#
|
406
|
-
#
|
463
|
+
# # 'songs/rock/classic/stairway-to-heaven' sets
|
464
|
+
# # params[:category] = 'rock/classic'
|
465
|
+
# # params[:title] = 'stairway-to-heaven'
|
407
466
|
#
|
408
|
-
#
|
467
|
+
# To match a wildcard parameter, it must have a name assigned to it. Without a
|
468
|
+
# variable name to attach the glob parameter to, the route can't be parsed.
|
409
469
|
#
|
410
|
-
#
|
470
|
+
# When a pattern points to an internal route, the route's `:action` and
|
471
|
+
# `:controller` should be set in options or hash shorthand. Examples:
|
411
472
|
#
|
412
|
-
#
|
473
|
+
# match 'photos/:id' => 'photos#show', via: :get
|
474
|
+
# match 'photos/:id', to: 'photos#show', via: :get
|
475
|
+
# match 'photos/:id', controller: 'photos', action: 'show', via: :get
|
413
476
|
#
|
414
|
-
#
|
415
|
-
#
|
416
|
-
# wildcard segments (globs) to params:
|
477
|
+
# A pattern can also point to a `Rack` endpoint i.e. anything that responds to
|
478
|
+
# `call`:
|
417
479
|
#
|
418
|
-
#
|
480
|
+
# match 'photos/:id', to: -> (hash) { [200, {}, ["Coming soon"]] }, via: :get
|
481
|
+
# match 'photos/:id', to: PhotoRackApp, via: :get
|
482
|
+
# # Yes, controller actions are just rack endpoints
|
483
|
+
# match 'photos/:id', to: PhotosController.action(:show), via: :get
|
419
484
|
#
|
420
|
-
#
|
421
|
-
#
|
422
|
-
#
|
485
|
+
# Because requesting various HTTP verbs with a single action has security
|
486
|
+
# implications, you must either specify the actions in the via options or use
|
487
|
+
# one of the [HttpHelpers](rdoc-ref:HttpHelpers) instead `match`
|
423
488
|
#
|
424
|
-
#
|
425
|
-
# Without a variable name to attach the glob parameter to, the route
|
426
|
-
# can't be parsed.
|
489
|
+
# ### Options
|
427
490
|
#
|
428
|
-
#
|
429
|
-
# +:controller+ should be set in options or hash shorthand. Examples:
|
491
|
+
# Any options not seen here are passed on as params with the URL.
|
430
492
|
#
|
431
|
-
#
|
432
|
-
#
|
433
|
-
# match 'photos/:id', controller: 'photos', action: 'show', via: :get
|
493
|
+
# :controller
|
494
|
+
# : The route's controller.
|
434
495
|
#
|
435
|
-
#
|
436
|
-
#
|
496
|
+
# :action
|
497
|
+
# : The route's action.
|
437
498
|
#
|
438
|
-
#
|
439
|
-
#
|
440
|
-
#
|
441
|
-
#
|
499
|
+
# :param
|
500
|
+
# : Overrides the default resource identifier `:id` (name of the dynamic
|
501
|
+
# segment used to generate the routes). You can access that segment from
|
502
|
+
# your controller using `params[<:param>]`. In your router:
|
442
503
|
#
|
443
|
-
#
|
444
|
-
# implications, you must either specify the actions in
|
445
|
-
# the via options or use one of the HttpHelpers[rdoc-ref:HttpHelpers]
|
446
|
-
# instead +match+
|
504
|
+
# resources :users, param: :name
|
447
505
|
#
|
448
|
-
#
|
506
|
+
# The `users` resource here will have the following routes generated for it:
|
449
507
|
#
|
450
|
-
#
|
508
|
+
# GET /users(.:format)
|
509
|
+
# POST /users(.:format)
|
510
|
+
# GET /users/new(.:format)
|
511
|
+
# GET /users/:name/edit(.:format)
|
512
|
+
# GET /users/:name(.:format)
|
513
|
+
# PATCH/PUT /users/:name(.:format)
|
514
|
+
# DELETE /users/:name(.:format)
|
451
515
|
#
|
452
|
-
#
|
453
|
-
#
|
516
|
+
# You can override `ActiveRecord::Base#to_param` of a related model to
|
517
|
+
# construct a URL:
|
454
518
|
#
|
455
|
-
#
|
456
|
-
#
|
519
|
+
# class User < ActiveRecord::Base
|
520
|
+
# def to_param
|
521
|
+
# name
|
522
|
+
# end
|
523
|
+
# end
|
457
524
|
#
|
458
|
-
#
|
459
|
-
#
|
460
|
-
# dynamic segment used to generate the routes).
|
461
|
-
# You can access that segment from your controller using
|
462
|
-
# <tt>params[<:param>]</tt>.
|
525
|
+
# user = User.find_by(name: 'Phusion')
|
526
|
+
# user_path(user) # => "/users/Phusion"
|
463
527
|
#
|
464
|
-
#
|
465
|
-
# The path prefix for the routes.
|
528
|
+
# :path
|
529
|
+
# : The path prefix for the routes.
|
466
530
|
#
|
467
|
-
#
|
468
|
-
# The namespace for :controller.
|
531
|
+
# :module
|
532
|
+
# : The namespace for :controller.
|
469
533
|
#
|
470
|
-
#
|
471
|
-
#
|
534
|
+
# match 'path', to: 'c#a', module: 'sekret', controller: 'posts', via: :get
|
535
|
+
# # => Sekret::PostsController
|
472
536
|
#
|
473
|
-
#
|
537
|
+
# See `Scoping#namespace` for its scope equivalent.
|
474
538
|
#
|
475
|
-
#
|
476
|
-
# The name used to generate routing helpers.
|
539
|
+
# :as
|
540
|
+
# : The name used to generate routing helpers.
|
477
541
|
#
|
478
|
-
#
|
479
|
-
# Allowed HTTP verb(s) for route.
|
542
|
+
# :via
|
543
|
+
# : Allowed HTTP verb(s) for route.
|
480
544
|
#
|
481
|
-
#
|
482
|
-
#
|
483
|
-
#
|
545
|
+
# match 'path', to: 'c#a', via: :get
|
546
|
+
# match 'path', to: 'c#a', via: [:get, :post]
|
547
|
+
# match 'path', to: 'c#a', via: :all
|
484
548
|
#
|
485
|
-
#
|
486
|
-
# Points to a
|
487
|
-
#
|
549
|
+
# :to
|
550
|
+
# : Points to a `Rack` endpoint. Can be an object that responds to `call` or a
|
551
|
+
# string representing a controller's action.
|
488
552
|
#
|
489
|
-
#
|
490
|
-
#
|
491
|
-
#
|
553
|
+
# match 'path', to: 'controller#action', via: :get
|
554
|
+
# match 'path', to: -> (env) { [200, {}, ["Success!"]] }, via: :get
|
555
|
+
# match 'path', to: RackApp, via: :get
|
492
556
|
#
|
493
|
-
#
|
494
|
-
# Shorthand for wrapping routes in a specific RESTful context. Valid
|
495
|
-
#
|
496
|
-
#
|
557
|
+
# :on
|
558
|
+
# : Shorthand for wrapping routes in a specific RESTful context. Valid values
|
559
|
+
# are `:member`, `:collection`, and `:new`. Only use within `resource(s)`
|
560
|
+
# block. For example:
|
497
561
|
#
|
498
|
-
#
|
499
|
-
#
|
500
|
-
#
|
562
|
+
# resource :bar do
|
563
|
+
# match 'foo', to: 'c#a', on: :member, via: [:get, :post]
|
564
|
+
# end
|
501
565
|
#
|
502
|
-
#
|
566
|
+
# Is equivalent to:
|
503
567
|
#
|
504
|
-
#
|
505
|
-
#
|
506
|
-
#
|
507
|
-
#
|
508
|
-
#
|
568
|
+
# resource :bar do
|
569
|
+
# member do
|
570
|
+
# match 'foo', to: 'c#a', via: [:get, :post]
|
571
|
+
# end
|
572
|
+
# end
|
509
573
|
#
|
510
|
-
#
|
511
|
-
# Constrains parameters with a hash of regular expressions
|
512
|
-
#
|
513
|
-
#
|
514
|
-
#
|
574
|
+
# :constraints
|
575
|
+
# : Constrains parameters with a hash of regular expressions or an object that
|
576
|
+
# responds to `matches?`. In addition, constraints other than path can also
|
577
|
+
# be specified with any object that responds to `===` (e.g. String, Array,
|
578
|
+
# Range, etc.).
|
515
579
|
#
|
516
|
-
#
|
580
|
+
# match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via: :get
|
517
581
|
#
|
518
|
-
#
|
582
|
+
# match 'json_only', constraints: { format: 'json' }, via: :get
|
519
583
|
#
|
520
|
-
#
|
521
|
-
#
|
522
|
-
#
|
523
|
-
#
|
584
|
+
# class PermitList
|
585
|
+
# def matches?(request) request.remote_ip == '1.2.3.4' end
|
586
|
+
# end
|
587
|
+
# match 'path', to: 'c#a', constraints: PermitList.new, via: :get
|
524
588
|
#
|
525
|
-
#
|
526
|
-
# equivalent.
|
589
|
+
# See `Scoping#constraints` for more examples with its scope equivalent.
|
527
590
|
#
|
528
|
-
#
|
529
|
-
# Sets defaults for parameters
|
591
|
+
# :defaults
|
592
|
+
# : Sets defaults for parameters
|
530
593
|
#
|
531
|
-
#
|
532
|
-
#
|
594
|
+
# # Sets params[:format] to 'jpg' by default
|
595
|
+
# match 'path', to: 'c#a', defaults: { format: 'jpg' }, via: :get
|
533
596
|
#
|
534
|
-
#
|
597
|
+
# See `Scoping#defaults` for its scope equivalent.
|
535
598
|
#
|
536
|
-
#
|
537
|
-
# Boolean to anchor a
|
538
|
-
#
|
599
|
+
# :anchor
|
600
|
+
# : Boolean to anchor a `match` pattern. Default is true. When set to false,
|
601
|
+
# the pattern matches any request prefixed with the given path.
|
539
602
|
#
|
540
|
-
#
|
541
|
-
#
|
603
|
+
# # Matches any request starting with 'path'
|
604
|
+
# match 'path', to: 'c#a', anchor: false, via: :get
|
542
605
|
#
|
543
|
-
#
|
544
|
-
# Allows you to specify the default value for optional
|
545
|
-
#
|
546
|
-
|
606
|
+
# :format
|
607
|
+
# : Allows you to specify the default value for optional `format` segment or
|
608
|
+
# disable it by supplying `false`.
|
609
|
+
#
|
610
|
+
def match(path, options = nil)
|
547
611
|
end
|
548
612
|
|
549
613
|
# Mount a Rack-based application to be used within the application.
|
550
614
|
#
|
551
|
-
#
|
615
|
+
# mount SomeRackApp, at: "some_route"
|
552
616
|
#
|
553
617
|
# Alternatively:
|
554
618
|
#
|
555
|
-
#
|
619
|
+
# mount(SomeRackApp => "some_route")
|
556
620
|
#
|
557
|
-
# For options, see
|
621
|
+
# For options, see `match`, as `mount` uses it internally.
|
558
622
|
#
|
559
|
-
# All mounted applications come with routing helpers to access them.
|
560
|
-
#
|
561
|
-
#
|
562
|
-
#
|
623
|
+
# All mounted applications come with routing helpers to access them. These are
|
624
|
+
# named after the class specified, so for the above example the helper is either
|
625
|
+
# `some_rack_app_path` or `some_rack_app_url`. To customize this helper's name,
|
626
|
+
# use the `:as` option:
|
563
627
|
#
|
564
|
-
#
|
628
|
+
# mount(SomeRackApp => "some_route", as: "exciting")
|
565
629
|
#
|
566
|
-
# This will generate the
|
567
|
-
#
|
630
|
+
# This will generate the `exciting_path` and `exciting_url` helpers which can be
|
631
|
+
# used to navigate to this mounted app.
|
568
632
|
def mount(app, options = nil)
|
569
633
|
if options
|
570
634
|
path = options.delete(:at)
|
571
|
-
|
572
|
-
unless Hash === app
|
573
|
-
raise ArgumentError, "must be called with mount point"
|
574
|
-
end
|
575
|
-
|
635
|
+
elsif Hash === app
|
576
636
|
options = app
|
577
637
|
app, path = options.find { |k, _| k.respond_to?(:call) }
|
578
638
|
options.delete(app) if app
|
579
639
|
end
|
580
640
|
|
581
|
-
raise "A rack application must be specified" unless
|
641
|
+
raise ArgumentError, "A rack application must be specified" unless app.respond_to?(:call)
|
642
|
+
raise ArgumentError, <<~MSG unless path
|
643
|
+
Must be called with mount point
|
644
|
+
|
645
|
+
mount SomeRackApp, at: "some_route"
|
646
|
+
or
|
647
|
+
mount(SomeRackApp => "some_route")
|
648
|
+
MSG
|
582
649
|
|
583
650
|
rails_app = rails_app? app
|
584
651
|
options[:as] ||= app_name(app, rails_app)
|
@@ -586,7 +653,7 @@ module ActionDispatch
|
|
586
653
|
target_as = name_for_action(options[:as], path)
|
587
654
|
options[:via] ||= :all
|
588
655
|
|
589
|
-
match(path,
|
656
|
+
match(path, { to: app, anchor: false, format: false }.merge(options))
|
590
657
|
|
591
658
|
define_generate_prefix(app, target_as) if rails_app
|
592
659
|
self
|
@@ -605,7 +672,7 @@ module ActionDispatch
|
|
605
672
|
|
606
673
|
# Query if the following named route was already defined.
|
607
674
|
def has_named_route?(name)
|
608
|
-
@set.named_routes.
|
675
|
+
@set.named_routes.key?(name)
|
609
676
|
end
|
610
677
|
|
611
678
|
private
|
@@ -625,18 +692,32 @@ module ActionDispatch
|
|
625
692
|
def define_generate_prefix(app, name)
|
626
693
|
_route = @set.named_routes.get name
|
627
694
|
_routes = @set
|
628
|
-
|
695
|
+
_url_helpers = @set.url_helpers
|
696
|
+
|
697
|
+
script_namer = ->(options) do
|
698
|
+
prefix_options = options.slice(*_route.segment_keys)
|
699
|
+
prefix_options[:script_name] = "" if options[:original_script_name]
|
700
|
+
|
701
|
+
if options[:_recall]
|
702
|
+
prefix_options.reverse_merge!(options[:_recall].slice(*_route.segment_keys))
|
703
|
+
end
|
704
|
+
|
705
|
+
# We must actually delete prefix segment keys to avoid passing them to next
|
706
|
+
# url_for.
|
707
|
+
_route.segment_keys.each { |k| options.delete(k) }
|
708
|
+
_url_helpers.public_send("#{name}_path", prefix_options)
|
709
|
+
end
|
710
|
+
|
711
|
+
app.routes.define_mounted_helper(name, script_namer)
|
712
|
+
|
629
713
|
app.routes.extend Module.new {
|
630
714
|
def optimize_routes_generation?; false; end
|
715
|
+
|
631
716
|
define_method :find_script_name do |options|
|
632
|
-
if options.key? :script_name
|
717
|
+
if options.key?(:script_name) && options[:script_name].present?
|
633
718
|
super(options)
|
634
719
|
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)
|
720
|
+
script_namer.call(options)
|
640
721
|
end
|
641
722
|
end
|
642
723
|
}
|
@@ -644,46 +725,54 @@ module ActionDispatch
|
|
644
725
|
end
|
645
726
|
|
646
727
|
module HttpHelpers
|
647
|
-
# Define a route that only recognizes HTTP GET.
|
648
|
-
#
|
728
|
+
# Define a route that only recognizes HTTP GET. For supported arguments, see
|
729
|
+
# [match](rdoc-ref:Base#match)
|
649
730
|
#
|
650
|
-
#
|
731
|
+
# get 'bacon', to: 'food#bacon'
|
651
732
|
def get(*args, &block)
|
652
733
|
map_method(:get, args, &block)
|
653
734
|
end
|
654
735
|
|
655
|
-
# Define a route that only recognizes HTTP POST.
|
656
|
-
#
|
736
|
+
# Define a route that only recognizes HTTP POST. For supported arguments, see
|
737
|
+
# [match](rdoc-ref:Base#match)
|
657
738
|
#
|
658
|
-
#
|
739
|
+
# post 'bacon', to: 'food#bacon'
|
659
740
|
def post(*args, &block)
|
660
741
|
map_method(:post, args, &block)
|
661
742
|
end
|
662
743
|
|
663
|
-
# Define a route that only recognizes HTTP PATCH.
|
664
|
-
#
|
744
|
+
# Define a route that only recognizes HTTP PATCH. For supported arguments, see
|
745
|
+
# [match](rdoc-ref:Base#match)
|
665
746
|
#
|
666
|
-
#
|
747
|
+
# patch 'bacon', to: 'food#bacon'
|
667
748
|
def patch(*args, &block)
|
668
749
|
map_method(:patch, args, &block)
|
669
750
|
end
|
670
751
|
|
671
|
-
# Define a route that only recognizes HTTP PUT.
|
672
|
-
#
|
752
|
+
# Define a route that only recognizes HTTP PUT. For supported arguments, see
|
753
|
+
# [match](rdoc-ref:Base#match)
|
673
754
|
#
|
674
|
-
#
|
755
|
+
# put 'bacon', to: 'food#bacon'
|
675
756
|
def put(*args, &block)
|
676
757
|
map_method(:put, args, &block)
|
677
758
|
end
|
678
759
|
|
679
|
-
# Define a route that only recognizes HTTP DELETE.
|
680
|
-
#
|
760
|
+
# Define a route that only recognizes HTTP DELETE. For supported arguments, see
|
761
|
+
# [match](rdoc-ref:Base#match)
|
681
762
|
#
|
682
|
-
#
|
763
|
+
# delete 'broccoli', to: 'food#broccoli'
|
683
764
|
def delete(*args, &block)
|
684
765
|
map_method(:delete, args, &block)
|
685
766
|
end
|
686
767
|
|
768
|
+
# Define a route that only recognizes HTTP OPTIONS. For supported arguments, see
|
769
|
+
# [match](rdoc-ref:Base#match)
|
770
|
+
#
|
771
|
+
# options 'carrots', to: 'food#carrots'
|
772
|
+
def options(*args, &block)
|
773
|
+
map_method(:options, args, &block)
|
774
|
+
end
|
775
|
+
|
687
776
|
private
|
688
777
|
def map_method(method, args, &block)
|
689
778
|
options = args.extract_options!
|
@@ -693,96 +782,95 @@ module ActionDispatch
|
|
693
782
|
end
|
694
783
|
end
|
695
784
|
|
696
|
-
# You may wish to organize groups of controllers under a namespace.
|
697
|
-
#
|
698
|
-
#
|
699
|
-
#
|
700
|
-
#
|
785
|
+
# You may wish to organize groups of controllers under a namespace. Most
|
786
|
+
# commonly, you might group a number of administrative controllers under an
|
787
|
+
# `admin` namespace. You would place these controllers under the
|
788
|
+
# `app/controllers/admin` directory, and you can group them together in your
|
789
|
+
# router:
|
701
790
|
#
|
702
|
-
#
|
703
|
-
#
|
704
|
-
#
|
791
|
+
# namespace "admin" do
|
792
|
+
# resources :posts, :comments
|
793
|
+
# end
|
705
794
|
#
|
706
795
|
# This will create a number of routes for each of the posts and comments
|
707
|
-
# controller. For
|
796
|
+
# controller. For `Admin::PostsController`, Rails will create:
|
708
797
|
#
|
709
|
-
#
|
710
|
-
#
|
711
|
-
#
|
712
|
-
#
|
713
|
-
#
|
714
|
-
#
|
715
|
-
#
|
798
|
+
# GET /admin/posts
|
799
|
+
# GET /admin/posts/new
|
800
|
+
# POST /admin/posts
|
801
|
+
# GET /admin/posts/1
|
802
|
+
# GET /admin/posts/1/edit
|
803
|
+
# PATCH/PUT /admin/posts/1
|
804
|
+
# DELETE /admin/posts/1
|
716
805
|
#
|
717
806
|
# If you want to route /posts (without the prefix /admin) to
|
718
|
-
#
|
807
|
+
# `Admin::PostsController`, you could use
|
719
808
|
#
|
720
|
-
#
|
721
|
-
#
|
722
|
-
#
|
809
|
+
# scope module: "admin" do
|
810
|
+
# resources :posts
|
811
|
+
# end
|
723
812
|
#
|
724
813
|
# or, for a single case
|
725
814
|
#
|
726
|
-
#
|
815
|
+
# resources :posts, module: "admin"
|
727
816
|
#
|
728
|
-
# If you want to route /admin/posts to
|
729
|
-
#
|
817
|
+
# If you want to route /admin/posts to `PostsController` (without the `Admin::`
|
818
|
+
# module prefix), you could use
|
730
819
|
#
|
731
|
-
#
|
732
|
-
#
|
733
|
-
#
|
820
|
+
# scope "/admin" do
|
821
|
+
# resources :posts
|
822
|
+
# end
|
734
823
|
#
|
735
824
|
# or, for a single case
|
736
825
|
#
|
737
|
-
#
|
826
|
+
# resources :posts, path: "/admin/posts"
|
738
827
|
#
|
739
|
-
# In each of these cases, the named routes remain the same as if you did
|
740
|
-
#
|
741
|
-
# +PostsController+:
|
828
|
+
# In each of these cases, the named routes remain the same as if you did not use
|
829
|
+
# scope. In the last case, the following paths map to `PostsController`:
|
742
830
|
#
|
743
|
-
#
|
744
|
-
#
|
745
|
-
#
|
746
|
-
#
|
747
|
-
#
|
748
|
-
#
|
749
|
-
#
|
831
|
+
# GET /admin/posts
|
832
|
+
# GET /admin/posts/new
|
833
|
+
# POST /admin/posts
|
834
|
+
# GET /admin/posts/1
|
835
|
+
# GET /admin/posts/1/edit
|
836
|
+
# PATCH/PUT /admin/posts/1
|
837
|
+
# DELETE /admin/posts/1
|
750
838
|
module Scoping
|
751
839
|
# Scopes a set of routes to the given default options.
|
752
840
|
#
|
753
841
|
# Take the following route definition as an example:
|
754
842
|
#
|
755
|
-
#
|
756
|
-
#
|
757
|
-
#
|
843
|
+
# scope path: ":account_id", as: "account" do
|
844
|
+
# resources :projects
|
845
|
+
# end
|
758
846
|
#
|
759
|
-
# This generates helpers such as
|
760
|
-
# The difference here being that the routes generated are like
|
761
|
-
# rather than /accounts/:account_id/projects.
|
847
|
+
# This generates helpers such as `account_projects_path`, just like `resources`
|
848
|
+
# does. The difference here being that the routes generated are like
|
849
|
+
# /:account_id/projects, rather than /accounts/:account_id/projects.
|
762
850
|
#
|
763
|
-
#
|
851
|
+
# ### Options
|
764
852
|
#
|
765
|
-
# Takes same options as
|
853
|
+
# Takes same options as `Base#match` and `Resources#resources`.
|
766
854
|
#
|
767
|
-
#
|
768
|
-
#
|
769
|
-
#
|
770
|
-
#
|
855
|
+
# # route /posts (without the prefix /admin) to +Admin::PostsController+
|
856
|
+
# scope module: "admin" do
|
857
|
+
# resources :posts
|
858
|
+
# end
|
771
859
|
#
|
772
|
-
#
|
773
|
-
#
|
774
|
-
#
|
775
|
-
#
|
860
|
+
# # prefix the posts resource's requests with '/admin'
|
861
|
+
# scope path: "/admin" do
|
862
|
+
# resources :posts
|
863
|
+
# end
|
776
864
|
#
|
777
|
-
#
|
778
|
-
#
|
779
|
-
#
|
780
|
-
#
|
865
|
+
# # prefix the routing helper name: +sekret_posts_path+ instead of +posts_path+
|
866
|
+
# scope as: "sekret" do
|
867
|
+
# resources :posts
|
868
|
+
# end
|
781
869
|
def scope(*args)
|
782
870
|
options = args.extract_options!.dup
|
783
871
|
scope = {}
|
784
872
|
|
785
|
-
options[:path] = args.flatten.join(
|
873
|
+
options[:path] = args.flatten.join("/") if args.any?
|
786
874
|
options[:constraints] ||= {}
|
787
875
|
|
788
876
|
unless nested_scope?
|
@@ -795,21 +883,30 @@ module ActionDispatch
|
|
795
883
|
URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Integer))
|
796
884
|
end
|
797
885
|
|
798
|
-
(options[:defaults]
|
886
|
+
options[:defaults] = defaults.merge(options[:defaults] || {})
|
799
887
|
else
|
800
888
|
block, options[:constraints] = options[:constraints], {}
|
801
889
|
end
|
802
890
|
|
891
|
+
if options.key?(:only) || options.key?(:except)
|
892
|
+
scope[:action_options] = { only: options.delete(:only),
|
893
|
+
except: options.delete(:except) }
|
894
|
+
end
|
895
|
+
|
896
|
+
if options.key? :anchor
|
897
|
+
raise ArgumentError, "anchor is ignored unless passed to `match`"
|
898
|
+
end
|
899
|
+
|
803
900
|
@scope.options.each do |option|
|
804
901
|
if option == :blocks
|
805
902
|
value = block
|
806
903
|
elsif option == :options
|
807
904
|
value = options
|
808
905
|
else
|
809
|
-
value = options.delete(option)
|
906
|
+
value = options.delete(option) { POISON }
|
810
907
|
end
|
811
908
|
|
812
|
-
|
909
|
+
unless POISON == value
|
813
910
|
scope[option] = send("merge_#{option}_scope", @scope[option], value)
|
814
911
|
end
|
815
912
|
end
|
@@ -821,264 +918,299 @@ module ActionDispatch
|
|
821
918
|
@scope = @scope.parent
|
822
919
|
end
|
823
920
|
|
921
|
+
POISON = Object.new # :nodoc:
|
922
|
+
|
824
923
|
# Scopes routes to a specific controller
|
825
924
|
#
|
826
|
-
#
|
827
|
-
#
|
828
|
-
#
|
829
|
-
def controller(controller
|
830
|
-
|
831
|
-
|
925
|
+
# controller "food" do
|
926
|
+
# match "bacon", action: :bacon, via: :get
|
927
|
+
# end
|
928
|
+
def controller(controller)
|
929
|
+
@scope = @scope.new(controller: controller)
|
930
|
+
yield
|
931
|
+
ensure
|
932
|
+
@scope = @scope.parent
|
832
933
|
end
|
833
934
|
|
834
935
|
# Scopes routes to a specific namespace. For example:
|
835
936
|
#
|
836
|
-
#
|
837
|
-
#
|
838
|
-
#
|
937
|
+
# namespace :admin do
|
938
|
+
# resources :posts
|
939
|
+
# end
|
839
940
|
#
|
840
941
|
# This generates the following routes:
|
841
942
|
#
|
842
|
-
#
|
843
|
-
#
|
844
|
-
#
|
845
|
-
#
|
846
|
-
#
|
847
|
-
#
|
848
|
-
#
|
943
|
+
# admin_posts GET /admin/posts(.:format) admin/posts#index
|
944
|
+
# admin_posts POST /admin/posts(.:format) admin/posts#create
|
945
|
+
# new_admin_post GET /admin/posts/new(.:format) admin/posts#new
|
946
|
+
# edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
|
947
|
+
# admin_post GET /admin/posts/:id(.:format) admin/posts#show
|
948
|
+
# admin_post PATCH/PUT /admin/posts/:id(.:format) admin/posts#update
|
949
|
+
# admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
|
849
950
|
#
|
850
|
-
#
|
951
|
+
# ### Options
|
851
952
|
#
|
852
|
-
# The
|
853
|
-
#
|
953
|
+
# The `:path`, `:as`, `:module`, `:shallow_path`, and `:shallow_prefix` options
|
954
|
+
# all default to the name of the namespace.
|
854
955
|
#
|
855
|
-
# For options, see
|
856
|
-
#
|
956
|
+
# For options, see `Base#match`. For `:shallow_path` option, see
|
957
|
+
# `Resources#resources`.
|
857
958
|
#
|
858
|
-
#
|
859
|
-
#
|
860
|
-
#
|
861
|
-
#
|
959
|
+
# # accessible through /sekret/posts rather than /admin/posts
|
960
|
+
# namespace :admin, path: "sekret" do
|
961
|
+
# resources :posts
|
962
|
+
# end
|
862
963
|
#
|
863
|
-
#
|
864
|
-
#
|
865
|
-
#
|
866
|
-
#
|
964
|
+
# # maps to +Sekret::PostsController+ rather than +Admin::PostsController+
|
965
|
+
# namespace :admin, module: "sekret" do
|
966
|
+
# resources :posts
|
967
|
+
# end
|
867
968
|
#
|
868
|
-
#
|
869
|
-
#
|
870
|
-
#
|
871
|
-
#
|
872
|
-
def namespace(path, options = {})
|
969
|
+
# # generates +sekret_posts_path+ rather than +admin_posts_path+
|
970
|
+
# namespace :admin, as: "sekret" do
|
971
|
+
# resources :posts
|
972
|
+
# end
|
973
|
+
def namespace(path, options = {}, &block)
|
873
974
|
path = path.to_s
|
874
975
|
|
875
976
|
defaults = {
|
876
977
|
module: path,
|
877
|
-
path: options.fetch(:path, path),
|
878
978
|
as: options.fetch(:as, path),
|
879
979
|
shallow_path: options.fetch(:path, path),
|
880
980
|
shallow_prefix: options.fetch(:as, path)
|
881
981
|
}
|
882
982
|
|
883
|
-
|
983
|
+
path_scope(options.delete(:path) { path }) do
|
984
|
+
scope(defaults.merge!(options), &block)
|
985
|
+
end
|
884
986
|
end
|
885
987
|
|
886
|
-
#
|
887
|
-
# Allows you to constrain the nested routes based on a set of rules.
|
888
|
-
#
|
988
|
+
# ### Parameter Restriction
|
989
|
+
# Allows you to constrain the nested routes based on a set of rules. For
|
990
|
+
# instance, in order to change the routes to allow for a dot character in the
|
991
|
+
# `id` parameter:
|
889
992
|
#
|
890
|
-
#
|
891
|
-
#
|
892
|
-
#
|
993
|
+
# constraints(id: /\d+\.\d+/) do
|
994
|
+
# resources :posts
|
995
|
+
# end
|
893
996
|
#
|
894
|
-
# Now routes such as
|
895
|
-
# The
|
997
|
+
# Now routes such as `/posts/1` will no longer be valid, but `/posts/1.1` will
|
998
|
+
# be. The `id` parameter must match the constraint passed in for this example.
|
896
999
|
#
|
897
1000
|
# You may use this to also restrict other parameters:
|
898
1001
|
#
|
899
|
-
#
|
900
|
-
#
|
901
|
-
#
|
1002
|
+
# resources :posts do
|
1003
|
+
# constraints(post_id: /\d+\.\d+/) do
|
1004
|
+
# resources :comments
|
1005
|
+
# end
|
902
1006
|
# end
|
903
|
-
# end
|
904
1007
|
#
|
905
|
-
#
|
1008
|
+
# ### Restricting based on IP
|
906
1009
|
#
|
907
1010
|
# Routes can also be constrained to an IP or a certain range of IP addresses:
|
908
1011
|
#
|
909
|
-
#
|
910
|
-
#
|
911
|
-
#
|
1012
|
+
# constraints(ip: /192\.168\.\d+\.\d+/) do
|
1013
|
+
# resources :posts
|
1014
|
+
# end
|
912
1015
|
#
|
913
|
-
# Any user connecting from the 192.168.* range will be able to see this
|
914
|
-
# where as any user connecting outside of this range will be told
|
1016
|
+
# Any user connecting from the 192.168.* range will be able to see this
|
1017
|
+
# resource, where as any user connecting outside of this range will be told
|
1018
|
+
# there is no such route.
|
915
1019
|
#
|
916
|
-
#
|
1020
|
+
# ### Dynamic request matching
|
917
1021
|
#
|
918
1022
|
# Requests to routes can be constrained based on specific criteria:
|
919
1023
|
#
|
920
|
-
#
|
921
|
-
#
|
922
|
-
#
|
1024
|
+
# constraints(-> (req) { /iPhone/.match?(req.env["HTTP_USER_AGENT"]) }) do
|
1025
|
+
# resources :iphones
|
1026
|
+
# end
|
923
1027
|
#
|
924
|
-
# You are able to move this logic out into a class if it is too complex for
|
925
|
-
# This class must have a
|
926
|
-
# if the user should be given access to that route, or
|
1028
|
+
# You are able to move this logic out into a class if it is too complex for
|
1029
|
+
# routes. This class must have a `matches?` method defined on it which either
|
1030
|
+
# returns `true` if the user should be given access to that route, or `false` if
|
1031
|
+
# the user should not.
|
927
1032
|
#
|
928
|
-
#
|
929
|
-
#
|
930
|
-
#
|
931
|
-
#
|
932
|
-
#
|
1033
|
+
# class Iphone
|
1034
|
+
# def self.matches?(request)
|
1035
|
+
# /iPhone/.match?(request.env["HTTP_USER_AGENT"])
|
1036
|
+
# end
|
1037
|
+
# end
|
933
1038
|
#
|
934
|
-
# An expected place for this code would be
|
1039
|
+
# An expected place for this code would be `lib/constraints`.
|
935
1040
|
#
|
936
1041
|
# This class is then used like this:
|
937
1042
|
#
|
938
|
-
#
|
939
|
-
#
|
940
|
-
#
|
941
|
-
def constraints(constraints = {})
|
942
|
-
scope(:constraints
|
1043
|
+
# constraints(Iphone) do
|
1044
|
+
# resources :iphones
|
1045
|
+
# end
|
1046
|
+
def constraints(constraints = {}, &block)
|
1047
|
+
scope(constraints: constraints, &block)
|
943
1048
|
end
|
944
1049
|
|
945
1050
|
# Allows you to set default parameters for a route, such as this:
|
946
|
-
#
|
947
|
-
#
|
948
|
-
#
|
949
|
-
#
|
1051
|
+
# defaults id: 'home' do
|
1052
|
+
# match 'scoped_pages/(:id)', to: 'pages#show'
|
1053
|
+
# end
|
1054
|
+
#
|
1055
|
+
# Using this, the `:id` parameter here will default to 'home'.
|
950
1056
|
def defaults(defaults = {})
|
951
|
-
scope(:defaults
|
1057
|
+
@scope = @scope.new(defaults: merge_defaults_scope(@scope[:defaults], defaults))
|
1058
|
+
yield
|
1059
|
+
ensure
|
1060
|
+
@scope = @scope.parent
|
952
1061
|
end
|
953
1062
|
|
954
1063
|
private
|
955
|
-
def merge_path_scope(parent, child)
|
1064
|
+
def merge_path_scope(parent, child)
|
956
1065
|
Mapper.normalize_path("#{parent}/#{child}")
|
957
1066
|
end
|
958
1067
|
|
959
|
-
def merge_shallow_path_scope(parent, child)
|
1068
|
+
def merge_shallow_path_scope(parent, child)
|
960
1069
|
Mapper.normalize_path("#{parent}/#{child}")
|
961
1070
|
end
|
962
1071
|
|
963
|
-
def merge_as_scope(parent, child)
|
1072
|
+
def merge_as_scope(parent, child)
|
964
1073
|
parent ? "#{parent}_#{child}" : child
|
965
1074
|
end
|
966
1075
|
|
967
|
-
def merge_shallow_prefix_scope(parent, child)
|
1076
|
+
def merge_shallow_prefix_scope(parent, child)
|
968
1077
|
parent ? "#{parent}_#{child}" : child
|
969
1078
|
end
|
970
1079
|
|
971
|
-
def merge_module_scope(parent, child)
|
1080
|
+
def merge_module_scope(parent, child)
|
972
1081
|
parent ? "#{parent}/#{child}" : child
|
973
1082
|
end
|
974
1083
|
|
975
|
-
def merge_controller_scope(parent, child)
|
1084
|
+
def merge_controller_scope(parent, child)
|
1085
|
+
child
|
1086
|
+
end
|
1087
|
+
|
1088
|
+
def merge_action_scope(parent, child)
|
1089
|
+
child
|
1090
|
+
end
|
1091
|
+
|
1092
|
+
def merge_via_scope(parent, child)
|
976
1093
|
child
|
977
1094
|
end
|
978
1095
|
|
979
|
-
def
|
1096
|
+
def merge_format_scope(parent, child)
|
980
1097
|
child
|
981
1098
|
end
|
982
1099
|
|
983
|
-
def merge_path_names_scope(parent, child)
|
1100
|
+
def merge_path_names_scope(parent, child)
|
984
1101
|
merge_options_scope(parent, child)
|
985
1102
|
end
|
986
1103
|
|
987
|
-
def merge_constraints_scope(parent, child)
|
1104
|
+
def merge_constraints_scope(parent, child)
|
988
1105
|
merge_options_scope(parent, child)
|
989
1106
|
end
|
990
1107
|
|
991
|
-
def merge_defaults_scope(parent, child)
|
1108
|
+
def merge_defaults_scope(parent, child)
|
992
1109
|
merge_options_scope(parent, child)
|
993
1110
|
end
|
994
1111
|
|
995
|
-
def merge_blocks_scope(parent, child)
|
1112
|
+
def merge_blocks_scope(parent, child)
|
996
1113
|
merged = parent ? parent.dup : []
|
997
1114
|
merged << child if child
|
998
1115
|
merged
|
999
1116
|
end
|
1000
1117
|
|
1001
|
-
def merge_options_scope(parent, child)
|
1002
|
-
(parent || {}).
|
1118
|
+
def merge_options_scope(parent, child)
|
1119
|
+
(parent || {}).merge(child)
|
1003
1120
|
end
|
1004
1121
|
|
1005
|
-
def merge_shallow_scope(parent, child)
|
1122
|
+
def merge_shallow_scope(parent, child)
|
1006
1123
|
child ? true : false
|
1007
1124
|
end
|
1008
1125
|
|
1009
|
-
def
|
1010
|
-
child
|
1126
|
+
def merge_to_scope(parent, child)
|
1127
|
+
child
|
1011
1128
|
end
|
1012
1129
|
end
|
1013
1130
|
|
1014
|
-
# Resource routing allows you to quickly declare all of the common routes
|
1015
|
-
#
|
1016
|
-
#
|
1017
|
-
#
|
1131
|
+
# Resource routing allows you to quickly declare all of the common routes for a
|
1132
|
+
# given resourceful controller. Instead of declaring separate routes for your
|
1133
|
+
# `index`, `show`, `new`, `edit`, `create`, `update`, and `destroy` actions, a
|
1134
|
+
# resourceful route declares them in a single line of code:
|
1018
1135
|
#
|
1019
|
-
#
|
1136
|
+
# resources :photos
|
1020
1137
|
#
|
1021
|
-
# Sometimes, you have a resource that clients always look up without
|
1022
|
-
#
|
1023
|
-
#
|
1024
|
-
#
|
1138
|
+
# Sometimes, you have a resource that clients always look up without referencing
|
1139
|
+
# an ID. A common example, /profile always shows the profile of the currently
|
1140
|
+
# logged in user. In this case, you can use a singular resource to map /profile
|
1141
|
+
# (rather than /profile/:id) to the show action.
|
1025
1142
|
#
|
1026
|
-
#
|
1143
|
+
# resource :profile
|
1027
1144
|
#
|
1028
|
-
# It's common to have resources that are logically children of other
|
1029
|
-
# resources:
|
1145
|
+
# It's common to have resources that are logically children of other resources:
|
1030
1146
|
#
|
1031
|
-
#
|
1032
|
-
#
|
1033
|
-
#
|
1147
|
+
# resources :magazines do
|
1148
|
+
# resources :ads
|
1149
|
+
# end
|
1034
1150
|
#
|
1035
1151
|
# You may wish to organize groups of controllers under a namespace. Most
|
1036
|
-
# commonly, you might group a number of administrative controllers under
|
1037
|
-
#
|
1038
|
-
#
|
1039
|
-
#
|
1152
|
+
# commonly, you might group a number of administrative controllers under an
|
1153
|
+
# `admin` namespace. You would place these controllers under the
|
1154
|
+
# `app/controllers/admin` directory, and you can group them together in your
|
1155
|
+
# router:
|
1040
1156
|
#
|
1041
|
-
#
|
1042
|
-
#
|
1043
|
-
#
|
1157
|
+
# namespace "admin" do
|
1158
|
+
# resources :posts, :comments
|
1159
|
+
# end
|
1044
1160
|
#
|
1045
|
-
# By default the
|
1046
|
-
#
|
1047
|
-
#
|
1161
|
+
# By default the `:id` parameter doesn't accept dots. If you need to use dots as
|
1162
|
+
# part of the `:id` parameter add a constraint which overrides this restriction,
|
1163
|
+
# e.g:
|
1048
1164
|
#
|
1049
|
-
#
|
1050
|
-
#
|
1051
|
-
# This allows any character other than a slash as part of your +:id+.
|
1165
|
+
# resources :articles, id: /[^\/]+/
|
1052
1166
|
#
|
1167
|
+
# This allows any character other than a slash as part of your `:id`.
|
1053
1168
|
module Resources
|
1054
|
-
# CANONICAL_ACTIONS holds all actions that does not need a prefix or
|
1055
|
-
#
|
1169
|
+
# CANONICAL_ACTIONS holds all actions that does not need a prefix or a path
|
1170
|
+
# appended since they fit properly in their scope level.
|
1056
1171
|
VALID_ON_OPTIONS = [:new, :collection, :member]
|
1057
1172
|
RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns]
|
1058
1173
|
CANONICAL_ACTIONS = %w(index create new show update destroy)
|
1059
1174
|
|
1060
|
-
class Resource
|
1061
|
-
attr_reader :controller, :path, :
|
1175
|
+
class Resource # :nodoc:
|
1176
|
+
attr_reader :controller, :path, :param
|
1177
|
+
|
1178
|
+
def initialize(entities, api_only, shallow, options = {})
|
1179
|
+
if options[:param].to_s.include?(":")
|
1180
|
+
raise ArgumentError, ":param option can't contain colons"
|
1181
|
+
end
|
1062
1182
|
|
1063
|
-
def initialize(entities, options = {})
|
1064
1183
|
@name = entities.to_s
|
1065
1184
|
@path = (options[:path] || @name).to_s
|
1066
1185
|
@controller = (options[:controller] || @name).to_s
|
1067
1186
|
@as = options[:as]
|
1068
1187
|
@param = (options[:param] || :id).to_sym
|
1069
1188
|
@options = options
|
1070
|
-
@shallow =
|
1189
|
+
@shallow = shallow
|
1190
|
+
@api_only = api_only
|
1191
|
+
@only = options.delete :only
|
1192
|
+
@except = options.delete :except
|
1071
1193
|
end
|
1072
1194
|
|
1073
1195
|
def default_actions
|
1074
|
-
|
1196
|
+
if @api_only
|
1197
|
+
[:index, :create, :show, :update, :destroy]
|
1198
|
+
else
|
1199
|
+
[:index, :create, :new, :show, :update, :destroy, :edit]
|
1200
|
+
end
|
1075
1201
|
end
|
1076
1202
|
|
1077
1203
|
def actions
|
1078
|
-
if
|
1079
|
-
Array(
|
1080
|
-
|
1081
|
-
|
1204
|
+
if @except
|
1205
|
+
available_actions - Array(@except).map(&:to_sym)
|
1206
|
+
else
|
1207
|
+
available_actions
|
1208
|
+
end
|
1209
|
+
end
|
1210
|
+
|
1211
|
+
def available_actions
|
1212
|
+
if @only
|
1213
|
+
Array(@only).map(&:to_sym)
|
1082
1214
|
else
|
1083
1215
|
default_actions
|
1084
1216
|
end
|
@@ -1098,14 +1230,14 @@ module ActionDispatch
|
|
1098
1230
|
|
1099
1231
|
alias :member_name :singular
|
1100
1232
|
|
1101
|
-
# Checks for uncountable plurals, and appends "_index" if the plural
|
1102
|
-
#
|
1233
|
+
# Checks for uncountable plurals, and appends "_index" if the plural and
|
1234
|
+
# singular form are the same.
|
1103
1235
|
def collection_name
|
1104
1236
|
singular == plural ? "#{plural}_index" : plural
|
1105
1237
|
end
|
1106
1238
|
|
1107
1239
|
def resource_scope
|
1108
|
-
|
1240
|
+
controller
|
1109
1241
|
end
|
1110
1242
|
|
1111
1243
|
alias :collection_scope :path
|
@@ -1128,17 +1260,15 @@ module ActionDispatch
|
|
1128
1260
|
"#{path}/:#{nested_param}"
|
1129
1261
|
end
|
1130
1262
|
|
1131
|
-
def shallow=(value)
|
1132
|
-
@shallow = value
|
1133
|
-
end
|
1134
|
-
|
1135
1263
|
def shallow?
|
1136
1264
|
@shallow
|
1137
1265
|
end
|
1266
|
+
|
1267
|
+
def singleton?; false; end
|
1138
1268
|
end
|
1139
1269
|
|
1140
|
-
class SingletonResource < Resource
|
1141
|
-
def initialize(entities, options)
|
1270
|
+
class SingletonResource < Resource # :nodoc:
|
1271
|
+
def initialize(entities, api_only, shallow, options)
|
1142
1272
|
super
|
1143
1273
|
@as = nil
|
1144
1274
|
@controller = (options[:controller] || plural).to_s
|
@@ -1146,7 +1276,11 @@ module ActionDispatch
|
|
1146
1276
|
end
|
1147
1277
|
|
1148
1278
|
def default_actions
|
1149
|
-
|
1279
|
+
if @api_only
|
1280
|
+
[:show, :create, :update, :destroy]
|
1281
|
+
else
|
1282
|
+
[:show, :create, :update, :destroy, :new, :edit]
|
1283
|
+
end
|
1150
1284
|
end
|
1151
1285
|
|
1152
1286
|
def plural
|
@@ -1162,33 +1296,43 @@ module ActionDispatch
|
|
1162
1296
|
|
1163
1297
|
alias :member_scope :path
|
1164
1298
|
alias :nested_scope :path
|
1299
|
+
|
1300
|
+
def singleton?; true; end
|
1165
1301
|
end
|
1166
1302
|
|
1167
1303
|
def resources_path_names(options)
|
1168
1304
|
@scope[:path_names].merge!(options)
|
1169
1305
|
end
|
1170
1306
|
|
1171
|
-
# Sometimes, you have a resource that clients always look up without
|
1172
|
-
#
|
1173
|
-
#
|
1174
|
-
#
|
1175
|
-
#
|
1307
|
+
# Sometimes, you have a resource that clients always look up without referencing
|
1308
|
+
# an ID. A common example, /profile always shows the profile of the currently
|
1309
|
+
# logged in user. In this case, you can use a singular resource to map /profile
|
1310
|
+
# (rather than /profile/:id) to the show action:
|
1311
|
+
#
|
1312
|
+
# resource :profile
|
1313
|
+
#
|
1314
|
+
# This creates six different routes in your application, all mapping to the
|
1315
|
+
# `Profiles` controller (note that the controller is named after the plural):
|
1316
|
+
#
|
1317
|
+
# GET /profile/new
|
1318
|
+
# GET /profile
|
1319
|
+
# GET /profile/edit
|
1320
|
+
# PATCH/PUT /profile
|
1321
|
+
# DELETE /profile
|
1322
|
+
# POST /profile
|
1176
1323
|
#
|
1177
|
-
#
|
1324
|
+
# If you want instances of a model to work with this resource via record
|
1325
|
+
# identification (e.g. in `form_with` or `redirect_to`), you will need to call
|
1326
|
+
# [resolve](rdoc-ref:CustomUrls#resolve):
|
1178
1327
|
#
|
1179
|
-
#
|
1180
|
-
#
|
1181
|
-
# the plural):
|
1328
|
+
# resource :profile
|
1329
|
+
# resolve('Profile') { [:profile] }
|
1182
1330
|
#
|
1183
|
-
#
|
1184
|
-
#
|
1185
|
-
# GET /profile
|
1186
|
-
# GET /profile/edit
|
1187
|
-
# PATCH/PUT /profile
|
1188
|
-
# DELETE /profile
|
1331
|
+
# # Enables this to work with singular routes:
|
1332
|
+
# form_with(model: @profile) {}
|
1189
1333
|
#
|
1190
|
-
#
|
1191
|
-
# Takes same options as
|
1334
|
+
# ### Options
|
1335
|
+
# Takes same options as [resources](rdoc-ref:#resources)
|
1192
1336
|
def resource(*resources, &block)
|
1193
1337
|
options = resources.extract_options!.dup
|
1194
1338
|
|
@@ -1196,157 +1340,169 @@ module ActionDispatch
|
|
1196
1340
|
return self
|
1197
1341
|
end
|
1198
1342
|
|
1199
|
-
|
1200
|
-
|
1343
|
+
with_scope_level(:resource) do
|
1344
|
+
options = apply_action_options options
|
1345
|
+
resource_scope(SingletonResource.new(resources.pop, api_only?, @scope[:shallow], options)) do
|
1346
|
+
yield if block_given?
|
1201
1347
|
|
1202
|
-
|
1348
|
+
concerns(options[:concerns]) if options[:concerns]
|
1203
1349
|
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1350
|
+
new do
|
1351
|
+
get :new
|
1352
|
+
end if parent_resource.actions.include?(:new)
|
1207
1353
|
|
1208
|
-
|
1209
|
-
get :new
|
1210
|
-
end if parent_resource.actions.include?(:new)
|
1354
|
+
set_member_mappings_for_resource
|
1211
1355
|
|
1212
|
-
|
1356
|
+
collection do
|
1357
|
+
post :create
|
1358
|
+
end if parent_resource.actions.include?(:create)
|
1359
|
+
end
|
1213
1360
|
end
|
1214
1361
|
|
1215
1362
|
self
|
1216
1363
|
end
|
1217
1364
|
|
1218
|
-
# In Rails, a resourceful route provides a mapping between HTTP verbs
|
1219
|
-
# and
|
1220
|
-
#
|
1221
|
-
# routing file, such as
|
1365
|
+
# In Rails, a resourceful route provides a mapping between HTTP verbs and URLs
|
1366
|
+
# and controller actions. By convention, each action also maps to particular
|
1367
|
+
# CRUD operations in a database. A single entry in the routing file, such as
|
1222
1368
|
#
|
1223
|
-
#
|
1369
|
+
# resources :photos
|
1224
1370
|
#
|
1225
|
-
# creates seven different routes in your application, all mapping to
|
1226
|
-
#
|
1371
|
+
# creates seven different routes in your application, all mapping to the
|
1372
|
+
# `Photos` controller:
|
1227
1373
|
#
|
1228
|
-
#
|
1229
|
-
#
|
1230
|
-
#
|
1231
|
-
#
|
1232
|
-
#
|
1233
|
-
#
|
1234
|
-
#
|
1374
|
+
# GET /photos
|
1375
|
+
# GET /photos/new
|
1376
|
+
# POST /photos
|
1377
|
+
# GET /photos/:id
|
1378
|
+
# GET /photos/:id/edit
|
1379
|
+
# PATCH/PUT /photos/:id
|
1380
|
+
# DELETE /photos/:id
|
1235
1381
|
#
|
1236
1382
|
# Resources can also be nested infinitely by using this block syntax:
|
1237
1383
|
#
|
1238
|
-
#
|
1239
|
-
#
|
1240
|
-
#
|
1384
|
+
# resources :photos do
|
1385
|
+
# resources :comments
|
1386
|
+
# end
|
1241
1387
|
#
|
1242
1388
|
# This generates the following comments routes:
|
1243
1389
|
#
|
1244
|
-
#
|
1245
|
-
#
|
1246
|
-
#
|
1247
|
-
#
|
1248
|
-
#
|
1249
|
-
#
|
1250
|
-
#
|
1390
|
+
# GET /photos/:photo_id/comments
|
1391
|
+
# GET /photos/:photo_id/comments/new
|
1392
|
+
# POST /photos/:photo_id/comments
|
1393
|
+
# GET /photos/:photo_id/comments/:id
|
1394
|
+
# GET /photos/:photo_id/comments/:id/edit
|
1395
|
+
# PATCH/PUT /photos/:photo_id/comments/:id
|
1396
|
+
# DELETE /photos/:photo_id/comments/:id
|
1251
1397
|
#
|
1252
|
-
#
|
1253
|
-
# Takes same options as
|
1398
|
+
# ### Options
|
1399
|
+
# Takes same options as [match](rdoc-ref:Base#match) as well as:
|
1254
1400
|
#
|
1255
|
-
#
|
1256
|
-
# Allows you to change the segment component of the
|
1257
|
-
#
|
1401
|
+
# :path_names
|
1402
|
+
# : Allows you to change the segment component of the `edit` and `new`
|
1403
|
+
# actions. Actions not specified are not changed.
|
1258
1404
|
#
|
1259
|
-
#
|
1405
|
+
# resources :posts, path_names: { new: "brand_new" }
|
1260
1406
|
#
|
1261
|
-
#
|
1407
|
+
# The above example will now change /posts/new to /posts/brand_new.
|
1262
1408
|
#
|
1263
|
-
#
|
1264
|
-
# Allows you to change the path prefix for the resource.
|
1409
|
+
# :path
|
1410
|
+
# : Allows you to change the path prefix for the resource.
|
1265
1411
|
#
|
1266
|
-
#
|
1412
|
+
# resources :posts, path: 'postings'
|
1267
1413
|
#
|
1268
|
-
#
|
1414
|
+
# The resource and all segments will now route to /postings instead of
|
1415
|
+
# /posts.
|
1269
1416
|
#
|
1270
|
-
#
|
1271
|
-
# Only generate routes for the given actions.
|
1417
|
+
# :only
|
1418
|
+
# : Only generate routes for the given actions.
|
1272
1419
|
#
|
1273
|
-
#
|
1274
|
-
#
|
1420
|
+
# resources :cows, only: :show
|
1421
|
+
# resources :cows, only: [:show, :index]
|
1275
1422
|
#
|
1276
|
-
#
|
1277
|
-
# Generate all routes except for the given actions.
|
1423
|
+
# :except
|
1424
|
+
# : Generate all routes except for the given actions.
|
1278
1425
|
#
|
1279
|
-
#
|
1280
|
-
#
|
1426
|
+
# resources :cows, except: :show
|
1427
|
+
# resources :cows, except: [:show, :index]
|
1281
1428
|
#
|
1282
|
-
#
|
1283
|
-
# Generates shallow routes for nested resource(s). When placed on a parent
|
1284
|
-
#
|
1429
|
+
# :shallow
|
1430
|
+
# : Generates shallow routes for nested resource(s). When placed on a parent
|
1431
|
+
# resource, generates shallow routes for all nested resources.
|
1285
1432
|
#
|
1286
|
-
#
|
1287
|
-
#
|
1288
|
-
#
|
1433
|
+
# resources :posts, shallow: true do
|
1434
|
+
# resources :comments
|
1435
|
+
# end
|
1289
1436
|
#
|
1290
|
-
#
|
1437
|
+
# Is the same as:
|
1291
1438
|
#
|
1292
|
-
#
|
1293
|
-
#
|
1294
|
-
#
|
1295
|
-
#
|
1439
|
+
# resources :posts do
|
1440
|
+
# resources :comments, except: [:show, :edit, :update, :destroy]
|
1441
|
+
# end
|
1442
|
+
# resources :comments, only: [:show, :edit, :update, :destroy]
|
1296
1443
|
#
|
1297
|
-
#
|
1298
|
-
#
|
1299
|
-
#
|
1444
|
+
# This allows URLs for resources that otherwise would be deeply nested such
|
1445
|
+
# as a comment on a blog post like `/posts/a-long-permalink/comments/1234`
|
1446
|
+
# to be shortened to just `/comments/1234`.
|
1300
1447
|
#
|
1301
|
-
#
|
1302
|
-
#
|
1448
|
+
# Set `shallow: false` on a child resource to ignore a parent's shallow
|
1449
|
+
# parameter.
|
1303
1450
|
#
|
1304
|
-
#
|
1305
|
-
#
|
1306
|
-
# resources :comments, shallow: true
|
1307
|
-
# end
|
1308
|
-
# end
|
1451
|
+
# :shallow_path
|
1452
|
+
# : Prefixes nested shallow routes with the specified path.
|
1309
1453
|
#
|
1310
|
-
#
|
1454
|
+
# scope shallow_path: "sekret" do
|
1455
|
+
# resources :posts do
|
1456
|
+
# resources :comments, shallow: true
|
1457
|
+
# end
|
1458
|
+
# end
|
1311
1459
|
#
|
1312
|
-
#
|
1313
|
-
#
|
1314
|
-
# new_post_comment GET /posts/:post_id/comments/new(.:format)
|
1315
|
-
# edit_comment GET /sekret/comments/:id/edit(.:format)
|
1316
|
-
# comment GET /sekret/comments/:id(.:format)
|
1317
|
-
# comment PATCH/PUT /sekret/comments/:id(.:format)
|
1318
|
-
# comment DELETE /sekret/comments/:id(.:format)
|
1460
|
+
# The `comments` resource here will have the following routes generated for
|
1461
|
+
# it:
|
1319
1462
|
#
|
1320
|
-
#
|
1321
|
-
#
|
1463
|
+
# post_comments GET /posts/:post_id/comments(.:format)
|
1464
|
+
# post_comments POST /posts/:post_id/comments(.:format)
|
1465
|
+
# new_post_comment GET /posts/:post_id/comments/new(.:format)
|
1466
|
+
# edit_comment GET /sekret/comments/:id/edit(.:format)
|
1467
|
+
# comment GET /sekret/comments/:id(.:format)
|
1468
|
+
# comment PATCH/PUT /sekret/comments/:id(.:format)
|
1469
|
+
# comment DELETE /sekret/comments/:id(.:format)
|
1322
1470
|
#
|
1323
|
-
#
|
1324
|
-
#
|
1325
|
-
# resources :comments, shallow: true
|
1326
|
-
# end
|
1327
|
-
# end
|
1471
|
+
# :shallow_prefix
|
1472
|
+
# : Prefixes nested shallow route names with specified prefix.
|
1328
1473
|
#
|
1329
|
-
#
|
1474
|
+
# scope shallow_prefix: "sekret" do
|
1475
|
+
# resources :posts do
|
1476
|
+
# resources :comments, shallow: true
|
1477
|
+
# end
|
1478
|
+
# end
|
1330
1479
|
#
|
1331
|
-
#
|
1332
|
-
#
|
1333
|
-
# new_post_comment GET /posts/:post_id/comments/new(.:format)
|
1334
|
-
# edit_sekret_comment GET /comments/:id/edit(.:format)
|
1335
|
-
# sekret_comment GET /comments/:id(.:format)
|
1336
|
-
# sekret_comment PATCH/PUT /comments/:id(.:format)
|
1337
|
-
# sekret_comment DELETE /comments/:id(.:format)
|
1480
|
+
# The `comments` resource here will have the following routes generated for
|
1481
|
+
# it:
|
1338
1482
|
#
|
1339
|
-
#
|
1340
|
-
#
|
1341
|
-
#
|
1483
|
+
# post_comments GET /posts/:post_id/comments(.:format)
|
1484
|
+
# post_comments POST /posts/:post_id/comments(.:format)
|
1485
|
+
# new_post_comment GET /posts/:post_id/comments/new(.:format)
|
1486
|
+
# edit_sekret_comment GET /comments/:id/edit(.:format)
|
1487
|
+
# sekret_comment GET /comments/:id(.:format)
|
1488
|
+
# sekret_comment PATCH/PUT /comments/:id(.:format)
|
1489
|
+
# sekret_comment DELETE /comments/:id(.:format)
|
1342
1490
|
#
|
1343
|
-
#
|
1491
|
+
# :format
|
1492
|
+
# : Allows you to specify the default value for optional `format` segment or
|
1493
|
+
# disable it by supplying `false`.
|
1344
1494
|
#
|
1345
|
-
#
|
1346
|
-
#
|
1495
|
+
# :param
|
1496
|
+
# : Allows you to override the default param name of `:id` in the URL.
|
1347
1497
|
#
|
1348
|
-
#
|
1349
|
-
#
|
1498
|
+
#
|
1499
|
+
# ### Examples
|
1500
|
+
#
|
1501
|
+
# # routes call +Admin::PostsController+
|
1502
|
+
# resources :posts, module: "admin"
|
1503
|
+
#
|
1504
|
+
# # resource actions are at /admin/posts.
|
1505
|
+
# resources :posts, path: "admin/posts"
|
1350
1506
|
def resources(*resources, &block)
|
1351
1507
|
options = resources.extract_options!.dup
|
1352
1508
|
|
@@ -1354,21 +1510,24 @@ module ActionDispatch
|
|
1354
1510
|
return self
|
1355
1511
|
end
|
1356
1512
|
|
1357
|
-
|
1358
|
-
|
1513
|
+
with_scope_level(:resources) do
|
1514
|
+
options = apply_action_options options
|
1515
|
+
resource_scope(Resource.new(resources.pop, api_only?, @scope[:shallow], options)) do
|
1516
|
+
yield if block_given?
|
1359
1517
|
|
1360
|
-
|
1518
|
+
concerns(options[:concerns]) if options[:concerns]
|
1361
1519
|
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1365
|
-
|
1520
|
+
collection do
|
1521
|
+
get :index if parent_resource.actions.include?(:index)
|
1522
|
+
post :create if parent_resource.actions.include?(:create)
|
1523
|
+
end
|
1366
1524
|
|
1367
|
-
|
1368
|
-
|
1369
|
-
|
1525
|
+
new do
|
1526
|
+
get :new
|
1527
|
+
end if parent_resource.actions.include?(:new)
|
1370
1528
|
|
1371
|
-
|
1529
|
+
set_member_mappings_for_resource
|
1530
|
+
end
|
1372
1531
|
end
|
1373
1532
|
|
1374
1533
|
self
|
@@ -1376,80 +1535,83 @@ module ActionDispatch
|
|
1376
1535
|
|
1377
1536
|
# To add a route to the collection:
|
1378
1537
|
#
|
1379
|
-
#
|
1380
|
-
#
|
1381
|
-
#
|
1538
|
+
# resources :photos do
|
1539
|
+
# collection do
|
1540
|
+
# get 'search'
|
1541
|
+
# end
|
1382
1542
|
# end
|
1383
|
-
# end
|
1384
1543
|
#
|
1385
|
-
# This will enable Rails to recognize paths such as
|
1386
|
-
#
|
1387
|
-
#
|
1388
|
-
|
1389
|
-
def collection
|
1544
|
+
# This will enable Rails to recognize paths such as `/photos/search` with GET,
|
1545
|
+
# and route to the search action of `PhotosController`. It will also create the
|
1546
|
+
# `search_photos_url` and `search_photos_path` route helpers.
|
1547
|
+
def collection(&block)
|
1390
1548
|
unless resource_scope?
|
1391
1549
|
raise ArgumentError, "can't use collection outside resource(s) scope"
|
1392
1550
|
end
|
1393
1551
|
|
1394
1552
|
with_scope_level(:collection) do
|
1395
|
-
|
1396
|
-
yield
|
1397
|
-
end
|
1553
|
+
path_scope(parent_resource.collection_scope, &block)
|
1398
1554
|
end
|
1399
1555
|
end
|
1400
1556
|
|
1401
1557
|
# To add a member route, add a member block into the resource block:
|
1402
1558
|
#
|
1403
|
-
#
|
1404
|
-
#
|
1405
|
-
#
|
1559
|
+
# resources :photos do
|
1560
|
+
# member do
|
1561
|
+
# get 'preview'
|
1562
|
+
# end
|
1406
1563
|
# end
|
1407
|
-
# end
|
1408
1564
|
#
|
1409
|
-
# This will recognize
|
1410
|
-
#
|
1411
|
-
#
|
1412
|
-
def member
|
1565
|
+
# This will recognize `/photos/1/preview` with GET, and route to the preview
|
1566
|
+
# action of `PhotosController`. It will also create the `preview_photo_url` and
|
1567
|
+
# `preview_photo_path` helpers.
|
1568
|
+
def member(&block)
|
1413
1569
|
unless resource_scope?
|
1414
1570
|
raise ArgumentError, "can't use member outside resource(s) scope"
|
1415
1571
|
end
|
1416
1572
|
|
1417
1573
|
with_scope_level(:member) do
|
1418
1574
|
if shallow?
|
1419
|
-
shallow_scope
|
1575
|
+
shallow_scope {
|
1576
|
+
path_scope(parent_resource.member_scope, &block)
|
1577
|
+
}
|
1420
1578
|
else
|
1421
|
-
|
1579
|
+
path_scope(parent_resource.member_scope, &block)
|
1422
1580
|
end
|
1423
1581
|
end
|
1424
1582
|
end
|
1425
1583
|
|
1426
|
-
def new
|
1584
|
+
def new(&block)
|
1427
1585
|
unless resource_scope?
|
1428
1586
|
raise ArgumentError, "can't use new outside resource(s) scope"
|
1429
1587
|
end
|
1430
1588
|
|
1431
1589
|
with_scope_level(:new) do
|
1432
|
-
|
1433
|
-
yield
|
1434
|
-
end
|
1590
|
+
path_scope(parent_resource.new_scope(action_path(:new)), &block)
|
1435
1591
|
end
|
1436
1592
|
end
|
1437
1593
|
|
1438
|
-
def nested
|
1594
|
+
def nested(&block)
|
1439
1595
|
unless resource_scope?
|
1440
1596
|
raise ArgumentError, "can't use nested outside resource(s) scope"
|
1441
1597
|
end
|
1442
1598
|
|
1443
1599
|
with_scope_level(:nested) do
|
1444
1600
|
if shallow? && shallow_nesting_depth >= 1
|
1445
|
-
shallow_scope
|
1601
|
+
shallow_scope do
|
1602
|
+
path_scope(parent_resource.nested_scope) do
|
1603
|
+
scope(nested_options, &block)
|
1604
|
+
end
|
1605
|
+
end
|
1446
1606
|
else
|
1447
|
-
|
1607
|
+
path_scope(parent_resource.nested_scope) do
|
1608
|
+
scope(nested_options, &block)
|
1609
|
+
end
|
1448
1610
|
end
|
1449
1611
|
end
|
1450
1612
|
end
|
1451
1613
|
|
1452
|
-
# See ActionDispatch::Routing::Mapper::Scoping#namespace
|
1614
|
+
# See ActionDispatch::Routing::Mapper::Scoping#namespace.
|
1453
1615
|
def namespace(path, options = {})
|
1454
1616
|
if resource_scope?
|
1455
1617
|
nested { super }
|
@@ -1459,28 +1621,72 @@ module ActionDispatch
|
|
1459
1621
|
end
|
1460
1622
|
|
1461
1623
|
def shallow
|
1462
|
-
scope(:
|
1463
|
-
|
1464
|
-
|
1624
|
+
@scope = @scope.new(shallow: true)
|
1625
|
+
yield
|
1626
|
+
ensure
|
1627
|
+
@scope = @scope.parent
|
1465
1628
|
end
|
1466
1629
|
|
1467
1630
|
def shallow?
|
1468
|
-
parent_resource.
|
1631
|
+
!parent_resource.singleton? && @scope[:shallow]
|
1632
|
+
end
|
1633
|
+
|
1634
|
+
# Loads another routes file with the given `name` located inside the
|
1635
|
+
# `config/routes` directory. In that file, you can use the normal routing DSL,
|
1636
|
+
# but *do not* surround it with a `Rails.application.routes.draw` block.
|
1637
|
+
#
|
1638
|
+
# # config/routes.rb
|
1639
|
+
# Rails.application.routes.draw do
|
1640
|
+
# draw :admin # Loads `config/routes/admin.rb`
|
1641
|
+
# draw "third_party/some_gem" # Loads `config/routes/third_party/some_gem.rb`
|
1642
|
+
# end
|
1643
|
+
#
|
1644
|
+
# # config/routes/admin.rb
|
1645
|
+
# namespace :admin do
|
1646
|
+
# resources :accounts
|
1647
|
+
# end
|
1648
|
+
#
|
1649
|
+
# # config/routes/third_party/some_gem.rb
|
1650
|
+
# mount SomeGem::Engine, at: "/some_gem"
|
1651
|
+
#
|
1652
|
+
# **CAUTION:** Use this feature with care. Having multiple routes files can
|
1653
|
+
# negatively impact discoverability and readability. For most applications —
|
1654
|
+
# even those with a few hundred routes — it's easier for developers to have a
|
1655
|
+
# single routes file.
|
1656
|
+
def draw(name)
|
1657
|
+
path = @draw_paths.find do |_path|
|
1658
|
+
File.exist? "#{_path}/#{name}.rb"
|
1659
|
+
end
|
1660
|
+
|
1661
|
+
unless path
|
1662
|
+
msg = "Your router tried to #draw the external file #{name}.rb,\n" \
|
1663
|
+
"but the file was not found in:\n\n"
|
1664
|
+
msg += @draw_paths.map { |_path| " * #{_path}" }.join("\n")
|
1665
|
+
raise ArgumentError, msg
|
1666
|
+
end
|
1667
|
+
|
1668
|
+
route_path = "#{path}/#{name}.rb"
|
1669
|
+
instance_eval(File.read(route_path), route_path.to_s)
|
1469
1670
|
end
|
1470
1671
|
|
1471
|
-
#
|
1472
|
-
# match
|
1473
|
-
#
|
1474
|
-
|
1672
|
+
# Matches a URL pattern to one or more routes. For more information, see
|
1673
|
+
# [match](rdoc-ref:Base#match).
|
1674
|
+
#
|
1675
|
+
# match 'path' => 'controller#action', via: :patch
|
1676
|
+
# match 'path', to: 'controller#action', via: :post
|
1677
|
+
# match 'path', 'otherpath', on: :member, via: :get
|
1678
|
+
def match(path, *rest, &block)
|
1475
1679
|
if rest.empty? && Hash === path
|
1476
1680
|
options = path
|
1477
1681
|
path, to = options.find { |name, _value| name.is_a?(String) }
|
1478
1682
|
|
1683
|
+
raise ArgumentError, "Route path not specified" if path.nil?
|
1684
|
+
|
1479
1685
|
case to
|
1480
1686
|
when Symbol
|
1481
1687
|
options[:action] = to
|
1482
1688
|
when String
|
1483
|
-
if to
|
1689
|
+
if to.include?("#")
|
1484
1690
|
options[:to] = to
|
1485
1691
|
else
|
1486
1692
|
options[:controller] = to
|
@@ -1496,77 +1702,30 @@ module ActionDispatch
|
|
1496
1702
|
paths = [path] + rest
|
1497
1703
|
end
|
1498
1704
|
|
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?("/")
|
1705
|
+
if options.key?(:defaults)
|
1706
|
+
defaults(options.delete(:defaults)) { map_match(paths, options, &block) }
|
1551
1707
|
else
|
1552
|
-
|
1708
|
+
map_match(paths, options, &block)
|
1553
1709
|
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
1710
|
end
|
1565
1711
|
|
1566
|
-
|
1712
|
+
# You can specify what Rails should route "/" to with the root method:
|
1713
|
+
#
|
1714
|
+
# root to: 'pages#main'
|
1715
|
+
#
|
1716
|
+
# For options, see `match`, as `root` uses it internally.
|
1717
|
+
#
|
1718
|
+
# You can also pass a string which will expand
|
1719
|
+
#
|
1720
|
+
# root 'pages#main'
|
1721
|
+
#
|
1722
|
+
# You should put the root route at the top of `config/routes.rb`, because this
|
1723
|
+
# means it will be matched first. As this is the most popular route of most
|
1724
|
+
# Rails applications, this is beneficial.
|
1725
|
+
def root(path, options = {})
|
1567
1726
|
if path.is_a?(String)
|
1568
1727
|
options[:to] = path
|
1569
|
-
elsif path.is_a?(Hash)
|
1728
|
+
elsif path.is_a?(Hash) && options.empty?
|
1570
1729
|
options = path
|
1571
1730
|
else
|
1572
1731
|
raise ArgumentError, "must be called with a path and/or options"
|
@@ -1574,36 +1733,36 @@ module ActionDispatch
|
|
1574
1733
|
|
1575
1734
|
if @scope.resources?
|
1576
1735
|
with_scope_level(:root) do
|
1577
|
-
|
1578
|
-
|
1736
|
+
path_scope(parent_resource.path) do
|
1737
|
+
match_root_route(options)
|
1579
1738
|
end
|
1580
1739
|
end
|
1581
1740
|
else
|
1582
|
-
|
1741
|
+
match_root_route(options)
|
1583
1742
|
end
|
1584
1743
|
end
|
1585
1744
|
|
1586
|
-
|
1587
|
-
|
1588
|
-
def parent_resource #:nodoc:
|
1745
|
+
private
|
1746
|
+
def parent_resource
|
1589
1747
|
@scope[:scope_level_resource]
|
1590
1748
|
end
|
1591
1749
|
|
1592
|
-
def apply_common_behavior_for(method, resources, options, &block)
|
1750
|
+
def apply_common_behavior_for(method, resources, options, &block)
|
1593
1751
|
if resources.length > 1
|
1594
|
-
resources.each { |r|
|
1752
|
+
resources.each { |r| public_send(method, r, options, &block) }
|
1595
1753
|
return true
|
1596
1754
|
end
|
1597
1755
|
|
1598
|
-
if options
|
1756
|
+
if options[:shallow]
|
1757
|
+
options.delete(:shallow)
|
1599
1758
|
shallow do
|
1600
|
-
|
1759
|
+
public_send(method, resources.pop, options, &block)
|
1601
1760
|
end
|
1602
1761
|
return true
|
1603
1762
|
end
|
1604
1763
|
|
1605
1764
|
if resource_scope?
|
1606
|
-
nested {
|
1765
|
+
nested { public_send(method, resources.pop, options, &block) }
|
1607
1766
|
return true
|
1608
1767
|
end
|
1609
1768
|
|
@@ -1614,76 +1773,56 @@ module ActionDispatch
|
|
1614
1773
|
scope_options = options.slice!(*RESOURCE_OPTIONS)
|
1615
1774
|
unless scope_options.empty?
|
1616
1775
|
scope(scope_options) do
|
1617
|
-
|
1776
|
+
public_send(method, resources.pop, options, &block)
|
1618
1777
|
end
|
1619
1778
|
return true
|
1620
1779
|
end
|
1621
1780
|
|
1622
|
-
unless action_options?(options)
|
1623
|
-
options.merge!(scope_action_options) if scope_action_options?
|
1624
|
-
end
|
1625
|
-
|
1626
1781
|
false
|
1627
1782
|
end
|
1628
1783
|
|
1629
|
-
def
|
1630
|
-
options
|
1784
|
+
def apply_action_options(options)
|
1785
|
+
return options if action_options? options
|
1786
|
+
options.merge scope_action_options
|
1631
1787
|
end
|
1632
1788
|
|
1633
|
-
def
|
1634
|
-
|
1789
|
+
def action_options?(options)
|
1790
|
+
options[:only] || options[:except]
|
1635
1791
|
end
|
1636
1792
|
|
1637
|
-
def scope_action_options
|
1638
|
-
@scope[:
|
1793
|
+
def scope_action_options
|
1794
|
+
@scope[:action_options] || {}
|
1639
1795
|
end
|
1640
1796
|
|
1641
|
-
def resource_scope?
|
1797
|
+
def resource_scope?
|
1642
1798
|
@scope.resource_scope?
|
1643
1799
|
end
|
1644
1800
|
|
1645
|
-
def resource_method_scope?
|
1801
|
+
def resource_method_scope?
|
1646
1802
|
@scope.resource_method_scope?
|
1647
1803
|
end
|
1648
1804
|
|
1649
|
-
def nested_scope?
|
1805
|
+
def nested_scope?
|
1650
1806
|
@scope.nested?
|
1651
1807
|
end
|
1652
1808
|
|
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)
|
1809
|
+
def with_scope_level(kind) # :doc:
|
1666
1810
|
@scope = @scope.new_level(kind)
|
1667
1811
|
yield
|
1668
1812
|
ensure
|
1669
1813
|
@scope = @scope.parent
|
1670
1814
|
end
|
1671
1815
|
|
1672
|
-
def resource_scope(
|
1673
|
-
|
1674
|
-
@scope = @scope.new(:scope_level_resource => resource)
|
1675
|
-
@nesting.push(resource)
|
1816
|
+
def resource_scope(resource, &block)
|
1817
|
+
@scope = @scope.new(scope_level_resource: resource)
|
1676
1818
|
|
1677
|
-
|
1678
|
-
scope(parent_resource.resource_scope) { yield }
|
1679
|
-
end
|
1819
|
+
controller(resource.resource_scope, &block)
|
1680
1820
|
ensure
|
1681
|
-
@nesting.pop
|
1682
1821
|
@scope = @scope.parent
|
1683
1822
|
end
|
1684
1823
|
|
1685
|
-
def nested_options
|
1686
|
-
options = { :
|
1824
|
+
def nested_options
|
1825
|
+
options = { as: parent_resource.member_name }
|
1687
1826
|
options[:constraints] = {
|
1688
1827
|
parent_resource.nested_param => param_constraint
|
1689
1828
|
} if param_constraint?
|
@@ -1691,62 +1830,61 @@ module ActionDispatch
|
|
1691
1830
|
options
|
1692
1831
|
end
|
1693
1832
|
|
1694
|
-
def
|
1695
|
-
@
|
1696
|
-
|
1697
|
-
|
1698
|
-
def shallow_nesting_depth #:nodoc:
|
1699
|
-
@nesting.select(&:shallow?).size
|
1833
|
+
def shallow_nesting_depth
|
1834
|
+
@scope.find_all { |node|
|
1835
|
+
node.frame[:scope_level_resource]
|
1836
|
+
}.count { |node| node.frame[:scope_level_resource].shallow? }
|
1700
1837
|
end
|
1701
1838
|
|
1702
|
-
def param_constraint?
|
1839
|
+
def param_constraint?
|
1703
1840
|
@scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp)
|
1704
1841
|
end
|
1705
1842
|
|
1706
|
-
def param_constraint
|
1843
|
+
def param_constraint
|
1707
1844
|
@scope[:constraints][parent_resource.param]
|
1708
1845
|
end
|
1709
1846
|
|
1710
|
-
def canonical_action?(action)
|
1847
|
+
def canonical_action?(action)
|
1711
1848
|
resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
|
1712
1849
|
end
|
1713
1850
|
|
1714
|
-
def shallow_scope
|
1715
|
-
scope = { :
|
1716
|
-
:
|
1851
|
+
def shallow_scope
|
1852
|
+
scope = { as: @scope[:shallow_prefix],
|
1853
|
+
path: @scope[:shallow_path] }
|
1717
1854
|
@scope = @scope.new scope
|
1718
1855
|
|
1719
|
-
|
1856
|
+
yield
|
1720
1857
|
ensure
|
1721
1858
|
@scope = @scope.parent
|
1722
1859
|
end
|
1723
1860
|
|
1724
|
-
def path_for_action(action, path)
|
1725
|
-
|
1861
|
+
def path_for_action(action, path)
|
1862
|
+
return "#{@scope[:path]}/#{path}" if path
|
1863
|
+
|
1864
|
+
if canonical_action?(action)
|
1726
1865
|
@scope[:path].to_s
|
1727
1866
|
else
|
1728
|
-
"#{@scope[:path]}/#{action_path(action
|
1867
|
+
"#{@scope[:path]}/#{action_path(action)}"
|
1729
1868
|
end
|
1730
1869
|
end
|
1731
1870
|
|
1732
|
-
def action_path(name
|
1733
|
-
name
|
1734
|
-
path || @scope[:path_names][name] || name.to_s
|
1871
|
+
def action_path(name)
|
1872
|
+
@scope[:path_names][name.to_sym] || name
|
1735
1873
|
end
|
1736
1874
|
|
1737
|
-
def prefix_name_for_action(as, action)
|
1875
|
+
def prefix_name_for_action(as, action)
|
1738
1876
|
if as
|
1739
1877
|
prefix = as
|
1740
1878
|
elsif !canonical_action?(action)
|
1741
1879
|
prefix = action
|
1742
1880
|
end
|
1743
1881
|
|
1744
|
-
if prefix && prefix !=
|
1745
|
-
Mapper.normalize_name prefix.to_s.tr(
|
1882
|
+
if prefix && prefix != "/" && !prefix.empty?
|
1883
|
+
Mapper.normalize_name prefix.to_s.tr("-", "_")
|
1746
1884
|
end
|
1747
1885
|
end
|
1748
1886
|
|
1749
|
-
def name_for_action(as, action)
|
1887
|
+
def name_for_action(as, action)
|
1750
1888
|
prefix = prefix_name_for_action(as, action)
|
1751
1889
|
name_prefix = @scope[:as]
|
1752
1890
|
|
@@ -1757,21 +1895,22 @@ module ActionDispatch
|
|
1757
1895
|
member_name = parent_resource.member_name
|
1758
1896
|
end
|
1759
1897
|
|
1760
|
-
|
1898
|
+
action_name = @scope.action_name(name_prefix, prefix, collection_name, member_name)
|
1899
|
+
candidate = action_name.select(&:present?).join("_")
|
1761
1900
|
|
1762
|
-
|
1763
|
-
# If a name was not explicitly given, we check if it is valid
|
1764
|
-
#
|
1765
|
-
#
|
1901
|
+
unless candidate.empty?
|
1902
|
+
# If a name was not explicitly given, we check if it is valid and return nil in
|
1903
|
+
# case it isn't. Otherwise, we pass the invalid name forward so the underlying
|
1904
|
+
# router engine treats it and raises an exception.
|
1766
1905
|
if as.nil?
|
1767
|
-
candidate unless candidate
|
1906
|
+
candidate unless !candidate.match?(/\A[_a-z]/i) || has_named_route?(candidate)
|
1768
1907
|
else
|
1769
1908
|
candidate
|
1770
1909
|
end
|
1771
1910
|
end
|
1772
1911
|
end
|
1773
1912
|
|
1774
|
-
def set_member_mappings_for_resource
|
1913
|
+
def set_member_mappings_for_resource # :doc:
|
1775
1914
|
member do
|
1776
1915
|
get :edit if parent_resource.actions.include?(:edit)
|
1777
1916
|
get :show if parent_resource.actions.include?(:show)
|
@@ -1782,85 +1921,197 @@ module ActionDispatch
|
|
1782
1921
|
delete :destroy if parent_resource.actions.include?(:destroy)
|
1783
1922
|
end
|
1784
1923
|
end
|
1924
|
+
|
1925
|
+
def api_only? # :doc:
|
1926
|
+
@set.api_only?
|
1927
|
+
end
|
1928
|
+
|
1929
|
+
def path_scope(path)
|
1930
|
+
@scope = @scope.new(path: merge_path_scope(@scope[:path], path))
|
1931
|
+
yield
|
1932
|
+
ensure
|
1933
|
+
@scope = @scope.parent
|
1934
|
+
end
|
1935
|
+
|
1936
|
+
def map_match(paths, options)
|
1937
|
+
if (on = options[:on]) && !VALID_ON_OPTIONS.include?(on)
|
1938
|
+
raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
|
1939
|
+
end
|
1940
|
+
|
1941
|
+
if @scope[:to]
|
1942
|
+
options[:to] ||= @scope[:to]
|
1943
|
+
end
|
1944
|
+
|
1945
|
+
if @scope[:controller] && @scope[:action]
|
1946
|
+
options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
|
1947
|
+
end
|
1948
|
+
|
1949
|
+
controller = options.delete(:controller) || @scope[:controller]
|
1950
|
+
option_path = options.delete :path
|
1951
|
+
to = options.delete :to
|
1952
|
+
via = Mapping.check_via Array(options.delete(:via) {
|
1953
|
+
@scope[:via]
|
1954
|
+
})
|
1955
|
+
formatted = options.delete(:format) { @scope[:format] }
|
1956
|
+
anchor = options.delete(:anchor) { true }
|
1957
|
+
options_constraints = options.delete(:constraints) || {}
|
1958
|
+
|
1959
|
+
path_types = paths.group_by(&:class)
|
1960
|
+
(path_types[String] || []).each do |_path|
|
1961
|
+
route_options = options.dup
|
1962
|
+
if _path && option_path
|
1963
|
+
raise ArgumentError, "Ambiguous route definition. Both :path and the route path were specified as strings."
|
1964
|
+
end
|
1965
|
+
to = get_to_from_path(_path, to, route_options[:action])
|
1966
|
+
decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints)
|
1967
|
+
end
|
1968
|
+
|
1969
|
+
(path_types[Symbol] || []).each do |action|
|
1970
|
+
route_options = options.dup
|
1971
|
+
decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints)
|
1972
|
+
end
|
1973
|
+
|
1974
|
+
self
|
1975
|
+
end
|
1976
|
+
|
1977
|
+
def get_to_from_path(path, to, action)
|
1978
|
+
return to if to || action
|
1979
|
+
|
1980
|
+
path_without_format = path.sub(/\(\.:format\)$/, "")
|
1981
|
+
if using_match_shorthand?(path_without_format)
|
1982
|
+
path_without_format.delete_prefix("/").sub(%r{/([^/]*)$}, '#\1').tr("-", "_")
|
1983
|
+
else
|
1984
|
+
nil
|
1985
|
+
end
|
1986
|
+
end
|
1987
|
+
|
1988
|
+
def using_match_shorthand?(path)
|
1989
|
+
%r{^/?[-\w]+/[-\w/]+$}.match?(path)
|
1990
|
+
end
|
1991
|
+
|
1992
|
+
def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
|
1993
|
+
if on = options.delete(:on)
|
1994
|
+
send(on) { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
|
1995
|
+
else
|
1996
|
+
case @scope.scope_level
|
1997
|
+
when :resources
|
1998
|
+
nested { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
|
1999
|
+
when :resource
|
2000
|
+
member { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
|
2001
|
+
else
|
2002
|
+
add_route(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
|
2003
|
+
end
|
2004
|
+
end
|
2005
|
+
end
|
2006
|
+
|
2007
|
+
def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints)
|
2008
|
+
path = path_for_action(action, _path)
|
2009
|
+
raise ArgumentError, "path is required" if path.blank?
|
2010
|
+
|
2011
|
+
action = action.to_s
|
2012
|
+
|
2013
|
+
default_action = options.delete(:action) || @scope[:action]
|
2014
|
+
|
2015
|
+
if /^[\w\-\/]+$/.match?(action)
|
2016
|
+
default_action ||= action.tr("-", "_") unless action.include?("/")
|
2017
|
+
else
|
2018
|
+
action = nil
|
2019
|
+
end
|
2020
|
+
|
2021
|
+
as = if !options.fetch(:as, true) # if it's set to nil or false
|
2022
|
+
options.delete(:as)
|
2023
|
+
else
|
2024
|
+
name_for_action(options.delete(:as), action)
|
2025
|
+
end
|
2026
|
+
|
2027
|
+
path = Mapping.normalize_path URI::DEFAULT_PARSER.escape(path), formatted
|
2028
|
+
ast = Journey::Parser.parse path
|
2029
|
+
|
2030
|
+
mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
|
2031
|
+
@set.add_route(mapping, as)
|
2032
|
+
end
|
2033
|
+
|
2034
|
+
def match_root_route(options)
|
2035
|
+
args = ["/", { as: :root, via: :get }.merge(options)]
|
2036
|
+
match(*args)
|
2037
|
+
end
|
1785
2038
|
end
|
1786
2039
|
|
1787
|
-
# Routing Concerns allow you to declare common routes that can be reused
|
1788
|
-
#
|
2040
|
+
# Routing Concerns allow you to declare common routes that can be reused inside
|
2041
|
+
# others resources and routes.
|
1789
2042
|
#
|
1790
|
-
#
|
1791
|
-
#
|
1792
|
-
#
|
2043
|
+
# concern :commentable do
|
2044
|
+
# resources :comments
|
2045
|
+
# end
|
1793
2046
|
#
|
1794
|
-
#
|
1795
|
-
#
|
1796
|
-
#
|
2047
|
+
# concern :image_attachable do
|
2048
|
+
# resources :images, only: :index
|
2049
|
+
# end
|
1797
2050
|
#
|
1798
2051
|
# These concerns are used in Resources routing:
|
1799
2052
|
#
|
1800
|
-
#
|
2053
|
+
# resources :messages, concerns: [:commentable, :image_attachable]
|
1801
2054
|
#
|
1802
2055
|
# or in a scope or namespace:
|
1803
2056
|
#
|
1804
|
-
#
|
1805
|
-
#
|
1806
|
-
#
|
2057
|
+
# namespace :posts do
|
2058
|
+
# concerns :commentable
|
2059
|
+
# end
|
1807
2060
|
module Concerns
|
1808
2061
|
# Define a routing concern using a name.
|
1809
2062
|
#
|
1810
|
-
# Concerns may be defined inline, using a block, or handled by
|
1811
|
-
#
|
2063
|
+
# Concerns may be defined inline, using a block, or handled by another object,
|
2064
|
+
# by passing that object as the second parameter.
|
1812
2065
|
#
|
1813
|
-
# The concern object, if supplied, should respond to
|
1814
|
-
#
|
2066
|
+
# The concern object, if supplied, should respond to `call`, which will receive
|
2067
|
+
# two parameters:
|
1815
2068
|
#
|
1816
|
-
#
|
1817
|
-
#
|
2069
|
+
# * The current mapper
|
2070
|
+
# * A hash of options which the concern object may use
|
1818
2071
|
#
|
1819
|
-
# Options may also be used by concerns defined in a block by accepting
|
1820
|
-
#
|
1821
|
-
#
|
1822
|
-
#
|
2072
|
+
# Options may also be used by concerns defined in a block by accepting a block
|
2073
|
+
# parameter. So, using a block, you might do something as simple as limit the
|
2074
|
+
# actions available on certain resources, passing standard resource options
|
2075
|
+
# through the concern:
|
1823
2076
|
#
|
1824
|
-
#
|
1825
|
-
#
|
1826
|
-
#
|
2077
|
+
# concern :commentable do |options|
|
2078
|
+
# resources :comments, options
|
2079
|
+
# end
|
1827
2080
|
#
|
1828
|
-
#
|
1829
|
-
#
|
1830
|
-
#
|
1831
|
-
#
|
1832
|
-
#
|
2081
|
+
# resources :posts, concerns: :commentable
|
2082
|
+
# resources :archived_posts do
|
2083
|
+
# # Don't allow comments on archived posts
|
2084
|
+
# concerns :commentable, only: [:index, :show]
|
2085
|
+
# end
|
1833
2086
|
#
|
1834
|
-
# Or, using a callable object, you might implement something more
|
1835
|
-
#
|
1836
|
-
# routes file.
|
2087
|
+
# Or, using a callable object, you might implement something more specific to
|
2088
|
+
# your application, which would be out of place in your routes file.
|
1837
2089
|
#
|
1838
|
-
#
|
1839
|
-
#
|
1840
|
-
#
|
1841
|
-
#
|
1842
|
-
#
|
2090
|
+
# # purchasable.rb
|
2091
|
+
# class Purchasable
|
2092
|
+
# def initialize(defaults = {})
|
2093
|
+
# @defaults = defaults
|
2094
|
+
# end
|
1843
2095
|
#
|
1844
|
-
#
|
1845
|
-
#
|
1846
|
-
#
|
1847
|
-
#
|
1848
|
-
#
|
2096
|
+
# def call(mapper, options = {})
|
2097
|
+
# options = @defaults.merge(options)
|
2098
|
+
# mapper.resources :purchases
|
2099
|
+
# mapper.resources :receipts
|
2100
|
+
# mapper.resources :returns if options[:returnable]
|
2101
|
+
# end
|
1849
2102
|
# end
|
1850
|
-
# end
|
1851
2103
|
#
|
1852
|
-
#
|
1853
|
-
#
|
2104
|
+
# # routes.rb
|
2105
|
+
# concern :purchasable, Purchasable.new(returnable: true)
|
1854
2106
|
#
|
1855
|
-
#
|
1856
|
-
#
|
1857
|
-
#
|
1858
|
-
#
|
1859
|
-
#
|
2107
|
+
# resources :toys, concerns: :purchasable
|
2108
|
+
# resources :electronics, concerns: :purchasable
|
2109
|
+
# resources :pets do
|
2110
|
+
# concerns :purchasable, returnable: false
|
2111
|
+
# end
|
1860
2112
|
#
|
1861
|
-
# Any routing helpers can be used inside a concern. If using a
|
1862
|
-
#
|
1863
|
-
# <tt>call</tt>.
|
2113
|
+
# Any routing helpers can be used inside a concern. If using a callable, they're
|
2114
|
+
# accessible from the Mapper that's passed to `call`.
|
1864
2115
|
def concern(name, callable = nil, &block)
|
1865
2116
|
callable ||= lambda { |mapper, options| mapper.instance_exec(options, &block) }
|
1866
2117
|
@concerns[name] = callable
|
@@ -1868,15 +2119,15 @@ module ActionDispatch
|
|
1868
2119
|
|
1869
2120
|
# Use the named concerns
|
1870
2121
|
#
|
1871
|
-
#
|
1872
|
-
#
|
1873
|
-
#
|
2122
|
+
# resources :posts do
|
2123
|
+
# concerns :commentable
|
2124
|
+
# end
|
1874
2125
|
#
|
1875
|
-
#
|
2126
|
+
# Concerns also work in any routes helper that you want to use:
|
1876
2127
|
#
|
1877
|
-
#
|
1878
|
-
#
|
1879
|
-
#
|
2128
|
+
# namespace :posts do
|
2129
|
+
# concerns :commentable
|
2130
|
+
# end
|
1880
2131
|
def concerns(*args)
|
1881
2132
|
options = args.extract_options!
|
1882
2133
|
args.flatten.each do |name|
|
@@ -1889,17 +2140,133 @@ module ActionDispatch
|
|
1889
2140
|
end
|
1890
2141
|
end
|
1891
2142
|
|
2143
|
+
module CustomUrls
|
2144
|
+
# Define custom URL helpers that will be added to the application's routes. This
|
2145
|
+
# allows you to override and/or replace the default behavior of routing helpers,
|
2146
|
+
# e.g:
|
2147
|
+
#
|
2148
|
+
# direct :homepage do
|
2149
|
+
# "https://rubyonrails.org"
|
2150
|
+
# end
|
2151
|
+
#
|
2152
|
+
# direct :commentable do |model|
|
2153
|
+
# [ model, anchor: model.dom_id ]
|
2154
|
+
# end
|
2155
|
+
#
|
2156
|
+
# direct :main do
|
2157
|
+
# { controller: "pages", action: "index", subdomain: "www" }
|
2158
|
+
# end
|
2159
|
+
#
|
2160
|
+
# The return value from the block passed to `direct` must be a valid set of
|
2161
|
+
# arguments for `url_for` which will actually build the URL string. This can be
|
2162
|
+
# one of the following:
|
2163
|
+
#
|
2164
|
+
# * A string, which is treated as a generated URL
|
2165
|
+
# * A hash, e.g. `{ controller: "pages", action: "index" }`
|
2166
|
+
# * An array, which is passed to `polymorphic_url`
|
2167
|
+
# * An Active Model instance
|
2168
|
+
# * An Active Model class
|
2169
|
+
#
|
2170
|
+
#
|
2171
|
+
# NOTE: Other URL helpers can be called in the block but be careful not to
|
2172
|
+
# invoke your custom URL helper again otherwise it will result in a stack
|
2173
|
+
# overflow error.
|
2174
|
+
#
|
2175
|
+
# You can also specify default options that will be passed through to your URL
|
2176
|
+
# helper definition, e.g:
|
2177
|
+
#
|
2178
|
+
# direct :browse, page: 1, size: 10 do |options|
|
2179
|
+
# [ :products, options.merge(params.permit(:page, :size).to_h.symbolize_keys) ]
|
2180
|
+
# end
|
2181
|
+
#
|
2182
|
+
# In this instance the `params` object comes from the context in which the block
|
2183
|
+
# is executed, e.g. generating a URL inside a controller action or a view. If
|
2184
|
+
# the block is executed where there isn't a `params` object such as this:
|
2185
|
+
#
|
2186
|
+
# Rails.application.routes.url_helpers.browse_path
|
2187
|
+
#
|
2188
|
+
# then it will raise a `NameError`. Because of this you need to be aware of the
|
2189
|
+
# context in which you will use your custom URL helper when defining it.
|
2190
|
+
#
|
2191
|
+
# NOTE: The `direct` method can't be used inside of a scope block such as
|
2192
|
+
# `namespace` or `scope` and will raise an error if it detects that it is.
|
2193
|
+
def direct(name, options = {}, &block)
|
2194
|
+
unless @scope.root?
|
2195
|
+
raise RuntimeError, "The direct method can't be used inside a routes scope block"
|
2196
|
+
end
|
2197
|
+
|
2198
|
+
@set.add_url_helper(name, options, &block)
|
2199
|
+
end
|
2200
|
+
|
2201
|
+
# Define custom polymorphic mappings of models to URLs. This alters the behavior
|
2202
|
+
# of `polymorphic_url` and consequently the behavior of `link_to` and `form_for`
|
2203
|
+
# when passed a model instance, e.g:
|
2204
|
+
#
|
2205
|
+
# resource :basket
|
2206
|
+
#
|
2207
|
+
# resolve "Basket" do
|
2208
|
+
# [:basket]
|
2209
|
+
# end
|
2210
|
+
#
|
2211
|
+
# This will now generate "/basket" when a `Basket` instance is passed to
|
2212
|
+
# `link_to` or `form_for` instead of the standard "/baskets/:id".
|
2213
|
+
#
|
2214
|
+
# NOTE: This custom behavior only applies to simple polymorphic URLs where a
|
2215
|
+
# single model instance is passed and not more complicated forms, e.g:
|
2216
|
+
#
|
2217
|
+
# # config/routes.rb
|
2218
|
+
# resource :profile
|
2219
|
+
# namespace :admin do
|
2220
|
+
# resources :users
|
2221
|
+
# end
|
2222
|
+
#
|
2223
|
+
# resolve("User") { [:profile] }
|
2224
|
+
#
|
2225
|
+
# # app/views/application/_menu.html.erb
|
2226
|
+
# link_to "Profile", @current_user
|
2227
|
+
# link_to "Profile", [:admin, @current_user]
|
2228
|
+
#
|
2229
|
+
# The first `link_to` will generate "/profile" but the second will generate the
|
2230
|
+
# standard polymorphic URL of "/admin/users/1".
|
2231
|
+
#
|
2232
|
+
# You can pass options to a polymorphic mapping - the arity for the block needs
|
2233
|
+
# to be two as the instance is passed as the first argument, e.g:
|
2234
|
+
#
|
2235
|
+
# resolve "Basket", anchor: "items" do |basket, options|
|
2236
|
+
# [:basket, options]
|
2237
|
+
# end
|
2238
|
+
#
|
2239
|
+
# This generates the URL "/basket#items" because when the last item in an array
|
2240
|
+
# passed to `polymorphic_url` is a hash then it's treated as options to the URL
|
2241
|
+
# helper that gets called.
|
2242
|
+
#
|
2243
|
+
# NOTE: The `resolve` method can't be used inside of a scope block such as
|
2244
|
+
# `namespace` or `scope` and will raise an error if it detects that it is.
|
2245
|
+
def resolve(*args, &block)
|
2246
|
+
unless @scope.root?
|
2247
|
+
raise RuntimeError, "The resolve method can't be used inside a routes scope block"
|
2248
|
+
end
|
2249
|
+
|
2250
|
+
options = args.extract_options!
|
2251
|
+
args = args.flatten(1)
|
2252
|
+
|
2253
|
+
args.each do |klass|
|
2254
|
+
@set.add_polymorphic_mapping(klass, options, &block)
|
2255
|
+
end
|
2256
|
+
end
|
2257
|
+
end
|
2258
|
+
|
1892
2259
|
class Scope # :nodoc:
|
1893
2260
|
OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
|
1894
2261
|
:controller, :action, :path_names, :constraints,
|
1895
|
-
:shallow, :blocks, :defaults, :options]
|
2262
|
+
:shallow, :blocks, :defaults, :via, :format, :options, :to]
|
1896
2263
|
|
1897
2264
|
RESOURCE_SCOPES = [:resource, :resources]
|
1898
2265
|
RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
|
1899
2266
|
|
1900
2267
|
attr_reader :parent, :scope_level
|
1901
2268
|
|
1902
|
-
def initialize(hash, parent =
|
2269
|
+
def initialize(hash, parent = NULL, scope_level = nil)
|
1903
2270
|
@hash = hash
|
1904
2271
|
@parent = parent
|
1905
2272
|
@scope_level = scope_level
|
@@ -1909,6 +2276,14 @@ module ActionDispatch
|
|
1909
2276
|
scope_level == :nested
|
1910
2277
|
end
|
1911
2278
|
|
2279
|
+
def null?
|
2280
|
+
@hash.nil? && @parent.nil?
|
2281
|
+
end
|
2282
|
+
|
2283
|
+
def root?
|
2284
|
+
@parent.null?
|
2285
|
+
end
|
2286
|
+
|
1912
2287
|
def resources?
|
1913
2288
|
scope_level == :resources
|
1914
2289
|
end
|
@@ -1947,27 +2322,34 @@ module ActionDispatch
|
|
1947
2322
|
end
|
1948
2323
|
|
1949
2324
|
def new_level(level)
|
1950
|
-
self.class.new(
|
1951
|
-
end
|
1952
|
-
|
1953
|
-
def fetch(key, &block)
|
1954
|
-
@hash.fetch(key, &block)
|
2325
|
+
self.class.new(frame, self, level)
|
1955
2326
|
end
|
1956
2327
|
|
1957
2328
|
def [](key)
|
1958
|
-
|
2329
|
+
scope = find { |node| node.frame.key? key }
|
2330
|
+
scope && scope.frame[key]
|
1959
2331
|
end
|
1960
2332
|
|
1961
|
-
|
1962
|
-
|
2333
|
+
include Enumerable
|
2334
|
+
|
2335
|
+
def each
|
2336
|
+
node = self
|
2337
|
+
until node.equal? NULL
|
2338
|
+
yield node
|
2339
|
+
node = node.parent
|
2340
|
+
end
|
1963
2341
|
end
|
2342
|
+
|
2343
|
+
def frame; @hash; end
|
2344
|
+
|
2345
|
+
NULL = Scope.new(nil, nil)
|
1964
2346
|
end
|
1965
2347
|
|
1966
|
-
def initialize(set)
|
2348
|
+
def initialize(set) # :nodoc:
|
1967
2349
|
@set = set
|
1968
|
-
@
|
2350
|
+
@draw_paths = set.draw_paths
|
2351
|
+
@scope = Scope.new(path_names: @set.resources_path_names)
|
1969
2352
|
@concerns = {}
|
1970
|
-
@nesting = []
|
1971
2353
|
end
|
1972
2354
|
|
1973
2355
|
include Base
|
@@ -1976,6 +2358,7 @@ module ActionDispatch
|
|
1976
2358
|
include Scoping
|
1977
2359
|
include Concerns
|
1978
2360
|
include Resources
|
2361
|
+
include CustomUrls
|
1979
2362
|
end
|
1980
2363
|
end
|
1981
2364
|
end
|