actionpack 4.2.11.1 → 5.2.4.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +287 -488
- data/MIT-LICENSE +1 -1
- data/README.rdoc +6 -7
- data/lib/abstract_controller/asset_paths.rb +2 -0
- data/lib/abstract_controller/base.rb +45 -49
- data/lib/{action_controller → abstract_controller}/caching/fragments.rb +78 -15
- data/lib/abstract_controller/caching.rb +66 -0
- data/lib/abstract_controller/callbacks.rb +47 -31
- data/lib/abstract_controller/collector.rb +8 -11
- data/lib/abstract_controller/error.rb +6 -0
- data/lib/abstract_controller/helpers.rb +25 -25
- data/lib/abstract_controller/logger.rb +2 -0
- data/lib/abstract_controller/railties/routes_helpers.rb +4 -2
- data/lib/abstract_controller/rendering.rb +42 -41
- data/lib/abstract_controller/translation.rb +10 -7
- data/lib/abstract_controller/url_for.rb +2 -0
- data/lib/abstract_controller.rb +12 -5
- data/lib/action_controller/api/api_rendering.rb +16 -0
- data/lib/action_controller/api.rb +149 -0
- data/lib/action_controller/base.rb +27 -19
- data/lib/action_controller/caching.rb +14 -57
- data/lib/action_controller/form_builder.rb +50 -0
- data/lib/action_controller/log_subscriber.rb +10 -15
- data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
- data/lib/action_controller/metal/conditional_get.rb +118 -44
- data/lib/action_controller/metal/content_security_policy.rb +52 -0
- data/lib/action_controller/metal/cookies.rb +3 -3
- data/lib/action_controller/metal/data_streaming.rb +27 -46
- data/lib/action_controller/metal/etag_with_flash.rb +18 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +20 -13
- data/lib/action_controller/metal/exceptions.rb +8 -14
- data/lib/action_controller/metal/flash.rb +4 -3
- data/lib/action_controller/metal/force_ssl.rb +23 -21
- data/lib/action_controller/metal/head.rb +21 -19
- data/lib/action_controller/metal/helpers.rb +24 -14
- data/lib/action_controller/metal/http_authentication.rb +64 -57
- data/lib/action_controller/metal/implicit_render.rb +62 -8
- data/lib/action_controller/metal/instrumentation.rb +19 -21
- data/lib/action_controller/metal/live.rb +90 -106
- data/lib/action_controller/metal/mime_responds.rb +33 -46
- data/lib/action_controller/metal/parameter_encoding.rb +51 -0
- data/lib/action_controller/metal/params_wrapper.rb +61 -53
- data/lib/action_controller/metal/redirecting.rb +49 -28
- data/lib/action_controller/metal/renderers.rb +87 -44
- data/lib/action_controller/metal/rendering.rb +72 -50
- data/lib/action_controller/metal/request_forgery_protection.rb +229 -93
- data/lib/action_controller/metal/rescue.rb +9 -16
- data/lib/action_controller/metal/streaming.rb +12 -10
- data/lib/action_controller/metal/strong_parameters.rb +583 -164
- data/lib/action_controller/metal/testing.rb +2 -17
- data/lib/action_controller/metal/url_for.rb +19 -10
- data/lib/action_controller/metal.rb +98 -83
- data/lib/action_controller/railtie.rb +28 -10
- data/lib/action_controller/railties/helpers.rb +2 -0
- data/lib/action_controller/renderer.rb +117 -0
- data/lib/action_controller/template_assertions.rb +11 -0
- data/lib/action_controller/test_case.rb +280 -411
- data/lib/action_controller.rb +29 -21
- data/lib/action_dispatch/http/cache.rb +93 -47
- data/lib/action_dispatch/http/content_security_policy.rb +272 -0
- data/lib/action_dispatch/http/filter_parameters.rb +26 -20
- data/lib/action_dispatch/http/filter_redirect.rb +10 -11
- data/lib/action_dispatch/http/headers.rb +55 -22
- data/lib/action_dispatch/http/mime_negotiation.rb +56 -41
- data/lib/action_dispatch/http/mime_type.rb +134 -121
- data/lib/action_dispatch/http/mime_types.rb +20 -6
- data/lib/action_dispatch/http/parameter_filter.rb +25 -11
- data/lib/action_dispatch/http/parameters.rb +98 -39
- data/lib/action_dispatch/http/rack_cache.rb +2 -0
- data/lib/action_dispatch/http/request.rb +200 -118
- data/lib/action_dispatch/http/response.rb +225 -110
- data/lib/action_dispatch/http/upload.rb +12 -6
- data/lib/action_dispatch/http/url.rb +110 -28
- data/lib/action_dispatch/journey/formatter.rb +55 -32
- data/lib/action_dispatch/journey/gtg/builder.rb +7 -5
- data/lib/action_dispatch/journey/gtg/simulator.rb +3 -9
- data/lib/action_dispatch/journey/gtg/transition_table.rb +17 -16
- data/lib/action_dispatch/journey/nfa/builder.rb +5 -3
- data/lib/action_dispatch/journey/nfa/dot.rb +13 -13
- data/lib/action_dispatch/journey/nfa/simulator.rb +3 -1
- data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -48
- data/lib/action_dispatch/journey/nodes/node.rb +18 -6
- data/lib/action_dispatch/journey/parser.rb +23 -22
- data/lib/action_dispatch/journey/parser.y +3 -2
- data/lib/action_dispatch/journey/parser_extras.rb +12 -4
- data/lib/action_dispatch/journey/path/pattern.rb +50 -44
- data/lib/action_dispatch/journey/route.rb +106 -28
- data/lib/action_dispatch/journey/router/utils.rb +20 -11
- data/lib/action_dispatch/journey/router.rb +35 -23
- data/lib/action_dispatch/journey/routes.rb +18 -16
- data/lib/action_dispatch/journey/scanner.rb +18 -15
- data/lib/action_dispatch/journey/visitors.rb +99 -52
- data/lib/action_dispatch/journey.rb +7 -5
- data/lib/action_dispatch/middleware/callbacks.rb +1 -2
- data/lib/action_dispatch/middleware/cookies.rb +304 -193
- data/lib/action_dispatch/middleware/debug_exceptions.rb +152 -57
- data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +68 -69
- data/lib/action_dispatch/middleware/executor.rb +21 -0
- data/lib/action_dispatch/middleware/flash.rb +78 -54
- data/lib/action_dispatch/middleware/public_exceptions.rb +27 -25
- data/lib/action_dispatch/middleware/reloader.rb +5 -91
- data/lib/action_dispatch/middleware/remote_ip.rb +41 -31
- data/lib/action_dispatch/middleware/request_id.rb +17 -9
- data/lib/action_dispatch/middleware/session/abstract_store.rb +41 -25
- data/lib/action_dispatch/middleware/session/cache_store.rb +24 -14
- data/lib/action_dispatch/middleware/session/cookie_store.rb +72 -67
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -2
- data/lib/action_dispatch/middleware/show_exceptions.rb +26 -22
- data/lib/action_dispatch/middleware/ssl.rb +114 -36
- data/lib/action_dispatch/middleware/stack.rb +31 -44
- data/lib/action_dispatch/middleware/static.rb +57 -50
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
- data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +64 -64
- data/lib/action_dispatch/railtie.rb +19 -11
- data/lib/action_dispatch/request/session.rb +106 -59
- data/lib/action_dispatch/request/utils.rb +67 -24
- data/lib/action_dispatch/routing/endpoint.rb +9 -2
- data/lib/action_dispatch/routing/inspector.rb +58 -67
- data/lib/action_dispatch/routing/mapper.rb +733 -447
- data/lib/action_dispatch/routing/polymorphic_routes.rb +161 -139
- data/lib/action_dispatch/routing/redirection.rb +36 -26
- data/lib/action_dispatch/routing/route_set.rb +321 -291
- data/lib/action_dispatch/routing/routes_proxy.rb +32 -5
- data/lib/action_dispatch/routing/url_for.rb +65 -25
- data/lib/action_dispatch/routing.rb +17 -18
- data/lib/action_dispatch/system_test_case.rb +147 -0
- data/lib/action_dispatch/system_testing/browser.rb +49 -0
- data/lib/action_dispatch/system_testing/driver.rb +59 -0
- data/lib/action_dispatch/system_testing/server.rb +31 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
- data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
- data/lib/action_dispatch/testing/assertion_response.rb +47 -0
- data/lib/action_dispatch/testing/assertions/response.rb +45 -20
- data/lib/action_dispatch/testing/assertions/routing.rb +30 -26
- data/lib/action_dispatch/testing/assertions.rb +6 -4
- data/lib/action_dispatch/testing/integration.rb +347 -209
- data/lib/action_dispatch/testing/request_encoder.rb +55 -0
- data/lib/action_dispatch/testing/test_process.rb +28 -22
- data/lib/action_dispatch/testing/test_request.rb +27 -34
- data/lib/action_dispatch/testing/test_response.rb +35 -7
- data/lib/action_dispatch.rb +27 -19
- data/lib/action_pack/gem_version.rb +5 -3
- data/lib/action_pack/version.rb +3 -1
- data/lib/action_pack.rb +4 -2
- metadata +56 -38
- 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/journey/backwards.rb +0 -5
- data/lib/action_dispatch/journey/router/strexp.rb +0 -27
- data/lib/action_dispatch/middleware/params_parser.rb +0 -60
- data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
- data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
- data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,39 +1,39 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require 'action_dispatch/routing/redirection'
|
10
|
-
require 'action_dispatch/routing/endpoint'
|
11
|
-
require 'active_support/deprecation'
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/hash/slice"
|
4
|
+
require "active_support/core_ext/enumerable"
|
5
|
+
require "active_support/core_ext/array/extract_options"
|
6
|
+
require "active_support/core_ext/regexp"
|
7
|
+
require "action_dispatch/routing/redirection"
|
8
|
+
require "action_dispatch/routing/endpoint"
|
12
9
|
|
13
10
|
module ActionDispatch
|
14
11
|
module Routing
|
15
12
|
class Mapper
|
16
13
|
URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
|
17
14
|
|
18
|
-
class Constraints < Endpoint #:nodoc:
|
15
|
+
class Constraints < Routing::Endpoint #:nodoc:
|
19
16
|
attr_reader :app, :constraints
|
20
17
|
|
21
|
-
|
22
|
-
|
18
|
+
SERVE = ->(app, req) { app.serve req }
|
19
|
+
CALL = ->(app, req) { app.call req.env }
|
20
|
+
|
21
|
+
def initialize(app, constraints, strategy)
|
22
|
+
# Unwrap Constraints objects. I don't actually think it's possible
|
23
23
|
# to pass a Constraints object to this constructor, but there were
|
24
|
-
# multiple places that kept testing children of this object.
|
24
|
+
# multiple places that kept testing children of this object. I
|
25
25
|
# *think* they were just being defensive, but I have no idea.
|
26
26
|
if app.is_a?(self.class)
|
27
27
|
constraints += app.constraints
|
28
28
|
app = app.app
|
29
29
|
end
|
30
30
|
|
31
|
-
@
|
31
|
+
@strategy = strategy
|
32
32
|
|
33
33
|
@app, @constraints, = app, constraints
|
34
34
|
end
|
35
35
|
|
36
|
-
def dispatcher?; @
|
36
|
+
def dispatcher?; @strategy == SERVE; end
|
37
37
|
|
38
38
|
def matches?(req)
|
39
39
|
@constraints.all? do |constraint|
|
@@ -43,13 +43,9 @@ module ActionDispatch
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def serve(req)
|
46
|
-
return [ 404, {
|
46
|
+
return [ 404, { "X-Cascade" => "pass" }, [] ] unless matches?(req)
|
47
47
|
|
48
|
-
|
49
|
-
@app.serve req
|
50
|
-
else
|
51
|
-
@app.call req.env
|
52
|
-
end
|
48
|
+
@strategy.call @app, req
|
53
49
|
end
|
54
50
|
|
55
51
|
private
|
@@ -62,101 +58,180 @@ module ActionDispatch
|
|
62
58
|
ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
|
63
59
|
OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z}
|
64
60
|
|
65
|
-
attr_reader :requirements, :
|
66
|
-
attr_reader :to, :default_controller, :default_action
|
61
|
+
attr_reader :requirements, :defaults
|
62
|
+
attr_reader :to, :default_controller, :default_action
|
63
|
+
attr_reader :required_defaults, :ast
|
67
64
|
|
68
|
-
def self.build(scope, set,
|
65
|
+
def self.build(scope, set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
|
69
66
|
options = scope[:options].merge(options) if scope[:options]
|
70
67
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
68
|
+
defaults = (scope[:defaults] || {}).dup
|
69
|
+
scope_constraints = scope[:constraints] || {}
|
70
|
+
|
71
|
+
new set, ast, defaults, controller, default_action, scope[:module], to, formatted, scope_constraints, scope[:blocks] || [], via, options_constraints, anchor, options
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.check_via(via)
|
75
|
+
if via.empty?
|
76
|
+
msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
|
77
|
+
"If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
|
78
|
+
"If you want to expose your action to GET, use `get` in the router:\n" \
|
79
|
+
" Instead of: match \"controller#action\"\n" \
|
80
|
+
" Do: get \"controller#action\""
|
81
|
+
raise ArgumentError, msg
|
82
|
+
end
|
83
|
+
via
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.normalize_path(path, format)
|
87
|
+
path = Mapper.normalize_path(path)
|
76
88
|
|
77
|
-
|
89
|
+
if format == true
|
90
|
+
"#{path}.:format"
|
91
|
+
elsif optional_format?(path, format)
|
92
|
+
"#{path}(.:format)"
|
93
|
+
else
|
94
|
+
path
|
95
|
+
end
|
96
|
+
end
|
78
97
|
|
79
|
-
|
98
|
+
def self.optional_format?(path, format)
|
99
|
+
format != false && path !~ OPTIONAL_FORMAT_REGEX
|
80
100
|
end
|
81
101
|
|
82
|
-
def initialize(
|
83
|
-
@requirements, @conditions = {}, {}
|
102
|
+
def initialize(set, ast, defaults, controller, default_action, modyoule, to, formatted, scope_constraints, blocks, via, options_constraints, anchor, options)
|
84
103
|
@defaults = defaults
|
85
104
|
@set = set
|
86
105
|
|
87
|
-
@to =
|
88
|
-
@default_controller =
|
89
|
-
@default_action =
|
90
|
-
@
|
91
|
-
@anchor =
|
106
|
+
@to = to
|
107
|
+
@default_controller = controller
|
108
|
+
@default_action = default_action
|
109
|
+
@ast = ast
|
110
|
+
@anchor = anchor
|
111
|
+
@via = via
|
112
|
+
@internal = options.delete(:internal)
|
92
113
|
|
93
|
-
|
94
|
-
via = Array(options.delete(:via) { [] })
|
95
|
-
options_constraints = options.delete :constraints
|
114
|
+
path_params = ast.find_all(&:symbol?).map(&:to_sym)
|
96
115
|
|
97
|
-
|
98
|
-
ast = path_ast path
|
99
|
-
path_params = path_params ast
|
116
|
+
options = add_wildcard_options(options, formatted, ast)
|
100
117
|
|
101
|
-
options = normalize_options!(options,
|
118
|
+
options = normalize_options!(options, path_params, modyoule)
|
102
119
|
|
120
|
+
split_options = constraints(options, path_params)
|
103
121
|
|
104
|
-
|
105
|
-
|
122
|
+
constraints = scope_constraints.merge Hash[split_options[:constraints] || []]
|
123
|
+
|
124
|
+
if options_constraints.is_a?(Hash)
|
125
|
+
@defaults = Hash[options_constraints.find_all { |key, default|
|
126
|
+
URL_OPTIONS.include?(key) && (String === default || Integer === default)
|
127
|
+
}].merge @defaults
|
128
|
+
@blocks = blocks
|
129
|
+
constraints.merge! options_constraints
|
130
|
+
else
|
131
|
+
@blocks = blocks(options_constraints)
|
132
|
+
end
|
106
133
|
|
107
|
-
split_constraints path_params, constraints
|
134
|
+
requirements, conditions = split_constraints path_params, constraints
|
135
|
+
verify_regexp_requirements requirements.map(&:last).grep(Regexp)
|
108
136
|
|
109
|
-
|
137
|
+
formats = normalize_format(formatted)
|
110
138
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
end
|
139
|
+
@requirements = formats[:requirements].merge Hash[requirements]
|
140
|
+
@conditions = Hash[conditions]
|
141
|
+
@defaults = formats[:defaults].merge(@defaults).merge(normalize_defaults(options))
|
142
|
+
|
143
|
+
if path_params.include?(:action) && !@requirements.key?(:action)
|
144
|
+
@defaults[:action] ||= "index"
|
118
145
|
end
|
119
146
|
|
120
|
-
|
147
|
+
@required_defaults = (split_options[:required_defaults] || []).map(&:first)
|
148
|
+
end
|
121
149
|
|
122
|
-
|
123
|
-
|
150
|
+
def make_route(name, precedence)
|
151
|
+
route = Journey::Route.new(name,
|
152
|
+
application,
|
153
|
+
path,
|
154
|
+
conditions,
|
155
|
+
required_defaults,
|
156
|
+
defaults,
|
157
|
+
request_method,
|
158
|
+
precedence,
|
159
|
+
@internal)
|
160
|
+
|
161
|
+
route
|
162
|
+
end
|
124
163
|
|
125
|
-
|
126
|
-
|
164
|
+
def application
|
165
|
+
app(@blocks)
|
127
166
|
end
|
128
167
|
|
129
|
-
def
|
130
|
-
|
168
|
+
def path
|
169
|
+
build_path @ast, requirements, @anchor
|
131
170
|
end
|
132
171
|
|
133
|
-
|
172
|
+
def conditions
|
173
|
+
build_conditions @conditions, @set.request_class
|
174
|
+
end
|
134
175
|
|
135
|
-
|
136
|
-
|
176
|
+
def build_conditions(current_conditions, request_class)
|
177
|
+
conditions = current_conditions.dup
|
137
178
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
179
|
+
conditions.keep_if do |k, _|
|
180
|
+
request_class.public_method_defined?(k)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
private :build_conditions
|
184
|
+
|
185
|
+
def request_method
|
186
|
+
@via.map { |x| Journey::Route.verb_matcher(x) }
|
187
|
+
end
|
188
|
+
private :request_method
|
189
|
+
|
190
|
+
JOINED_SEPARATORS = SEPARATORS.join # :nodoc:
|
191
|
+
|
192
|
+
def build_path(ast, requirements, anchor)
|
193
|
+
pattern = Journey::Path::Pattern.new(ast, requirements, JOINED_SEPARATORS, anchor)
|
194
|
+
|
195
|
+
# Find all the symbol nodes that are adjacent to literal nodes and alter
|
196
|
+
# the regexp so that Journey will partition them into custom routes.
|
197
|
+
ast.find_all { |node|
|
198
|
+
next unless node.cat?
|
199
|
+
|
200
|
+
if node.left.literal? && node.right.symbol?
|
201
|
+
symbol = node.right
|
202
|
+
elsif node.left.literal? && node.right.cat? && node.right.left.symbol?
|
203
|
+
symbol = node.right.left
|
204
|
+
elsif node.left.symbol? && node.right.literal?
|
205
|
+
symbol = node.left
|
206
|
+
elsif node.left.symbol? && node.right.cat? && node.right.left.literal?
|
207
|
+
symbol = node.left
|
142
208
|
else
|
143
|
-
|
209
|
+
next
|
144
210
|
end
|
145
|
-
end
|
146
211
|
|
147
|
-
|
148
|
-
|
149
|
-
|
212
|
+
if symbol
|
213
|
+
symbol.regexp = /(?:#{Regexp.union(symbol.regexp, '-')})+/
|
214
|
+
end
|
215
|
+
}
|
216
|
+
|
217
|
+
pattern
|
218
|
+
end
|
219
|
+
private :build_path
|
150
220
|
|
151
|
-
|
221
|
+
private
|
222
|
+
def add_wildcard_options(options, formatted, path_ast)
|
152
223
|
# Add a constraint for wildcard route to make it non-greedy and match the
|
153
|
-
# optional format part of the route by default
|
224
|
+
# optional format part of the route by default.
|
154
225
|
if formatted != false
|
155
|
-
path_ast.grep(Journey::Nodes::Star)
|
156
|
-
|
157
|
-
|
226
|
+
path_ast.grep(Journey::Nodes::Star).each_with_object({}) { |node, hash|
|
227
|
+
hash[node.name.to_sym] ||= /.+?/
|
228
|
+
}.merge options
|
229
|
+
else
|
230
|
+
options
|
158
231
|
end
|
232
|
+
end
|
159
233
|
|
234
|
+
def normalize_options!(options, path_params, modyoule)
|
160
235
|
if path_params.include?(:controller)
|
161
236
|
raise ArgumentError, ":controller segment is not allowed within a namespace block" if modyoule
|
162
237
|
|
@@ -167,7 +242,7 @@ module ActionDispatch
|
|
167
242
|
options[:controller] ||= /.+?/
|
168
243
|
end
|
169
244
|
|
170
|
-
if to.respond_to? :call
|
245
|
+
if to.respond_to?(:action) || to.respond_to?(:call)
|
171
246
|
options
|
172
247
|
else
|
173
248
|
to_endpoint = split_to to
|
@@ -181,81 +256,59 @@ module ActionDispatch
|
|
181
256
|
end
|
182
257
|
|
183
258
|
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
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
def normalize_format!(formatted)
|
195
|
-
if formatted == true
|
196
|
-
@requirements[:format] ||= /.+/
|
197
|
-
elsif Regexp === formatted
|
198
|
-
@requirements[:format] = formatted
|
199
|
-
@defaults[:format] = nil
|
200
|
-
elsif String === formatted
|
201
|
-
@requirements[:format] = Regexp.compile(formatted)
|
202
|
-
@defaults[:format] = formatted
|
259
|
+
constraints.partition do |key, requirement|
|
260
|
+
path_params.include?(key) || key == :controller
|
203
261
|
end
|
204
262
|
end
|
205
263
|
|
206
|
-
def
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
264
|
+
def normalize_format(formatted)
|
265
|
+
case formatted
|
266
|
+
when true
|
267
|
+
{ requirements: { format: /.+/ },
|
268
|
+
defaults: {} }
|
269
|
+
when Regexp
|
270
|
+
{ requirements: { format: formatted },
|
271
|
+
defaults: { format: nil } }
|
272
|
+
when String
|
273
|
+
{ requirements: { format: Regexp.compile(formatted) },
|
274
|
+
defaults: { format: formatted } }
|
275
|
+
else
|
276
|
+
{ requirements: {}, defaults: {} }
|
213
277
|
end
|
214
278
|
end
|
215
279
|
|
216
|
-
def
|
217
|
-
|
218
|
-
|
219
|
-
|
280
|
+
def verify_regexp_requirements(requirements)
|
281
|
+
requirements.each do |requirement|
|
282
|
+
if requirement.source =~ ANCHOR_CHARACTERS_REGEX
|
283
|
+
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
|
220
284
|
end
|
221
|
-
end
|
222
|
-
end
|
223
285
|
|
224
|
-
|
225
|
-
|
226
|
-
|
286
|
+
if requirement.multiline?
|
287
|
+
raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
|
288
|
+
end
|
227
289
|
end
|
228
290
|
end
|
229
291
|
|
230
|
-
def
|
231
|
-
|
232
|
-
|
233
|
-
if via.empty?
|
234
|
-
msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
|
235
|
-
"If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
|
236
|
-
"If you want to expose your action to GET, use `get` in the router:\n" \
|
237
|
-
" Instead of: match \"controller#action\"\n" \
|
238
|
-
" Do: get \"controller#action\""
|
239
|
-
raise ArgumentError, msg
|
240
|
-
end
|
241
|
-
|
242
|
-
conditions[:request_method] = via.map { |m| m.to_s.dasherize.upcase }
|
292
|
+
def normalize_defaults(options)
|
293
|
+
Hash[options.reject { |_, default| Regexp === default }]
|
243
294
|
end
|
244
295
|
|
245
296
|
def app(blocks)
|
246
|
-
if to.respond_to?(:
|
247
|
-
|
297
|
+
if to.respond_to?(:action)
|
298
|
+
Routing::RouteSet::StaticDispatcher.new to
|
299
|
+
elsif to.respond_to?(:call)
|
300
|
+
Constraints.new(to, blocks, Constraints::CALL)
|
248
301
|
elsif blocks.any?
|
249
|
-
Constraints.new(dispatcher(defaults), blocks,
|
302
|
+
Constraints.new(dispatcher(defaults.key?(:controller)), blocks, Constraints::SERVE)
|
250
303
|
else
|
251
|
-
dispatcher(defaults)
|
304
|
+
dispatcher(defaults.key?(:controller))
|
252
305
|
end
|
253
306
|
end
|
254
307
|
|
255
308
|
def check_controller_and_action(path_params, controller, action)
|
256
309
|
hash = check_part(:controller, controller, path_params, {}) do |part|
|
257
310
|
translate_controller(part) {
|
258
|
-
message = "'#{part}' is not a supported controller name. This can lead to potential routing problems."
|
311
|
+
message = "'#{part}' is not a supported controller name. This can lead to potential routing problems.".dup
|
259
312
|
message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
|
260
313
|
|
261
314
|
raise ArgumentError, message
|
@@ -280,22 +333,8 @@ module ActionDispatch
|
|
280
333
|
end
|
281
334
|
|
282
335
|
def split_to(to)
|
283
|
-
|
284
|
-
|
285
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
286
|
-
Defining a route where `to` is a symbol is deprecated.
|
287
|
-
Please change `to: :#{to}` to `action: :#{to}`.
|
288
|
-
MSG
|
289
|
-
|
290
|
-
[nil, to.to_s]
|
291
|
-
when /#/ then to.split('#')
|
292
|
-
when String
|
293
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
294
|
-
Defining a route where `to` is a controller without an action is deprecated.
|
295
|
-
Please change `to: '#{to}'` to `controller: '#{to}'`.
|
296
|
-
MSG
|
297
|
-
|
298
|
-
[to, nil]
|
336
|
+
if to =~ /#/
|
337
|
+
to.split("#")
|
299
338
|
else
|
300
339
|
[]
|
301
340
|
end
|
@@ -320,40 +359,29 @@ module ActionDispatch
|
|
320
359
|
yield
|
321
360
|
end
|
322
361
|
|
323
|
-
def blocks(
|
324
|
-
|
325
|
-
|
326
|
-
[options_constraints]
|
327
|
-
else
|
328
|
-
scope_blocks || []
|
362
|
+
def blocks(callable_constraint)
|
363
|
+
unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?)
|
364
|
+
raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?"
|
329
365
|
end
|
366
|
+
[callable_constraint]
|
330
367
|
end
|
331
368
|
|
332
369
|
def constraints(options, path_params)
|
333
|
-
|
334
|
-
required_defaults = []
|
335
|
-
options.each_pair do |key, option|
|
370
|
+
options.group_by do |key, option|
|
336
371
|
if Regexp === option
|
337
|
-
constraints
|
372
|
+
:constraints
|
338
373
|
else
|
339
|
-
|
374
|
+
if path_params.include?(key)
|
375
|
+
:path_params
|
376
|
+
else
|
377
|
+
:required_defaults
|
378
|
+
end
|
340
379
|
end
|
341
380
|
end
|
342
|
-
@conditions[:required_defaults] = required_defaults
|
343
|
-
constraints
|
344
381
|
end
|
345
382
|
|
346
|
-
def
|
347
|
-
|
348
|
-
end
|
349
|
-
|
350
|
-
def path_ast(path)
|
351
|
-
parser = Journey::Parser.new
|
352
|
-
parser.parse path
|
353
|
-
end
|
354
|
-
|
355
|
-
def dispatcher(defaults)
|
356
|
-
@set.dispatcher defaults
|
383
|
+
def dispatcher(raise_on_name_error)
|
384
|
+
Routing::RouteSet::Dispatcher.new raise_on_name_error
|
357
385
|
end
|
358
386
|
end
|
359
387
|
|
@@ -362,7 +390,7 @@ module ActionDispatch
|
|
362
390
|
# for root cases, where the latter is the correct one.
|
363
391
|
def self.normalize_path(path)
|
364
392
|
path = Journey::Router::Utils.normalize_path(path)
|
365
|
-
path.gsub!(%r{/(\(+)/?}, '\1/') unless path =~ %r{
|
393
|
+
path.gsub!(%r{/(\(+)/?}, '\1/') unless path =~ %r{^/(\(+[^)]+\)){1,}$}
|
366
394
|
path
|
367
395
|
end
|
368
396
|
|
@@ -371,24 +399,7 @@ module ActionDispatch
|
|
371
399
|
end
|
372
400
|
|
373
401
|
module Base
|
374
|
-
#
|
375
|
-
#
|
376
|
-
# root to: 'pages#main'
|
377
|
-
#
|
378
|
-
# For options, see +match+, as +root+ uses it internally.
|
379
|
-
#
|
380
|
-
# You can also pass a string which will expand
|
381
|
-
#
|
382
|
-
# root 'pages#main'
|
383
|
-
#
|
384
|
-
# You should put the root route at the top of <tt>config/routes.rb</tt>,
|
385
|
-
# because this means it will be matched first. As this is the most popular route
|
386
|
-
# of most Rails applications, this is beneficial.
|
387
|
-
def root(options = {})
|
388
|
-
match '/', { :as => :root, :via => :get }.merge!(options)
|
389
|
-
end
|
390
|
-
|
391
|
-
# Matches a url pattern to one or more routes.
|
402
|
+
# Matches a URL pattern to one or more routes.
|
392
403
|
#
|
393
404
|
# You should not use the +match+ method in your router
|
394
405
|
# without specifying an HTTP method.
|
@@ -398,7 +409,7 @@ module ActionDispatch
|
|
398
409
|
# # sets :controller, :action and :id in params
|
399
410
|
# match ':controller/:action/:id', via: [:get, :post]
|
400
411
|
#
|
401
|
-
# Note that +:controller+, +:action+ and +:id+ are interpreted as
|
412
|
+
# Note that +:controller+, +:action+ and +:id+ are interpreted as URL
|
402
413
|
# query parameters and thus available through +params+ in an action.
|
403
414
|
#
|
404
415
|
# If you want to expose your action to GET, use +get+ in the router:
|
@@ -435,7 +446,7 @@ module ActionDispatch
|
|
435
446
|
# A pattern can also point to a +Rack+ endpoint i.e. anything that
|
436
447
|
# responds to +call+:
|
437
448
|
#
|
438
|
-
# match 'photos/:id', to:
|
449
|
+
# match 'photos/:id', to: -> (hash) { [200, {}, ["Coming soon"]] }, via: :get
|
439
450
|
# match 'photos/:id', to: PhotoRackApp, via: :get
|
440
451
|
# # Yes, controller actions are just rack endpoints
|
441
452
|
# match 'photos/:id', to: PhotosController.action(:show), via: :get
|
@@ -447,7 +458,7 @@ module ActionDispatch
|
|
447
458
|
#
|
448
459
|
# === Options
|
449
460
|
#
|
450
|
-
# Any options not seen here are passed on as params with the
|
461
|
+
# Any options not seen here are passed on as params with the URL.
|
451
462
|
#
|
452
463
|
# [:controller]
|
453
464
|
# The route's controller.
|
@@ -460,6 +471,31 @@ module ActionDispatch
|
|
460
471
|
# dynamic segment used to generate the routes).
|
461
472
|
# You can access that segment from your controller using
|
462
473
|
# <tt>params[<:param>]</tt>.
|
474
|
+
# In your router:
|
475
|
+
#
|
476
|
+
# resources :users, param: :name
|
477
|
+
#
|
478
|
+
# The +users+ resource here will have the following routes generated for it:
|
479
|
+
#
|
480
|
+
# GET /users(.:format)
|
481
|
+
# POST /users(.:format)
|
482
|
+
# GET /users/new(.:format)
|
483
|
+
# GET /users/:name/edit(.:format)
|
484
|
+
# GET /users/:name(.:format)
|
485
|
+
# PATCH/PUT /users/:name(.:format)
|
486
|
+
# DELETE /users/:name(.:format)
|
487
|
+
#
|
488
|
+
# You can override <tt>ActiveRecord::Base#to_param</tt> of a related
|
489
|
+
# model to construct a URL:
|
490
|
+
#
|
491
|
+
# class User < ActiveRecord::Base
|
492
|
+
# def to_param
|
493
|
+
# name
|
494
|
+
# end
|
495
|
+
# end
|
496
|
+
#
|
497
|
+
# user = User.find_by(name: 'Phusion')
|
498
|
+
# user_path(user) # => "/users/Phusion"
|
463
499
|
#
|
464
500
|
# [:path]
|
465
501
|
# The path prefix for the routes.
|
@@ -487,7 +523,7 @@ module ActionDispatch
|
|
487
523
|
# +call+ or a string representing a controller's action.
|
488
524
|
#
|
489
525
|
# match 'path', to: 'controller#action', via: :get
|
490
|
-
# match 'path', to:
|
526
|
+
# match 'path', to: -> (env) { [200, {}, ["Success!"]] }, via: :get
|
491
527
|
# match 'path', to: RackApp, via: :get
|
492
528
|
#
|
493
529
|
# [:on]
|
@@ -543,7 +579,7 @@ module ActionDispatch
|
|
543
579
|
# [:format]
|
544
580
|
# Allows you to specify the default value for optional +format+
|
545
581
|
# segment or disable it by supplying +false+.
|
546
|
-
def match(path, options=nil)
|
582
|
+
def match(path, options = nil)
|
547
583
|
end
|
548
584
|
|
549
585
|
# Mount a Rack-based application to be used within the application.
|
@@ -568,17 +604,20 @@ module ActionDispatch
|
|
568
604
|
def mount(app, options = nil)
|
569
605
|
if options
|
570
606
|
path = options.delete(:at)
|
571
|
-
|
572
|
-
unless Hash === app
|
573
|
-
raise ArgumentError, "must be called with mount point"
|
574
|
-
end
|
575
|
-
|
607
|
+
elsif Hash === app
|
576
608
|
options = app
|
577
609
|
app, path = options.find { |k, _| k.respond_to?(:call) }
|
578
610
|
options.delete(app) if app
|
579
611
|
end
|
580
612
|
|
581
|
-
raise "A rack application must be specified" unless
|
613
|
+
raise ArgumentError, "A rack application must be specified" unless app.respond_to?(:call)
|
614
|
+
raise ArgumentError, <<-MSG.strip_heredoc unless path
|
615
|
+
Must be called with mount point
|
616
|
+
|
617
|
+
mount SomeRackApp, at: "some_route"
|
618
|
+
or
|
619
|
+
mount(SomeRackApp => "some_route")
|
620
|
+
MSG
|
582
621
|
|
583
622
|
rails_app = rails_app? app
|
584
623
|
options[:as] ||= app_name(app, rails_app)
|
@@ -586,7 +625,7 @@ module ActionDispatch
|
|
586
625
|
target_as = name_for_action(options[:as], path)
|
587
626
|
options[:via] ||= :all
|
588
627
|
|
589
|
-
match(path, options.merge(:
|
628
|
+
match(path, options.merge(to: app, anchor: false, format: false))
|
590
629
|
|
591
630
|
define_generate_prefix(app, target_as) if rails_app
|
592
631
|
self
|
@@ -605,7 +644,7 @@ module ActionDispatch
|
|
605
644
|
|
606
645
|
# Query if the following named route was already defined.
|
607
646
|
def has_named_route?(name)
|
608
|
-
@set.named_routes.
|
647
|
+
@set.named_routes.key? name
|
609
648
|
end
|
610
649
|
|
611
650
|
private
|
@@ -625,18 +664,31 @@ module ActionDispatch
|
|
625
664
|
def define_generate_prefix(app, name)
|
626
665
|
_route = @set.named_routes.get name
|
627
666
|
_routes = @set
|
628
|
-
|
667
|
+
_url_helpers = @set.url_helpers
|
668
|
+
|
669
|
+
script_namer = ->(options) do
|
670
|
+
prefix_options = options.slice(*_route.segment_keys)
|
671
|
+
prefix_options[:relative_url_root] = "".freeze
|
672
|
+
|
673
|
+
if options[:_recall]
|
674
|
+
prefix_options.reverse_merge!(options[:_recall].slice(*_route.segment_keys))
|
675
|
+
end
|
676
|
+
|
677
|
+
# We must actually delete prefix segment keys to avoid passing them to next url_for.
|
678
|
+
_route.segment_keys.each { |k| options.delete(k) }
|
679
|
+
_url_helpers.send("#{name}_path", prefix_options)
|
680
|
+
end
|
681
|
+
|
682
|
+
app.routes.define_mounted_helper(name, script_namer)
|
683
|
+
|
629
684
|
app.routes.extend Module.new {
|
630
685
|
def optimize_routes_generation?; false; end
|
686
|
+
|
631
687
|
define_method :find_script_name do |options|
|
632
688
|
if options.key? :script_name
|
633
689
|
super(options)
|
634
690
|
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)
|
691
|
+
script_namer.call(options)
|
640
692
|
end
|
641
693
|
end
|
642
694
|
}
|
@@ -782,7 +834,7 @@ module ActionDispatch
|
|
782
834
|
options = args.extract_options!.dup
|
783
835
|
scope = {}
|
784
836
|
|
785
|
-
options[:path] = args.flatten.join(
|
837
|
+
options[:path] = args.flatten.join("/") if args.any?
|
786
838
|
options[:constraints] ||= {}
|
787
839
|
|
788
840
|
unless nested_scope?
|
@@ -795,21 +847,30 @@ module ActionDispatch
|
|
795
847
|
URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Integer))
|
796
848
|
end
|
797
849
|
|
798
|
-
(options[:defaults]
|
850
|
+
options[:defaults] = defaults.merge(options[:defaults] || {})
|
799
851
|
else
|
800
852
|
block, options[:constraints] = options[:constraints], {}
|
801
853
|
end
|
802
854
|
|
855
|
+
if options.key?(:only) || options.key?(:except)
|
856
|
+
scope[:action_options] = { only: options.delete(:only),
|
857
|
+
except: options.delete(:except) }
|
858
|
+
end
|
859
|
+
|
860
|
+
if options.key? :anchor
|
861
|
+
raise ArgumentError, "anchor is ignored unless passed to `match`"
|
862
|
+
end
|
863
|
+
|
803
864
|
@scope.options.each do |option|
|
804
865
|
if option == :blocks
|
805
866
|
value = block
|
806
867
|
elsif option == :options
|
807
868
|
value = options
|
808
869
|
else
|
809
|
-
value = options.delete(option)
|
870
|
+
value = options.delete(option) { POISON }
|
810
871
|
end
|
811
872
|
|
812
|
-
|
873
|
+
unless POISON == value
|
813
874
|
scope[option] = send("merge_#{option}_scope", @scope[option], value)
|
814
875
|
end
|
815
876
|
end
|
@@ -821,14 +882,18 @@ module ActionDispatch
|
|
821
882
|
@scope = @scope.parent
|
822
883
|
end
|
823
884
|
|
885
|
+
POISON = Object.new # :nodoc:
|
886
|
+
|
824
887
|
# Scopes routes to a specific controller
|
825
888
|
#
|
826
889
|
# controller "food" do
|
827
|
-
# match "bacon", action:
|
890
|
+
# match "bacon", action: :bacon, via: :get
|
828
891
|
# end
|
829
|
-
def controller(controller
|
830
|
-
|
831
|
-
|
892
|
+
def controller(controller)
|
893
|
+
@scope = @scope.new(controller: controller)
|
894
|
+
yield
|
895
|
+
ensure
|
896
|
+
@scope = @scope.parent
|
832
897
|
end
|
833
898
|
|
834
899
|
# Scopes routes to a specific namespace. For example:
|
@@ -874,13 +939,14 @@ module ActionDispatch
|
|
874
939
|
|
875
940
|
defaults = {
|
876
941
|
module: path,
|
877
|
-
path: options.fetch(:path, path),
|
878
942
|
as: options.fetch(:as, path),
|
879
943
|
shallow_path: options.fetch(:path, path),
|
880
944
|
shallow_prefix: options.fetch(:as, path)
|
881
945
|
}
|
882
946
|
|
883
|
-
|
947
|
+
path_scope(options.delete(:path) { path }) do
|
948
|
+
scope(defaults.merge!(options)) { yield }
|
949
|
+
end
|
884
950
|
end
|
885
951
|
|
886
952
|
# === Parameter Restriction
|
@@ -917,7 +983,7 @@ module ActionDispatch
|
|
917
983
|
#
|
918
984
|
# Requests to routes can be constrained based on specific criteria:
|
919
985
|
#
|
920
|
-
# constraints(
|
986
|
+
# constraints(-> (req) { req.env["HTTP_USER_AGENT"] =~ /iPhone/ }) do
|
921
987
|
# resources :iphones
|
922
988
|
# end
|
923
989
|
#
|
@@ -939,7 +1005,7 @@ module ActionDispatch
|
|
939
1005
|
# resources :iphones
|
940
1006
|
# end
|
941
1007
|
def constraints(constraints = {})
|
942
|
-
scope(:
|
1008
|
+
scope(constraints: constraints) { yield }
|
943
1009
|
end
|
944
1010
|
|
945
1011
|
# Allows you to set default parameters for a route, such as this:
|
@@ -948,66 +1014,77 @@ module ActionDispatch
|
|
948
1014
|
# end
|
949
1015
|
# Using this, the +:id+ parameter here will default to 'home'.
|
950
1016
|
def defaults(defaults = {})
|
951
|
-
scope(:defaults
|
1017
|
+
@scope = @scope.new(defaults: merge_defaults_scope(@scope[:defaults], defaults))
|
1018
|
+
yield
|
1019
|
+
ensure
|
1020
|
+
@scope = @scope.parent
|
952
1021
|
end
|
953
1022
|
|
954
1023
|
private
|
955
|
-
def merge_path_scope(parent, child)
|
1024
|
+
def merge_path_scope(parent, child)
|
956
1025
|
Mapper.normalize_path("#{parent}/#{child}")
|
957
1026
|
end
|
958
1027
|
|
959
|
-
def merge_shallow_path_scope(parent, child)
|
1028
|
+
def merge_shallow_path_scope(parent, child)
|
960
1029
|
Mapper.normalize_path("#{parent}/#{child}")
|
961
1030
|
end
|
962
1031
|
|
963
|
-
def merge_as_scope(parent, child)
|
1032
|
+
def merge_as_scope(parent, child)
|
964
1033
|
parent ? "#{parent}_#{child}" : child
|
965
1034
|
end
|
966
1035
|
|
967
|
-
def merge_shallow_prefix_scope(parent, child)
|
1036
|
+
def merge_shallow_prefix_scope(parent, child)
|
968
1037
|
parent ? "#{parent}_#{child}" : child
|
969
1038
|
end
|
970
1039
|
|
971
|
-
def merge_module_scope(parent, child)
|
1040
|
+
def merge_module_scope(parent, child)
|
972
1041
|
parent ? "#{parent}/#{child}" : child
|
973
1042
|
end
|
974
1043
|
|
975
|
-
def merge_controller_scope(parent, child)
|
1044
|
+
def merge_controller_scope(parent, child)
|
1045
|
+
child
|
1046
|
+
end
|
1047
|
+
|
1048
|
+
def merge_action_scope(parent, child)
|
976
1049
|
child
|
977
1050
|
end
|
978
1051
|
|
979
|
-
def
|
1052
|
+
def merge_via_scope(parent, child)
|
980
1053
|
child
|
981
1054
|
end
|
982
1055
|
|
983
|
-
def
|
1056
|
+
def merge_format_scope(parent, child)
|
1057
|
+
child
|
1058
|
+
end
|
1059
|
+
|
1060
|
+
def merge_path_names_scope(parent, child)
|
984
1061
|
merge_options_scope(parent, child)
|
985
1062
|
end
|
986
1063
|
|
987
|
-
def merge_constraints_scope(parent, child)
|
1064
|
+
def merge_constraints_scope(parent, child)
|
988
1065
|
merge_options_scope(parent, child)
|
989
1066
|
end
|
990
1067
|
|
991
|
-
def merge_defaults_scope(parent, child)
|
1068
|
+
def merge_defaults_scope(parent, child)
|
992
1069
|
merge_options_scope(parent, child)
|
993
1070
|
end
|
994
1071
|
|
995
|
-
def merge_blocks_scope(parent, child)
|
1072
|
+
def merge_blocks_scope(parent, child)
|
996
1073
|
merged = parent ? parent.dup : []
|
997
1074
|
merged << child if child
|
998
1075
|
merged
|
999
1076
|
end
|
1000
1077
|
|
1001
|
-
def merge_options_scope(parent, child)
|
1002
|
-
(parent || {}).
|
1078
|
+
def merge_options_scope(parent, child)
|
1079
|
+
(parent || {}).merge(child)
|
1003
1080
|
end
|
1004
1081
|
|
1005
|
-
def merge_shallow_scope(parent, child)
|
1082
|
+
def merge_shallow_scope(parent, child)
|
1006
1083
|
child ? true : false
|
1007
1084
|
end
|
1008
1085
|
|
1009
|
-
def
|
1010
|
-
child
|
1086
|
+
def merge_to_scope(parent, child)
|
1087
|
+
child
|
1011
1088
|
end
|
1012
1089
|
end
|
1013
1090
|
|
@@ -1058,27 +1135,34 @@ module ActionDispatch
|
|
1058
1135
|
CANONICAL_ACTIONS = %w(index create new show update destroy)
|
1059
1136
|
|
1060
1137
|
class Resource #:nodoc:
|
1061
|
-
attr_reader :controller, :path, :
|
1138
|
+
attr_reader :controller, :path, :param
|
1062
1139
|
|
1063
|
-
def initialize(entities, options = {})
|
1140
|
+
def initialize(entities, api_only, shallow, options = {})
|
1064
1141
|
@name = entities.to_s
|
1065
1142
|
@path = (options[:path] || @name).to_s
|
1066
1143
|
@controller = (options[:controller] || @name).to_s
|
1067
1144
|
@as = options[:as]
|
1068
1145
|
@param = (options[:param] || :id).to_sym
|
1069
1146
|
@options = options
|
1070
|
-
@shallow =
|
1147
|
+
@shallow = shallow
|
1148
|
+
@api_only = api_only
|
1149
|
+
@only = options.delete :only
|
1150
|
+
@except = options.delete :except
|
1071
1151
|
end
|
1072
1152
|
|
1073
1153
|
def default_actions
|
1074
|
-
|
1154
|
+
if @api_only
|
1155
|
+
[:index, :create, :show, :update, :destroy]
|
1156
|
+
else
|
1157
|
+
[:index, :create, :new, :show, :update, :destroy, :edit]
|
1158
|
+
end
|
1075
1159
|
end
|
1076
1160
|
|
1077
1161
|
def actions
|
1078
|
-
if
|
1079
|
-
Array(only).map(&:to_sym)
|
1080
|
-
elsif
|
1081
|
-
default_actions - Array(except).map(&:to_sym)
|
1162
|
+
if @only
|
1163
|
+
Array(@only).map(&:to_sym)
|
1164
|
+
elsif @except
|
1165
|
+
default_actions - Array(@except).map(&:to_sym)
|
1082
1166
|
else
|
1083
1167
|
default_actions
|
1084
1168
|
end
|
@@ -1105,7 +1189,7 @@ module ActionDispatch
|
|
1105
1189
|
end
|
1106
1190
|
|
1107
1191
|
def resource_scope
|
1108
|
-
|
1192
|
+
controller
|
1109
1193
|
end
|
1110
1194
|
|
1111
1195
|
alias :collection_scope :path
|
@@ -1128,17 +1212,15 @@ module ActionDispatch
|
|
1128
1212
|
"#{path}/:#{nested_param}"
|
1129
1213
|
end
|
1130
1214
|
|
1131
|
-
def shallow=(value)
|
1132
|
-
@shallow = value
|
1133
|
-
end
|
1134
|
-
|
1135
1215
|
def shallow?
|
1136
1216
|
@shallow
|
1137
1217
|
end
|
1218
|
+
|
1219
|
+
def singleton?; false; end
|
1138
1220
|
end
|
1139
1221
|
|
1140
1222
|
class SingletonResource < Resource #:nodoc:
|
1141
|
-
def initialize(entities, options)
|
1223
|
+
def initialize(entities, api_only, shallow, options)
|
1142
1224
|
super
|
1143
1225
|
@as = nil
|
1144
1226
|
@controller = (options[:controller] || plural).to_s
|
@@ -1146,7 +1228,11 @@ module ActionDispatch
|
|
1146
1228
|
end
|
1147
1229
|
|
1148
1230
|
def default_actions
|
1149
|
-
|
1231
|
+
if @api_only
|
1232
|
+
[:show, :create, :update, :destroy]
|
1233
|
+
else
|
1234
|
+
[:show, :create, :update, :destroy, :new, :edit]
|
1235
|
+
end
|
1150
1236
|
end
|
1151
1237
|
|
1152
1238
|
def plural
|
@@ -1162,6 +1248,8 @@ module ActionDispatch
|
|
1162
1248
|
|
1163
1249
|
alias :member_scope :path
|
1164
1250
|
alias :nested_scope :path
|
1251
|
+
|
1252
|
+
def singleton?; true; end
|
1165
1253
|
end
|
1166
1254
|
|
1167
1255
|
def resources_path_names(options)
|
@@ -1176,19 +1264,19 @@ module ActionDispatch
|
|
1176
1264
|
#
|
1177
1265
|
# resource :profile
|
1178
1266
|
#
|
1179
|
-
# creates six different routes in your application, all mapping to
|
1267
|
+
# This creates six different routes in your application, all mapping to
|
1180
1268
|
# the +Profiles+ controller (note that the controller is named after
|
1181
1269
|
# the plural):
|
1182
1270
|
#
|
1183
1271
|
# GET /profile/new
|
1184
|
-
# POST /profile
|
1185
1272
|
# GET /profile
|
1186
1273
|
# GET /profile/edit
|
1187
1274
|
# PATCH/PUT /profile
|
1188
1275
|
# DELETE /profile
|
1276
|
+
# POST /profile
|
1189
1277
|
#
|
1190
1278
|
# === Options
|
1191
|
-
# Takes same options as
|
1279
|
+
# Takes same options as resources[rdoc-ref:#resources]
|
1192
1280
|
def resource(*resources, &block)
|
1193
1281
|
options = resources.extract_options!.dup
|
1194
1282
|
|
@@ -1196,20 +1284,23 @@ module ActionDispatch
|
|
1196
1284
|
return self
|
1197
1285
|
end
|
1198
1286
|
|
1199
|
-
|
1200
|
-
|
1287
|
+
with_scope_level(:resource) do
|
1288
|
+
options = apply_action_options options
|
1289
|
+
resource_scope(SingletonResource.new(resources.pop, api_only?, @scope[:shallow], options)) do
|
1290
|
+
yield if block_given?
|
1201
1291
|
|
1202
|
-
|
1292
|
+
concerns(options[:concerns]) if options[:concerns]
|
1203
1293
|
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1294
|
+
new do
|
1295
|
+
get :new
|
1296
|
+
end if parent_resource.actions.include?(:new)
|
1207
1297
|
|
1208
|
-
|
1209
|
-
get :new
|
1210
|
-
end if parent_resource.actions.include?(:new)
|
1298
|
+
set_member_mappings_for_resource
|
1211
1299
|
|
1212
|
-
|
1300
|
+
collection do
|
1301
|
+
post :create
|
1302
|
+
end if parent_resource.actions.include?(:create)
|
1303
|
+
end
|
1213
1304
|
end
|
1214
1305
|
|
1215
1306
|
self
|
@@ -1250,7 +1341,7 @@ module ActionDispatch
|
|
1250
1341
|
# DELETE /photos/:photo_id/comments/:id
|
1251
1342
|
#
|
1252
1343
|
# === Options
|
1253
|
-
# Takes same options as
|
1344
|
+
# Takes same options as match[rdoc-ref:Base#match] as well as:
|
1254
1345
|
#
|
1255
1346
|
# [:path_names]
|
1256
1347
|
# Allows you to change the segment component of the +edit+ and +new+ actions.
|
@@ -1258,14 +1349,14 @@ module ActionDispatch
|
|
1258
1349
|
#
|
1259
1350
|
# resources :posts, path_names: { new: "brand_new" }
|
1260
1351
|
#
|
1261
|
-
# The above example will now change /posts/new to /posts/brand_new
|
1352
|
+
# The above example will now change /posts/new to /posts/brand_new.
|
1262
1353
|
#
|
1263
1354
|
# [:path]
|
1264
1355
|
# Allows you to change the path prefix for the resource.
|
1265
1356
|
#
|
1266
1357
|
# resources :posts, path: 'postings'
|
1267
1358
|
#
|
1268
|
-
# The resource and all segments will now route to /postings instead of /posts
|
1359
|
+
# The resource and all segments will now route to /postings instead of /posts.
|
1269
1360
|
#
|
1270
1361
|
# [:only]
|
1271
1362
|
# Only generate routes for the given actions.
|
@@ -1354,21 +1445,24 @@ module ActionDispatch
|
|
1354
1445
|
return self
|
1355
1446
|
end
|
1356
1447
|
|
1357
|
-
|
1358
|
-
|
1448
|
+
with_scope_level(:resources) do
|
1449
|
+
options = apply_action_options options
|
1450
|
+
resource_scope(Resource.new(resources.pop, api_only?, @scope[:shallow], options)) do
|
1451
|
+
yield if block_given?
|
1359
1452
|
|
1360
|
-
|
1453
|
+
concerns(options[:concerns]) if options[:concerns]
|
1361
1454
|
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1365
|
-
|
1455
|
+
collection do
|
1456
|
+
get :index if parent_resource.actions.include?(:index)
|
1457
|
+
post :create if parent_resource.actions.include?(:create)
|
1458
|
+
end
|
1366
1459
|
|
1367
|
-
|
1368
|
-
|
1369
|
-
|
1460
|
+
new do
|
1461
|
+
get :new
|
1462
|
+
end if parent_resource.actions.include?(:new)
|
1370
1463
|
|
1371
|
-
|
1464
|
+
set_member_mappings_for_resource
|
1465
|
+
end
|
1372
1466
|
end
|
1373
1467
|
|
1374
1468
|
self
|
@@ -1392,7 +1486,7 @@ module ActionDispatch
|
|
1392
1486
|
end
|
1393
1487
|
|
1394
1488
|
with_scope_level(:collection) do
|
1395
|
-
|
1489
|
+
path_scope(parent_resource.collection_scope) do
|
1396
1490
|
yield
|
1397
1491
|
end
|
1398
1492
|
end
|
@@ -1416,9 +1510,11 @@ module ActionDispatch
|
|
1416
1510
|
|
1417
1511
|
with_scope_level(:member) do
|
1418
1512
|
if shallow?
|
1419
|
-
shallow_scope
|
1513
|
+
shallow_scope {
|
1514
|
+
path_scope(parent_resource.member_scope) { yield }
|
1515
|
+
}
|
1420
1516
|
else
|
1421
|
-
|
1517
|
+
path_scope(parent_resource.member_scope) { yield }
|
1422
1518
|
end
|
1423
1519
|
end
|
1424
1520
|
end
|
@@ -1429,7 +1525,7 @@ module ActionDispatch
|
|
1429
1525
|
end
|
1430
1526
|
|
1431
1527
|
with_scope_level(:new) do
|
1432
|
-
|
1528
|
+
path_scope(parent_resource.new_scope(action_path(:new))) do
|
1433
1529
|
yield
|
1434
1530
|
end
|
1435
1531
|
end
|
@@ -1442,14 +1538,20 @@ module ActionDispatch
|
|
1442
1538
|
|
1443
1539
|
with_scope_level(:nested) do
|
1444
1540
|
if shallow? && shallow_nesting_depth >= 1
|
1445
|
-
shallow_scope
|
1541
|
+
shallow_scope do
|
1542
|
+
path_scope(parent_resource.nested_scope) do
|
1543
|
+
scope(nested_options) { yield }
|
1544
|
+
end
|
1545
|
+
end
|
1446
1546
|
else
|
1447
|
-
|
1547
|
+
path_scope(parent_resource.nested_scope) do
|
1548
|
+
scope(nested_options) { yield }
|
1549
|
+
end
|
1448
1550
|
end
|
1449
1551
|
end
|
1450
1552
|
end
|
1451
1553
|
|
1452
|
-
# See ActionDispatch::Routing::Mapper::Scoping#namespace
|
1554
|
+
# See ActionDispatch::Routing::Mapper::Scoping#namespace.
|
1453
1555
|
def namespace(path, options = {})
|
1454
1556
|
if resource_scope?
|
1455
1557
|
nested { super }
|
@@ -1459,23 +1561,29 @@ module ActionDispatch
|
|
1459
1561
|
end
|
1460
1562
|
|
1461
1563
|
def shallow
|
1462
|
-
scope(:
|
1463
|
-
|
1464
|
-
|
1564
|
+
@scope = @scope.new(shallow: true)
|
1565
|
+
yield
|
1566
|
+
ensure
|
1567
|
+
@scope = @scope.parent
|
1465
1568
|
end
|
1466
1569
|
|
1467
1570
|
def shallow?
|
1468
|
-
parent_resource.
|
1571
|
+
!parent_resource.singleton? && @scope[:shallow]
|
1469
1572
|
end
|
1470
1573
|
|
1471
|
-
#
|
1472
|
-
#
|
1473
|
-
#
|
1474
|
-
|
1574
|
+
# Matches a URL pattern to one or more routes.
|
1575
|
+
# For more information, see match[rdoc-ref:Base#match].
|
1576
|
+
#
|
1577
|
+
# match 'path' => 'controller#action', via: :patch
|
1578
|
+
# match 'path', to: 'controller#action', via: :post
|
1579
|
+
# match 'path', 'otherpath', on: :member, via: :get
|
1580
|
+
def match(path, *rest, &block)
|
1475
1581
|
if rest.empty? && Hash === path
|
1476
1582
|
options = path
|
1477
1583
|
path, to = options.find { |name, _value| name.is_a?(String) }
|
1478
1584
|
|
1585
|
+
raise ArgumentError, "Route path not specified" if path.nil?
|
1586
|
+
|
1479
1587
|
case to
|
1480
1588
|
when Symbol
|
1481
1589
|
options[:action] = to
|
@@ -1496,77 +1604,30 @@ module ActionDispatch
|
|
1496
1604
|
paths = [path] + rest
|
1497
1605
|
end
|
1498
1606
|
|
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) }
|
1607
|
+
if options.key?(:defaults)
|
1608
|
+
defaults(options.delete(:defaults)) { map_match(paths, options, &block) }
|
1531
1609
|
else
|
1532
|
-
|
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
|
1610
|
+
map_match(paths, options, &block)
|
1540
1611
|
end
|
1541
1612
|
end
|
1542
1613
|
|
1543
|
-
|
1544
|
-
|
1545
|
-
|
1546
|
-
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
1551
|
-
|
1552
|
-
|
1553
|
-
|
1554
|
-
|
1555
|
-
|
1556
|
-
|
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
|
-
end
|
1565
|
-
|
1566
|
-
def root(path, options={})
|
1614
|
+
# You can specify what Rails should route "/" to with the root method:
|
1615
|
+
#
|
1616
|
+
# root to: 'pages#main'
|
1617
|
+
#
|
1618
|
+
# For options, see +match+, as +root+ uses it internally.
|
1619
|
+
#
|
1620
|
+
# You can also pass a string which will expand
|
1621
|
+
#
|
1622
|
+
# root 'pages#main'
|
1623
|
+
#
|
1624
|
+
# You should put the root route at the top of <tt>config/routes.rb</tt>,
|
1625
|
+
# because this means it will be matched first. As this is the most popular route
|
1626
|
+
# of most Rails applications, this is beneficial.
|
1627
|
+
def root(path, options = {})
|
1567
1628
|
if path.is_a?(String)
|
1568
1629
|
options[:to] = path
|
1569
|
-
elsif path.is_a?(Hash)
|
1630
|
+
elsif path.is_a?(Hash) && options.empty?
|
1570
1631
|
options = path
|
1571
1632
|
else
|
1572
1633
|
raise ArgumentError, "must be called with a path and/or options"
|
@@ -1574,22 +1635,22 @@ module ActionDispatch
|
|
1574
1635
|
|
1575
1636
|
if @scope.resources?
|
1576
1637
|
with_scope_level(:root) do
|
1577
|
-
|
1578
|
-
|
1638
|
+
path_scope(parent_resource.path) do
|
1639
|
+
match_root_route(options)
|
1579
1640
|
end
|
1580
1641
|
end
|
1581
1642
|
else
|
1582
|
-
|
1643
|
+
match_root_route(options)
|
1583
1644
|
end
|
1584
1645
|
end
|
1585
1646
|
|
1586
|
-
|
1647
|
+
private
|
1587
1648
|
|
1588
|
-
def parent_resource
|
1649
|
+
def parent_resource
|
1589
1650
|
@scope[:scope_level_resource]
|
1590
1651
|
end
|
1591
1652
|
|
1592
|
-
def apply_common_behavior_for(method, resources, options, &block)
|
1653
|
+
def apply_common_behavior_for(method, resources, options, &block)
|
1593
1654
|
if resources.length > 1
|
1594
1655
|
resources.each { |r| send(method, r, options, &block) }
|
1595
1656
|
return true
|
@@ -1619,71 +1680,51 @@ module ActionDispatch
|
|
1619
1680
|
return true
|
1620
1681
|
end
|
1621
1682
|
|
1622
|
-
unless action_options?(options)
|
1623
|
-
options.merge!(scope_action_options) if scope_action_options?
|
1624
|
-
end
|
1625
|
-
|
1626
1683
|
false
|
1627
1684
|
end
|
1628
1685
|
|
1629
|
-
def
|
1630
|
-
options
|
1686
|
+
def apply_action_options(options)
|
1687
|
+
return options if action_options? options
|
1688
|
+
options.merge scope_action_options
|
1631
1689
|
end
|
1632
1690
|
|
1633
|
-
def
|
1634
|
-
|
1691
|
+
def action_options?(options)
|
1692
|
+
options[:only] || options[:except]
|
1635
1693
|
end
|
1636
1694
|
|
1637
|
-
def scope_action_options
|
1638
|
-
@scope[:
|
1695
|
+
def scope_action_options
|
1696
|
+
@scope[:action_options] || {}
|
1639
1697
|
end
|
1640
1698
|
|
1641
|
-
def resource_scope?
|
1699
|
+
def resource_scope?
|
1642
1700
|
@scope.resource_scope?
|
1643
1701
|
end
|
1644
1702
|
|
1645
|
-
def resource_method_scope?
|
1703
|
+
def resource_method_scope?
|
1646
1704
|
@scope.resource_method_scope?
|
1647
1705
|
end
|
1648
1706
|
|
1649
|
-
def nested_scope?
|
1707
|
+
def nested_scope?
|
1650
1708
|
@scope.nested?
|
1651
1709
|
end
|
1652
1710
|
|
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)
|
1711
|
+
def with_scope_level(kind) # :doc:
|
1666
1712
|
@scope = @scope.new_level(kind)
|
1667
1713
|
yield
|
1668
1714
|
ensure
|
1669
1715
|
@scope = @scope.parent
|
1670
1716
|
end
|
1671
1717
|
|
1672
|
-
def resource_scope(
|
1673
|
-
|
1674
|
-
@scope = @scope.new(:scope_level_resource => resource)
|
1675
|
-
@nesting.push(resource)
|
1718
|
+
def resource_scope(resource)
|
1719
|
+
@scope = @scope.new(scope_level_resource: resource)
|
1676
1720
|
|
1677
|
-
|
1678
|
-
scope(parent_resource.resource_scope) { yield }
|
1679
|
-
end
|
1721
|
+
controller(resource.resource_scope) { yield }
|
1680
1722
|
ensure
|
1681
|
-
@nesting.pop
|
1682
1723
|
@scope = @scope.parent
|
1683
1724
|
end
|
1684
1725
|
|
1685
|
-
def nested_options
|
1686
|
-
options = { :
|
1726
|
+
def nested_options
|
1727
|
+
options = { as: parent_resource.member_name }
|
1687
1728
|
options[:constraints] = {
|
1688
1729
|
parent_resource.nested_param => param_constraint
|
1689
1730
|
} if param_constraint?
|
@@ -1691,62 +1732,61 @@ module ActionDispatch
|
|
1691
1732
|
options
|
1692
1733
|
end
|
1693
1734
|
|
1694
|
-
def
|
1695
|
-
@
|
1696
|
-
|
1697
|
-
|
1698
|
-
def shallow_nesting_depth #:nodoc:
|
1699
|
-
@nesting.select(&:shallow?).size
|
1735
|
+
def shallow_nesting_depth
|
1736
|
+
@scope.find_all { |node|
|
1737
|
+
node.frame[:scope_level_resource]
|
1738
|
+
}.count { |node| node.frame[:scope_level_resource].shallow? }
|
1700
1739
|
end
|
1701
1740
|
|
1702
|
-
def param_constraint?
|
1741
|
+
def param_constraint?
|
1703
1742
|
@scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp)
|
1704
1743
|
end
|
1705
1744
|
|
1706
|
-
def param_constraint
|
1745
|
+
def param_constraint
|
1707
1746
|
@scope[:constraints][parent_resource.param]
|
1708
1747
|
end
|
1709
1748
|
|
1710
|
-
def canonical_action?(action)
|
1749
|
+
def canonical_action?(action)
|
1711
1750
|
resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
|
1712
1751
|
end
|
1713
1752
|
|
1714
|
-
def shallow_scope
|
1715
|
-
scope = { :
|
1716
|
-
:
|
1753
|
+
def shallow_scope
|
1754
|
+
scope = { as: @scope[:shallow_prefix],
|
1755
|
+
path: @scope[:shallow_path] }
|
1717
1756
|
@scope = @scope.new scope
|
1718
1757
|
|
1719
|
-
|
1758
|
+
yield
|
1720
1759
|
ensure
|
1721
1760
|
@scope = @scope.parent
|
1722
1761
|
end
|
1723
1762
|
|
1724
|
-
def path_for_action(action, path)
|
1725
|
-
|
1763
|
+
def path_for_action(action, path)
|
1764
|
+
return "#{@scope[:path]}/#{path}" if path
|
1765
|
+
|
1766
|
+
if canonical_action?(action)
|
1726
1767
|
@scope[:path].to_s
|
1727
1768
|
else
|
1728
|
-
"#{@scope[:path]}/#{action_path(action
|
1769
|
+
"#{@scope[:path]}/#{action_path(action)}"
|
1729
1770
|
end
|
1730
1771
|
end
|
1731
1772
|
|
1732
|
-
def action_path(name
|
1733
|
-
name
|
1734
|
-
path || @scope[:path_names][name] || name.to_s
|
1773
|
+
def action_path(name)
|
1774
|
+
@scope[:path_names][name.to_sym] || name
|
1735
1775
|
end
|
1736
1776
|
|
1737
|
-
def prefix_name_for_action(as, action)
|
1777
|
+
def prefix_name_for_action(as, action)
|
1738
1778
|
if as
|
1739
1779
|
prefix = as
|
1740
1780
|
elsif !canonical_action?(action)
|
1741
1781
|
prefix = action
|
1742
1782
|
end
|
1743
1783
|
|
1744
|
-
if prefix && prefix !=
|
1745
|
-
Mapper.normalize_name prefix.to_s.tr(
|
1784
|
+
if prefix && prefix != "/" && !prefix.empty?
|
1785
|
+
Mapper.normalize_name prefix.to_s.tr("-", "_")
|
1746
1786
|
end
|
1747
1787
|
end
|
1748
1788
|
|
1749
|
-
def name_for_action(as, action)
|
1789
|
+
def name_for_action(as, action)
|
1750
1790
|
prefix = prefix_name_for_action(as, action)
|
1751
1791
|
name_prefix = @scope[:as]
|
1752
1792
|
|
@@ -1757,21 +1797,22 @@ module ActionDispatch
|
|
1757
1797
|
member_name = parent_resource.member_name
|
1758
1798
|
end
|
1759
1799
|
|
1760
|
-
|
1800
|
+
action_name = @scope.action_name(name_prefix, prefix, collection_name, member_name)
|
1801
|
+
candidate = action_name.select(&:present?).join("_")
|
1761
1802
|
|
1762
|
-
|
1803
|
+
unless candidate.empty?
|
1763
1804
|
# If a name was not explicitly given, we check if it is valid
|
1764
1805
|
# and return nil in case it isn't. Otherwise, we pass the invalid name
|
1765
1806
|
# forward so the underlying router engine treats it and raises an exception.
|
1766
1807
|
if as.nil?
|
1767
|
-
candidate unless candidate !~ /\A[_a-z]/i ||
|
1808
|
+
candidate unless candidate !~ /\A[_a-z]/i || has_named_route?(candidate)
|
1768
1809
|
else
|
1769
1810
|
candidate
|
1770
1811
|
end
|
1771
1812
|
end
|
1772
1813
|
end
|
1773
1814
|
|
1774
|
-
def set_member_mappings_for_resource
|
1815
|
+
def set_member_mappings_for_resource # :doc:
|
1775
1816
|
member do
|
1776
1817
|
get :edit if parent_resource.actions.include?(:edit)
|
1777
1818
|
get :show if parent_resource.actions.include?(:show)
|
@@ -1782,6 +1823,122 @@ module ActionDispatch
|
|
1782
1823
|
delete :destroy if parent_resource.actions.include?(:destroy)
|
1783
1824
|
end
|
1784
1825
|
end
|
1826
|
+
|
1827
|
+
def api_only? # :doc:
|
1828
|
+
@set.api_only?
|
1829
|
+
end
|
1830
|
+
|
1831
|
+
def path_scope(path)
|
1832
|
+
@scope = @scope.new(path: merge_path_scope(@scope[:path], path))
|
1833
|
+
yield
|
1834
|
+
ensure
|
1835
|
+
@scope = @scope.parent
|
1836
|
+
end
|
1837
|
+
|
1838
|
+
def map_match(paths, options)
|
1839
|
+
if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
|
1840
|
+
raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
|
1841
|
+
end
|
1842
|
+
|
1843
|
+
if @scope[:to]
|
1844
|
+
options[:to] ||= @scope[:to]
|
1845
|
+
end
|
1846
|
+
|
1847
|
+
if @scope[:controller] && @scope[:action]
|
1848
|
+
options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
|
1849
|
+
end
|
1850
|
+
|
1851
|
+
controller = options.delete(:controller) || @scope[:controller]
|
1852
|
+
option_path = options.delete :path
|
1853
|
+
to = options.delete :to
|
1854
|
+
via = Mapping.check_via Array(options.delete(:via) {
|
1855
|
+
@scope[:via]
|
1856
|
+
})
|
1857
|
+
formatted = options.delete(:format) { @scope[:format] }
|
1858
|
+
anchor = options.delete(:anchor) { true }
|
1859
|
+
options_constraints = options.delete(:constraints) || {}
|
1860
|
+
|
1861
|
+
path_types = paths.group_by(&:class)
|
1862
|
+
path_types.fetch(String, []).each do |_path|
|
1863
|
+
route_options = options.dup
|
1864
|
+
if _path && option_path
|
1865
|
+
raise ArgumentError, "Ambiguous route definition. Both :path and the route path were specified as strings."
|
1866
|
+
end
|
1867
|
+
to = get_to_from_path(_path, to, route_options[:action])
|
1868
|
+
decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints)
|
1869
|
+
end
|
1870
|
+
|
1871
|
+
path_types.fetch(Symbol, []).each do |action|
|
1872
|
+
route_options = options.dup
|
1873
|
+
decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints)
|
1874
|
+
end
|
1875
|
+
|
1876
|
+
self
|
1877
|
+
end
|
1878
|
+
|
1879
|
+
def get_to_from_path(path, to, action)
|
1880
|
+
return to if to || action
|
1881
|
+
|
1882
|
+
path_without_format = path.sub(/\(\.:format\)$/, "")
|
1883
|
+
if using_match_shorthand?(path_without_format)
|
1884
|
+
path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1').tr("-", "_")
|
1885
|
+
else
|
1886
|
+
nil
|
1887
|
+
end
|
1888
|
+
end
|
1889
|
+
|
1890
|
+
def using_match_shorthand?(path)
|
1891
|
+
path =~ %r{^/?[-\w]+/[-\w/]+$}
|
1892
|
+
end
|
1893
|
+
|
1894
|
+
def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
|
1895
|
+
if on = options.delete(:on)
|
1896
|
+
send(on) { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
|
1897
|
+
else
|
1898
|
+
case @scope.scope_level
|
1899
|
+
when :resources
|
1900
|
+
nested { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
|
1901
|
+
when :resource
|
1902
|
+
member { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
|
1903
|
+
else
|
1904
|
+
add_route(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
|
1905
|
+
end
|
1906
|
+
end
|
1907
|
+
end
|
1908
|
+
|
1909
|
+
def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints)
|
1910
|
+
path = path_for_action(action, _path)
|
1911
|
+
raise ArgumentError, "path is required" if path.blank?
|
1912
|
+
|
1913
|
+
action = action.to_s
|
1914
|
+
|
1915
|
+
default_action = options.delete(:action) || @scope[:action]
|
1916
|
+
|
1917
|
+
if action =~ /^[\w\-\/]+$/
|
1918
|
+
default_action ||= action.tr("-", "_") unless action.include?("/")
|
1919
|
+
else
|
1920
|
+
action = nil
|
1921
|
+
end
|
1922
|
+
|
1923
|
+
as = if !options.fetch(:as, true) # if it's set to nil or false
|
1924
|
+
options.delete(:as)
|
1925
|
+
else
|
1926
|
+
name_for_action(options.delete(:as), action)
|
1927
|
+
end
|
1928
|
+
|
1929
|
+
path = Mapping.normalize_path URI.parser.escape(path), formatted
|
1930
|
+
ast = Journey::Parser.parse path
|
1931
|
+
|
1932
|
+
mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
|
1933
|
+
@set.add_route(mapping, as)
|
1934
|
+
end
|
1935
|
+
|
1936
|
+
def match_root_route(options)
|
1937
|
+
name = has_named_route?(name_for_action(:root, nil)) ? nil : :root
|
1938
|
+
args = ["/", { as: name, via: :get }.merge!(options)]
|
1939
|
+
|
1940
|
+
match(*args)
|
1941
|
+
end
|
1785
1942
|
end
|
1786
1943
|
|
1787
1944
|
# Routing Concerns allow you to declare common routes that can be reused
|
@@ -1872,7 +2029,7 @@ module ActionDispatch
|
|
1872
2029
|
# concerns :commentable
|
1873
2030
|
# end
|
1874
2031
|
#
|
1875
|
-
#
|
2032
|
+
# Concerns also work in any routes helper that you want to use:
|
1876
2033
|
#
|
1877
2034
|
# namespace :posts do
|
1878
2035
|
# concerns :commentable
|
@@ -1889,17 +2046,131 @@ module ActionDispatch
|
|
1889
2046
|
end
|
1890
2047
|
end
|
1891
2048
|
|
2049
|
+
module CustomUrls
|
2050
|
+
# Define custom URL helpers that will be added to the application's
|
2051
|
+
# routes. This allows you to override and/or replace the default behavior
|
2052
|
+
# of routing helpers, e.g:
|
2053
|
+
#
|
2054
|
+
# direct :homepage do
|
2055
|
+
# "http://www.rubyonrails.org"
|
2056
|
+
# end
|
2057
|
+
#
|
2058
|
+
# direct :commentable do |model|
|
2059
|
+
# [ model, anchor: model.dom_id ]
|
2060
|
+
# end
|
2061
|
+
#
|
2062
|
+
# direct :main do
|
2063
|
+
# { controller: "pages", action: "index", subdomain: "www" }
|
2064
|
+
# end
|
2065
|
+
#
|
2066
|
+
# The return value from the block passed to +direct+ must be a valid set of
|
2067
|
+
# arguments for +url_for+ which will actually build the URL string. This can
|
2068
|
+
# be one of the following:
|
2069
|
+
#
|
2070
|
+
# * A string, which is treated as a generated URL
|
2071
|
+
# * A hash, e.g. <tt>{ controller: "pages", action: "index" }</tt>
|
2072
|
+
# * An array, which is passed to +polymorphic_url+
|
2073
|
+
# * An Active Model instance
|
2074
|
+
# * An Active Model class
|
2075
|
+
#
|
2076
|
+
# NOTE: Other URL helpers can be called in the block but be careful not to invoke
|
2077
|
+
# your custom URL helper again otherwise it will result in a stack overflow error.
|
2078
|
+
#
|
2079
|
+
# You can also specify default options that will be passed through to
|
2080
|
+
# your URL helper definition, e.g:
|
2081
|
+
#
|
2082
|
+
# direct :browse, page: 1, size: 10 do |options|
|
2083
|
+
# [ :products, options.merge(params.permit(:page, :size).to_h.symbolize_keys) ]
|
2084
|
+
# end
|
2085
|
+
#
|
2086
|
+
# In this instance the +params+ object comes from the context in which the
|
2087
|
+
# block is executed, e.g. generating a URL inside a controller action or a view.
|
2088
|
+
# If the block is executed where there isn't a +params+ object such as this:
|
2089
|
+
#
|
2090
|
+
# Rails.application.routes.url_helpers.browse_path
|
2091
|
+
#
|
2092
|
+
# then it will raise a +NameError+. Because of this you need to be aware of the
|
2093
|
+
# context in which you will use your custom URL helper when defining it.
|
2094
|
+
#
|
2095
|
+
# NOTE: The +direct+ method can't be used inside of a scope block such as
|
2096
|
+
# +namespace+ or +scope+ and will raise an error if it detects that it is.
|
2097
|
+
def direct(name, options = {}, &block)
|
2098
|
+
unless @scope.root?
|
2099
|
+
raise RuntimeError, "The direct method can't be used inside a routes scope block"
|
2100
|
+
end
|
2101
|
+
|
2102
|
+
@set.add_url_helper(name, options, &block)
|
2103
|
+
end
|
2104
|
+
|
2105
|
+
# Define custom polymorphic mappings of models to URLs. This alters the
|
2106
|
+
# behavior of +polymorphic_url+ and consequently the behavior of
|
2107
|
+
# +link_to+ and +form_for+ when passed a model instance, e.g:
|
2108
|
+
#
|
2109
|
+
# resource :basket
|
2110
|
+
#
|
2111
|
+
# resolve "Basket" do
|
2112
|
+
# [:basket]
|
2113
|
+
# end
|
2114
|
+
#
|
2115
|
+
# This will now generate "/basket" when a +Basket+ instance is passed to
|
2116
|
+
# +link_to+ or +form_for+ instead of the standard "/baskets/:id".
|
2117
|
+
#
|
2118
|
+
# NOTE: This custom behavior only applies to simple polymorphic URLs where
|
2119
|
+
# a single model instance is passed and not more complicated forms, e.g:
|
2120
|
+
#
|
2121
|
+
# # config/routes.rb
|
2122
|
+
# resource :profile
|
2123
|
+
# namespace :admin do
|
2124
|
+
# resources :users
|
2125
|
+
# end
|
2126
|
+
#
|
2127
|
+
# resolve("User") { [:profile] }
|
2128
|
+
#
|
2129
|
+
# # app/views/application/_menu.html.erb
|
2130
|
+
# link_to "Profile", @current_user
|
2131
|
+
# link_to "Profile", [:admin, @current_user]
|
2132
|
+
#
|
2133
|
+
# The first +link_to+ will generate "/profile" but the second will generate
|
2134
|
+
# the standard polymorphic URL of "/admin/users/1".
|
2135
|
+
#
|
2136
|
+
# You can pass options to a polymorphic mapping - the arity for the block
|
2137
|
+
# needs to be two as the instance is passed as the first argument, e.g:
|
2138
|
+
#
|
2139
|
+
# resolve "Basket", anchor: "items" do |basket, options|
|
2140
|
+
# [:basket, options]
|
2141
|
+
# end
|
2142
|
+
#
|
2143
|
+
# This generates the URL "/basket#items" because when the last item in an
|
2144
|
+
# array passed to +polymorphic_url+ is a hash then it's treated as options
|
2145
|
+
# to the URL helper that gets called.
|
2146
|
+
#
|
2147
|
+
# NOTE: The +resolve+ method can't be used inside of a scope block such as
|
2148
|
+
# +namespace+ or +scope+ and will raise an error if it detects that it is.
|
2149
|
+
def resolve(*args, &block)
|
2150
|
+
unless @scope.root?
|
2151
|
+
raise RuntimeError, "The resolve method can't be used inside a routes scope block"
|
2152
|
+
end
|
2153
|
+
|
2154
|
+
options = args.extract_options!
|
2155
|
+
args = args.flatten(1)
|
2156
|
+
|
2157
|
+
args.each do |klass|
|
2158
|
+
@set.add_polymorphic_mapping(klass, options, &block)
|
2159
|
+
end
|
2160
|
+
end
|
2161
|
+
end
|
2162
|
+
|
1892
2163
|
class Scope # :nodoc:
|
1893
2164
|
OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
|
1894
2165
|
:controller, :action, :path_names, :constraints,
|
1895
|
-
:shallow, :blocks, :defaults, :options]
|
2166
|
+
:shallow, :blocks, :defaults, :via, :format, :options, :to]
|
1896
2167
|
|
1897
2168
|
RESOURCE_SCOPES = [:resource, :resources]
|
1898
2169
|
RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
|
1899
2170
|
|
1900
2171
|
attr_reader :parent, :scope_level
|
1901
2172
|
|
1902
|
-
def initialize(hash, parent =
|
2173
|
+
def initialize(hash, parent = NULL, scope_level = nil)
|
1903
2174
|
@hash = hash
|
1904
2175
|
@parent = parent
|
1905
2176
|
@scope_level = scope_level
|
@@ -1909,6 +2180,14 @@ module ActionDispatch
|
|
1909
2180
|
scope_level == :nested
|
1910
2181
|
end
|
1911
2182
|
|
2183
|
+
def null?
|
2184
|
+
@hash.nil? && @parent.nil?
|
2185
|
+
end
|
2186
|
+
|
2187
|
+
def root?
|
2188
|
+
@parent.null?
|
2189
|
+
end
|
2190
|
+
|
1912
2191
|
def resources?
|
1913
2192
|
scope_level == :resources
|
1914
2193
|
end
|
@@ -1947,27 +2226,33 @@ module ActionDispatch
|
|
1947
2226
|
end
|
1948
2227
|
|
1949
2228
|
def new_level(level)
|
1950
|
-
self.class.new(
|
1951
|
-
end
|
1952
|
-
|
1953
|
-
def fetch(key, &block)
|
1954
|
-
@hash.fetch(key, &block)
|
2229
|
+
self.class.new(frame, self, level)
|
1955
2230
|
end
|
1956
2231
|
|
1957
2232
|
def [](key)
|
1958
|
-
|
2233
|
+
scope = find { |node| node.frame.key? key }
|
2234
|
+
scope && scope.frame[key]
|
1959
2235
|
end
|
1960
2236
|
|
1961
|
-
|
1962
|
-
|
2237
|
+
include Enumerable
|
2238
|
+
|
2239
|
+
def each
|
2240
|
+
node = self
|
2241
|
+
until node.equal? NULL
|
2242
|
+
yield node
|
2243
|
+
node = node.parent
|
2244
|
+
end
|
1963
2245
|
end
|
2246
|
+
|
2247
|
+
def frame; @hash; end
|
2248
|
+
|
2249
|
+
NULL = Scope.new(nil, nil)
|
1964
2250
|
end
|
1965
2251
|
|
1966
2252
|
def initialize(set) #:nodoc:
|
1967
2253
|
@set = set
|
1968
|
-
@scope = Scope.new(
|
2254
|
+
@scope = Scope.new(path_names: @set.resources_path_names)
|
1969
2255
|
@concerns = {}
|
1970
|
-
@nesting = []
|
1971
2256
|
end
|
1972
2257
|
|
1973
2258
|
include Base
|
@@ -1976,6 +2261,7 @@ module ActionDispatch
|
|
1976
2261
|
include Scoping
|
1977
2262
|
include Concerns
|
1978
2263
|
include Resources
|
2264
|
+
include CustomUrls
|
1979
2265
|
end
|
1980
2266
|
end
|
1981
2267
|
end
|