actionpack 4.2.11.3 → 5.0.7.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +890 -384
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -3
- data/lib/abstract_controller/base.rb +28 -38
- data/lib/{action_controller → abstract_controller}/caching/fragments.rb +51 -11
- data/lib/abstract_controller/caching.rb +62 -0
- data/lib/abstract_controller/callbacks.rb +54 -19
- data/lib/abstract_controller/collector.rb +4 -9
- data/lib/abstract_controller/error.rb +4 -0
- data/lib/abstract_controller/helpers.rb +4 -3
- data/lib/abstract_controller/railties/routes_helpers.rb +2 -2
- data/lib/abstract_controller/rendering.rb +28 -18
- data/lib/abstract_controller/translation.rb +8 -7
- data/lib/abstract_controller.rb +6 -2
- data/lib/action_controller/api/api_rendering.rb +14 -0
- data/lib/action_controller/api.rb +147 -0
- data/lib/action_controller/base.rb +14 -11
- data/lib/action_controller/caching.rb +13 -58
- data/lib/action_controller/form_builder.rb +48 -0
- data/lib/action_controller/log_subscriber.rb +3 -10
- data/lib/action_controller/metal/basic_implicit_render.rb +11 -0
- data/lib/action_controller/metal/conditional_get.rb +106 -34
- data/lib/action_controller/metal/cookies.rb +1 -3
- data/lib/action_controller/metal/data_streaming.rb +14 -34
- data/lib/action_controller/metal/etag_with_template_digest.rb +8 -2
- data/lib/action_controller/metal/exceptions.rb +11 -6
- data/lib/action_controller/metal/force_ssl.rb +11 -11
- data/lib/action_controller/metal/head.rb +14 -8
- data/lib/action_controller/metal/helpers.rb +15 -6
- data/lib/action_controller/metal/http_authentication.rb +44 -35
- data/lib/action_controller/metal/implicit_render.rb +61 -6
- data/lib/action_controller/metal/instrumentation.rb +5 -5
- data/lib/action_controller/metal/live.rb +71 -88
- data/lib/action_controller/metal/mime_responds.rb +27 -42
- data/lib/action_controller/metal/params_wrapper.rb +9 -9
- data/lib/action_controller/metal/redirecting.rb +32 -9
- data/lib/action_controller/metal/renderers.rb +83 -40
- data/lib/action_controller/metal/rendering.rb +38 -6
- data/lib/action_controller/metal/request_forgery_protection.rb +126 -48
- data/lib/action_controller/metal/rescue.rb +3 -12
- data/lib/action_controller/metal/streaming.rb +4 -4
- data/lib/action_controller/metal/strong_parameters.rb +527 -134
- data/lib/action_controller/metal/testing.rb +1 -12
- data/lib/action_controller/metal/url_for.rb +12 -5
- data/lib/action_controller/metal.rb +88 -63
- data/lib/action_controller/railtie.rb +11 -7
- data/lib/action_controller/renderer.rb +113 -0
- data/lib/action_controller/template_assertions.rb +9 -0
- data/lib/action_controller/test_case.rb +311 -374
- data/lib/action_controller.rb +12 -9
- data/lib/action_dispatch/http/cache.rb +73 -34
- data/lib/action_dispatch/http/filter_parameters.rb +16 -12
- data/lib/action_dispatch/http/filter_redirect.rb +7 -8
- data/lib/action_dispatch/http/headers.rb +45 -14
- data/lib/action_dispatch/http/mime_negotiation.rb +42 -23
- data/lib/action_dispatch/http/mime_type.rb +126 -90
- data/lib/action_dispatch/http/mime_types.rb +3 -4
- data/lib/action_dispatch/http/parameter_filter.rb +19 -9
- data/lib/action_dispatch/http/parameters.rb +70 -40
- data/lib/action_dispatch/http/request.rb +144 -89
- data/lib/action_dispatch/http/response.rb +215 -102
- data/lib/action_dispatch/http/upload.rb +6 -2
- data/lib/action_dispatch/http/url.rb +117 -8
- data/lib/action_dispatch/journey/formatter.rb +47 -30
- data/lib/action_dispatch/journey/gtg/transition_table.rb +1 -1
- data/lib/action_dispatch/journey/nfa/dot.rb +0 -2
- data/lib/action_dispatch/journey/nfa/transition_table.rb +1 -46
- data/lib/action_dispatch/journey/nodes/node.rb +14 -4
- data/lib/action_dispatch/journey/parser.rb +2 -0
- data/lib/action_dispatch/journey/parser_extras.rb +8 -2
- data/lib/action_dispatch/journey/path/pattern.rb +38 -42
- data/lib/action_dispatch/journey/route.rb +88 -26
- data/lib/action_dispatch/journey/router/utils.rb +5 -5
- data/lib/action_dispatch/journey/router.rb +8 -10
- data/lib/action_dispatch/journey/routes.rb +14 -15
- data/lib/action_dispatch/journey/visitors.rb +89 -44
- data/lib/action_dispatch/middleware/callbacks.rb +10 -1
- data/lib/action_dispatch/middleware/cookies.rb +188 -134
- data/lib/action_dispatch/middleware/debug_exceptions.rb +128 -49
- data/lib/action_dispatch/middleware/debug_locks.rb +122 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +21 -21
- data/lib/action_dispatch/middleware/executor.rb +19 -0
- data/lib/action_dispatch/middleware/flash.rb +66 -45
- data/lib/action_dispatch/middleware/params_parser.rb +32 -46
- data/lib/action_dispatch/middleware/public_exceptions.rb +2 -2
- data/lib/action_dispatch/middleware/reloader.rb +14 -58
- data/lib/action_dispatch/middleware/remote_ip.rb +29 -19
- data/lib/action_dispatch/middleware/request_id.rb +11 -6
- data/lib/action_dispatch/middleware/session/abstract_store.rb +23 -11
- data/lib/action_dispatch/middleware/session/cache_store.rb +9 -6
- data/lib/action_dispatch/middleware/session/cookie_store.rb +30 -24
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +4 -0
- data/lib/action_dispatch/middleware/show_exceptions.rb +11 -9
- data/lib/action_dispatch/middleware/ssl.rb +124 -36
- data/lib/action_dispatch/middleware/stack.rb +44 -40
- data/lib/action_dispatch/middleware/static.rb +51 -35
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -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 +59 -63
- data/lib/action_dispatch/railtie.rb +2 -2
- data/lib/action_dispatch/request/session.rb +69 -33
- data/lib/action_dispatch/request/utils.rb +51 -19
- data/lib/action_dispatch/routing/inspector.rb +32 -43
- data/lib/action_dispatch/routing/mapper.rb +515 -348
- data/lib/action_dispatch/routing/polymorphic_routes.rb +8 -14
- data/lib/action_dispatch/routing/redirection.rb +5 -4
- data/lib/action_dispatch/routing/route_set.rb +148 -240
- data/lib/action_dispatch/routing/url_for.rb +27 -10
- data/lib/action_dispatch/routing.rb +17 -13
- data/lib/action_dispatch/testing/assertion_response.rb +45 -0
- data/lib/action_dispatch/testing/assertions/response.rb +38 -20
- data/lib/action_dispatch/testing/assertions/routing.rb +16 -12
- data/lib/action_dispatch/testing/assertions.rb +1 -1
- data/lib/action_dispatch/testing/integration.rb +377 -149
- data/lib/action_dispatch/testing/request_encoder.rb +53 -0
- data/lib/action_dispatch/testing/test_process.rb +24 -20
- data/lib/action_dispatch/testing/test_request.rb +22 -31
- data/lib/action_dispatch/testing/test_response.rb +12 -4
- data/lib/action_dispatch.rb +4 -1
- data/lib/action_pack/gem_version.rb +4 -4
- data/lib/action_pack.rb +1 -1
- metadata +32 -34
- 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/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
- /data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
@@ -1,24 +1,22 @@
|
|
1
|
-
require 'active_support/core_ext/hash/except'
|
2
|
-
require 'active_support/core_ext/hash/reverse_merge'
|
3
1
|
require 'active_support/core_ext/hash/slice'
|
4
2
|
require 'active_support/core_ext/enumerable'
|
5
3
|
require 'active_support/core_ext/array/extract_options'
|
6
|
-
require 'active_support/core_ext/
|
7
|
-
require 'active_support/core_ext/string/filters'
|
8
|
-
require 'active_support/inflector'
|
4
|
+
require 'active_support/core_ext/regexp'
|
9
5
|
require 'action_dispatch/routing/redirection'
|
10
6
|
require 'action_dispatch/routing/endpoint'
|
11
|
-
require 'active_support/deprecation'
|
12
7
|
|
13
8
|
module ActionDispatch
|
14
9
|
module Routing
|
15
10
|
class Mapper
|
16
11
|
URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
|
17
12
|
|
18
|
-
class Constraints < Endpoint #:nodoc:
|
13
|
+
class Constraints < Routing::Endpoint #:nodoc:
|
19
14
|
attr_reader :app, :constraints
|
20
15
|
|
21
|
-
|
16
|
+
SERVE = ->(app, req) { app.serve req }
|
17
|
+
CALL = ->(app, req) { app.call req.env }
|
18
|
+
|
19
|
+
def initialize(app, constraints, strategy)
|
22
20
|
# Unwrap Constraints objects. I don't actually think it's possible
|
23
21
|
# to pass a Constraints object to this constructor, but there were
|
24
22
|
# multiple places that kept testing children of this object. I
|
@@ -28,12 +26,12 @@ module ActionDispatch
|
|
28
26
|
app = app.app
|
29
27
|
end
|
30
28
|
|
31
|
-
@
|
29
|
+
@strategy = strategy
|
32
30
|
|
33
31
|
@app, @constraints, = app, constraints
|
34
32
|
end
|
35
33
|
|
36
|
-
def dispatcher?; @
|
34
|
+
def dispatcher?; @strategy == SERVE; end
|
37
35
|
|
38
36
|
def matches?(req)
|
39
37
|
@constraints.all? do |constraint|
|
@@ -45,11 +43,7 @@ module ActionDispatch
|
|
45
43
|
def serve(req)
|
46
44
|
return [ 404, {'X-Cascade' => 'pass'}, [] ] unless matches?(req)
|
47
45
|
|
48
|
-
|
49
|
-
@app.serve req
|
50
|
-
else
|
51
|
-
@app.call req.env
|
52
|
-
end
|
46
|
+
@strategy.call @app, req
|
53
47
|
end
|
54
48
|
|
55
49
|
private
|
@@ -62,101 +56,180 @@ module ActionDispatch
|
|
62
56
|
ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
|
63
57
|
OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z}
|
64
58
|
|
65
|
-
attr_reader :requirements, :
|
66
|
-
attr_reader :to, :default_controller, :default_action
|
59
|
+
attr_reader :requirements, :defaults
|
60
|
+
attr_reader :to, :default_controller, :default_action
|
61
|
+
attr_reader :required_defaults, :ast
|
67
62
|
|
68
|
-
def self.build(scope, set,
|
63
|
+
def self.build(scope, set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
|
69
64
|
options = scope[:options].merge(options) if scope[:options]
|
70
65
|
|
71
|
-
|
72
|
-
|
73
|
-
options.delete :shallow_path
|
74
|
-
options.delete :shallow_prefix
|
75
|
-
options.delete :shallow
|
66
|
+
defaults = (scope[:defaults] || {}).dup
|
67
|
+
scope_constraints = scope[:constraints] || {}
|
76
68
|
|
77
|
-
defaults
|
69
|
+
new set, ast, defaults, controller, default_action, scope[:module], to, formatted, scope_constraints, scope[:blocks] || [], via, options_constraints, anchor, options
|
70
|
+
end
|
78
71
|
|
79
|
-
|
72
|
+
def self.check_via(via)
|
73
|
+
if via.empty?
|
74
|
+
msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
|
75
|
+
"If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
|
76
|
+
"If you want to expose your action to GET, use `get` in the router:\n" \
|
77
|
+
" Instead of: match \"controller#action\"\n" \
|
78
|
+
" Do: get \"controller#action\""
|
79
|
+
raise ArgumentError, msg
|
80
|
+
end
|
81
|
+
via
|
80
82
|
end
|
81
83
|
|
82
|
-
def
|
83
|
-
|
84
|
+
def self.normalize_path(path, format)
|
85
|
+
path = Mapper.normalize_path(path)
|
86
|
+
|
87
|
+
if format == true
|
88
|
+
"#{path}.:format"
|
89
|
+
elsif optional_format?(path, format)
|
90
|
+
"#{path}(.:format)"
|
91
|
+
else
|
92
|
+
path
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.optional_format?(path, format)
|
97
|
+
format != false && path !~ OPTIONAL_FORMAT_REGEX
|
98
|
+
end
|
99
|
+
|
100
|
+
def initialize(set, ast, defaults, controller, default_action, modyoule, to, formatted, scope_constraints, blocks, via, options_constraints, anchor, options)
|
84
101
|
@defaults = defaults
|
85
102
|
@set = set
|
86
103
|
|
87
|
-
@to =
|
88
|
-
@default_controller =
|
89
|
-
@default_action =
|
90
|
-
@
|
91
|
-
@anchor =
|
104
|
+
@to = to
|
105
|
+
@default_controller = controller
|
106
|
+
@default_action = default_action
|
107
|
+
@ast = ast
|
108
|
+
@anchor = anchor
|
109
|
+
@via = via
|
110
|
+
@internal = options[:internal]
|
92
111
|
|
93
|
-
|
94
|
-
via = Array(options.delete(:via) { [] })
|
95
|
-
options_constraints = options.delete :constraints
|
112
|
+
path_params = ast.find_all(&:symbol?).map(&:to_sym)
|
96
113
|
|
97
|
-
|
98
|
-
ast = path_ast path
|
99
|
-
path_params = path_params ast
|
114
|
+
options = add_wildcard_options(options, formatted, ast)
|
100
115
|
|
101
|
-
options = normalize_options!(options,
|
116
|
+
options = normalize_options!(options, path_params, modyoule)
|
102
117
|
|
118
|
+
split_options = constraints(options, path_params)
|
119
|
+
|
120
|
+
constraints = scope_constraints.merge Hash[split_options[:constraints] || []]
|
121
|
+
|
122
|
+
if options_constraints.is_a?(Hash)
|
123
|
+
@defaults = Hash[options_constraints.find_all { |key, default|
|
124
|
+
URL_OPTIONS.include?(key) && (String === default || Integer === default)
|
125
|
+
}].merge @defaults
|
126
|
+
@blocks = blocks
|
127
|
+
constraints.merge! options_constraints
|
128
|
+
else
|
129
|
+
@blocks = blocks(options_constraints)
|
130
|
+
end
|
103
131
|
|
104
|
-
split_constraints
|
105
|
-
|
132
|
+
requirements, conditions = split_constraints path_params, constraints
|
133
|
+
verify_regexp_requirements requirements.map(&:last).grep(Regexp)
|
106
134
|
|
107
|
-
|
135
|
+
formats = normalize_format(formatted)
|
108
136
|
|
109
|
-
@
|
137
|
+
@requirements = formats[:requirements].merge Hash[requirements]
|
138
|
+
@conditions = Hash[conditions]
|
139
|
+
@defaults = formats[:defaults].merge(@defaults).merge(normalize_defaults(options))
|
110
140
|
|
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
|
141
|
+
if path_params.include?(:action) && !@requirements.key?(:action)
|
142
|
+
@defaults[:action] ||= 'index'
|
118
143
|
end
|
119
144
|
|
120
|
-
|
145
|
+
@required_defaults = (split_options[:required_defaults] || []).map(&:first)
|
146
|
+
end
|
121
147
|
|
122
|
-
|
123
|
-
|
148
|
+
def make_route(name, precedence)
|
149
|
+
route = Journey::Route.new(name,
|
150
|
+
application,
|
151
|
+
path,
|
152
|
+
conditions,
|
153
|
+
required_defaults,
|
154
|
+
defaults,
|
155
|
+
request_method,
|
156
|
+
precedence,
|
157
|
+
@internal)
|
158
|
+
|
159
|
+
route
|
160
|
+
end
|
124
161
|
|
125
|
-
|
126
|
-
|
162
|
+
def application
|
163
|
+
app(@blocks)
|
127
164
|
end
|
128
165
|
|
129
|
-
def
|
130
|
-
|
166
|
+
def path
|
167
|
+
build_path @ast, requirements, @anchor
|
131
168
|
end
|
132
169
|
|
133
|
-
|
170
|
+
def conditions
|
171
|
+
build_conditions @conditions, @set.request_class
|
172
|
+
end
|
173
|
+
|
174
|
+
def build_conditions(current_conditions, request_class)
|
175
|
+
conditions = current_conditions.dup
|
134
176
|
|
135
|
-
|
136
|
-
|
177
|
+
conditions.keep_if do |k, _|
|
178
|
+
request_class.public_method_defined?(k)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
private :build_conditions
|
137
182
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
183
|
+
def request_method
|
184
|
+
@via.map { |x| Journey::Route.verb_matcher(x) }
|
185
|
+
end
|
186
|
+
private :request_method
|
187
|
+
|
188
|
+
JOINED_SEPARATORS = SEPARATORS.join # :nodoc:
|
189
|
+
|
190
|
+
def build_path(ast, requirements, anchor)
|
191
|
+
pattern = Journey::Path::Pattern.new(ast, requirements, JOINED_SEPARATORS, anchor)
|
192
|
+
|
193
|
+
# Find all the symbol nodes that are adjacent to literal nodes and alter
|
194
|
+
# the regexp so that Journey will partition them into custom routes.
|
195
|
+
ast.find_all { |node|
|
196
|
+
next unless node.cat?
|
197
|
+
|
198
|
+
if node.left.literal? && node.right.symbol?
|
199
|
+
symbol = node.right
|
200
|
+
elsif node.left.literal? && node.right.cat? && node.right.left.symbol?
|
201
|
+
symbol = node.right.left
|
202
|
+
elsif node.left.symbol? && node.right.literal?
|
203
|
+
symbol = node.left
|
204
|
+
elsif node.left.symbol? && node.right.cat? && node.right.left.literal?
|
205
|
+
symbol = node.left
|
142
206
|
else
|
143
|
-
|
207
|
+
next
|
144
208
|
end
|
145
|
-
end
|
146
209
|
|
147
|
-
|
148
|
-
|
149
|
-
|
210
|
+
if symbol
|
211
|
+
symbol.regexp = /(?:#{Regexp.union(symbol.regexp, '-')})+/
|
212
|
+
end
|
213
|
+
}
|
214
|
+
|
215
|
+
pattern
|
216
|
+
end
|
217
|
+
private :build_path
|
150
218
|
|
151
|
-
|
219
|
+
private
|
220
|
+
def add_wildcard_options(options, formatted, path_ast)
|
152
221
|
# Add a constraint for wildcard route to make it non-greedy and match the
|
153
222
|
# optional format part of the route by default
|
154
223
|
if formatted != false
|
155
|
-
path_ast.grep(Journey::Nodes::Star)
|
156
|
-
|
157
|
-
|
224
|
+
path_ast.grep(Journey::Nodes::Star).each_with_object({}) { |node, hash|
|
225
|
+
hash[node.name.to_sym] ||= /.+?/
|
226
|
+
}.merge options
|
227
|
+
else
|
228
|
+
options
|
158
229
|
end
|
230
|
+
end
|
159
231
|
|
232
|
+
def normalize_options!(options, path_params, modyoule)
|
160
233
|
if path_params.include?(:controller)
|
161
234
|
raise ArgumentError, ":controller segment is not allowed within a namespace block" if modyoule
|
162
235
|
|
@@ -181,74 +254,54 @@ module ActionDispatch
|
|
181
254
|
end
|
182
255
|
|
183
256
|
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
|
257
|
+
constraints.partition do |key, requirement|
|
258
|
+
path_params.include?(key) || key == :controller
|
191
259
|
end
|
192
260
|
end
|
193
261
|
|
194
|
-
def normalize_format
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
if requirement.source =~ ANCHOR_CHARACTERS_REGEX
|
208
|
-
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
|
209
|
-
end
|
210
|
-
|
211
|
-
if requirement.multiline?
|
212
|
-
raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
|
262
|
+
def normalize_format(formatted)
|
263
|
+
case formatted
|
264
|
+
when true
|
265
|
+
{ requirements: { format: /.+/ },
|
266
|
+
defaults: {} }
|
267
|
+
when Regexp
|
268
|
+
{ requirements: { format: formatted },
|
269
|
+
defaults: { format: nil } }
|
270
|
+
when String
|
271
|
+
{ requirements: { format: Regexp.compile(formatted) },
|
272
|
+
defaults: { format: formatted } }
|
273
|
+
else
|
274
|
+
{ requirements: { }, defaults: { } }
|
213
275
|
end
|
214
276
|
end
|
215
277
|
|
216
|
-
def
|
217
|
-
|
218
|
-
|
219
|
-
|
278
|
+
def verify_regexp_requirements(requirements)
|
279
|
+
requirements.each do |requirement|
|
280
|
+
if requirement.source =~ ANCHOR_CHARACTERS_REGEX
|
281
|
+
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
|
220
282
|
end
|
221
|
-
end
|
222
|
-
end
|
223
283
|
|
224
|
-
|
225
|
-
|
226
|
-
|
284
|
+
if requirement.multiline?
|
285
|
+
raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
|
286
|
+
end
|
227
287
|
end
|
228
288
|
end
|
229
289
|
|
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 }
|
290
|
+
def normalize_defaults(options)
|
291
|
+
Hash[options.reject { |_, default| Regexp === default }]
|
243
292
|
end
|
244
293
|
|
245
294
|
def app(blocks)
|
246
|
-
if to.
|
247
|
-
|
248
|
-
elsif blocks.any?
|
249
|
-
Constraints.new(dispatcher(defaults), blocks, true)
|
295
|
+
if to.is_a?(Class) && to < ActionController::Metal
|
296
|
+
Routing::RouteSet::StaticDispatcher.new to
|
250
297
|
else
|
251
|
-
|
298
|
+
if to.respond_to?(:call)
|
299
|
+
Constraints.new(to, blocks, Constraints::CALL)
|
300
|
+
elsif blocks.any?
|
301
|
+
Constraints.new(dispatcher(defaults.key?(:controller)), blocks, Constraints::SERVE)
|
302
|
+
else
|
303
|
+
dispatcher(defaults.key?(:controller))
|
304
|
+
end
|
252
305
|
end
|
253
306
|
end
|
254
307
|
|
@@ -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
|
|
@@ -371,23 +399,6 @@ module ActionDispatch
|
|
371
399
|
end
|
372
400
|
|
373
401
|
module Base
|
374
|
-
# You can specify what Rails should route "/" to with the root method:
|
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
402
|
# Matches a url pattern to one or more routes.
|
392
403
|
#
|
393
404
|
# You should not use the +match+ method in your 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
|
@@ -460,6 +471,21 @@ 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 :user, param: :name
|
477
|
+
#
|
478
|
+
# You can override <tt>ActiveRecord::Base#to_param</tt> of a related
|
479
|
+
# model to construct a URL:
|
480
|
+
#
|
481
|
+
# class User < ActiveRecord::Base
|
482
|
+
# def to_param
|
483
|
+
# name
|
484
|
+
# end
|
485
|
+
# end
|
486
|
+
#
|
487
|
+
# user = User.find_by(name: 'Phusion')
|
488
|
+
# user_path(user) # => "/users/Phusion"
|
463
489
|
#
|
464
490
|
# [:path]
|
465
491
|
# The path prefix for the routes.
|
@@ -487,7 +513,7 @@ module ActionDispatch
|
|
487
513
|
# +call+ or a string representing a controller's action.
|
488
514
|
#
|
489
515
|
# match 'path', to: 'controller#action', via: :get
|
490
|
-
# match 'path', to:
|
516
|
+
# match 'path', to: -> (env) { [200, {}, ["Success!"]] }, via: :get
|
491
517
|
# match 'path', to: RackApp, via: :get
|
492
518
|
#
|
493
519
|
# [:on]
|
@@ -568,17 +594,20 @@ module ActionDispatch
|
|
568
594
|
def mount(app, options = nil)
|
569
595
|
if options
|
570
596
|
path = options.delete(:at)
|
571
|
-
|
572
|
-
unless Hash === app
|
573
|
-
raise ArgumentError, "must be called with mount point"
|
574
|
-
end
|
575
|
-
|
597
|
+
elsif Hash === app
|
576
598
|
options = app
|
577
599
|
app, path = options.find { |k, _| k.respond_to?(:call) }
|
578
600
|
options.delete(app) if app
|
579
601
|
end
|
580
602
|
|
581
|
-
raise "A rack application must be specified" unless
|
603
|
+
raise ArgumentError, "A rack application must be specified" unless app.respond_to?(:call)
|
604
|
+
raise ArgumentError, <<-MSG.strip_heredoc unless path
|
605
|
+
Must be called with mount point
|
606
|
+
|
607
|
+
mount SomeRackApp, at: "some_route"
|
608
|
+
or
|
609
|
+
mount(SomeRackApp => "some_route")
|
610
|
+
MSG
|
582
611
|
|
583
612
|
rails_app = rails_app? app
|
584
613
|
options[:as] ||= app_name(app, rails_app)
|
@@ -605,7 +634,7 @@ module ActionDispatch
|
|
605
634
|
|
606
635
|
# Query if the following named route was already defined.
|
607
636
|
def has_named_route?(name)
|
608
|
-
@set.named_routes.
|
637
|
+
@set.named_routes.key? name
|
609
638
|
end
|
610
639
|
|
611
640
|
private
|
@@ -795,21 +824,30 @@ module ActionDispatch
|
|
795
824
|
URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Integer))
|
796
825
|
end
|
797
826
|
|
798
|
-
(options[:defaults]
|
827
|
+
options[:defaults] = defaults.merge(options[:defaults] || {})
|
799
828
|
else
|
800
829
|
block, options[:constraints] = options[:constraints], {}
|
801
830
|
end
|
802
831
|
|
832
|
+
if options.key?(:only) || options.key?(:except)
|
833
|
+
scope[:action_options] = { only: options.delete(:only),
|
834
|
+
except: options.delete(:except) }
|
835
|
+
end
|
836
|
+
|
837
|
+
if options.key? :anchor
|
838
|
+
raise ArgumentError, 'anchor is ignored unless passed to `match`'
|
839
|
+
end
|
840
|
+
|
803
841
|
@scope.options.each do |option|
|
804
842
|
if option == :blocks
|
805
843
|
value = block
|
806
844
|
elsif option == :options
|
807
845
|
value = options
|
808
846
|
else
|
809
|
-
value = options.delete(option)
|
847
|
+
value = options.delete(option) { POISON }
|
810
848
|
end
|
811
849
|
|
812
|
-
|
850
|
+
unless POISON == value
|
813
851
|
scope[option] = send("merge_#{option}_scope", @scope[option], value)
|
814
852
|
end
|
815
853
|
end
|
@@ -821,14 +859,27 @@ module ActionDispatch
|
|
821
859
|
@scope = @scope.parent
|
822
860
|
end
|
823
861
|
|
862
|
+
POISON = Object.new # :nodoc:
|
863
|
+
|
824
864
|
# Scopes routes to a specific controller
|
825
865
|
#
|
826
866
|
# controller "food" do
|
827
|
-
# match "bacon", action:
|
867
|
+
# match "bacon", action: :bacon, via: :get
|
828
868
|
# end
|
829
|
-
def controller(controller, options={})
|
830
|
-
options
|
831
|
-
|
869
|
+
def controller(controller, options = {})
|
870
|
+
if options.empty?
|
871
|
+
begin
|
872
|
+
@scope = @scope.new(controller: controller)
|
873
|
+
yield
|
874
|
+
ensure
|
875
|
+
@scope = @scope.parent
|
876
|
+
end
|
877
|
+
else
|
878
|
+
ActiveSupport::Deprecation.warn "#controller with options is deprecated. If you need to pass more options than the controller name use #scope."
|
879
|
+
|
880
|
+
options[:controller] = controller
|
881
|
+
scope(options) { yield }
|
882
|
+
end
|
832
883
|
end
|
833
884
|
|
834
885
|
# Scopes routes to a specific namespace. For example:
|
@@ -874,13 +925,14 @@ module ActionDispatch
|
|
874
925
|
|
875
926
|
defaults = {
|
876
927
|
module: path,
|
877
|
-
path: options.fetch(:path, path),
|
878
928
|
as: options.fetch(:as, path),
|
879
929
|
shallow_path: options.fetch(:path, path),
|
880
930
|
shallow_prefix: options.fetch(:as, path)
|
881
931
|
}
|
882
932
|
|
883
|
-
|
933
|
+
path_scope(options.delete(:path) { path }) do
|
934
|
+
scope(defaults.merge!(options)) { yield }
|
935
|
+
end
|
884
936
|
end
|
885
937
|
|
886
938
|
# === Parameter Restriction
|
@@ -917,7 +969,7 @@ module ActionDispatch
|
|
917
969
|
#
|
918
970
|
# Requests to routes can be constrained based on specific criteria:
|
919
971
|
#
|
920
|
-
# constraints(
|
972
|
+
# constraints(-> (req) { req.env["HTTP_USER_AGENT"] =~ /iPhone/ }) do
|
921
973
|
# resources :iphones
|
922
974
|
# end
|
923
975
|
#
|
@@ -948,7 +1000,10 @@ module ActionDispatch
|
|
948
1000
|
# end
|
949
1001
|
# Using this, the +:id+ parameter here will default to 'home'.
|
950
1002
|
def defaults(defaults = {})
|
951
|
-
scope(:defaults
|
1003
|
+
@scope = @scope.new(defaults: merge_defaults_scope(@scope[:defaults], defaults))
|
1004
|
+
yield
|
1005
|
+
ensure
|
1006
|
+
@scope = @scope.parent
|
952
1007
|
end
|
953
1008
|
|
954
1009
|
private
|
@@ -980,6 +1035,14 @@ module ActionDispatch
|
|
980
1035
|
child
|
981
1036
|
end
|
982
1037
|
|
1038
|
+
def merge_via_scope(parent, child) #:nodoc:
|
1039
|
+
child
|
1040
|
+
end
|
1041
|
+
|
1042
|
+
def merge_format_scope(parent, child) #:nodoc:
|
1043
|
+
child
|
1044
|
+
end
|
1045
|
+
|
983
1046
|
def merge_path_names_scope(parent, child) #:nodoc:
|
984
1047
|
merge_options_scope(parent, child)
|
985
1048
|
end
|
@@ -999,15 +1062,15 @@ module ActionDispatch
|
|
999
1062
|
end
|
1000
1063
|
|
1001
1064
|
def merge_options_scope(parent, child) #:nodoc:
|
1002
|
-
(parent || {}).
|
1065
|
+
(parent || {}).merge(child)
|
1003
1066
|
end
|
1004
1067
|
|
1005
1068
|
def merge_shallow_scope(parent, child) #:nodoc:
|
1006
1069
|
child ? true : false
|
1007
1070
|
end
|
1008
1071
|
|
1009
|
-
def
|
1010
|
-
child
|
1072
|
+
def merge_to_scope(parent, child)
|
1073
|
+
child
|
1011
1074
|
end
|
1012
1075
|
end
|
1013
1076
|
|
@@ -1058,27 +1121,34 @@ module ActionDispatch
|
|
1058
1121
|
CANONICAL_ACTIONS = %w(index create new show update destroy)
|
1059
1122
|
|
1060
1123
|
class Resource #:nodoc:
|
1061
|
-
attr_reader :controller, :path, :
|
1124
|
+
attr_reader :controller, :path, :param
|
1062
1125
|
|
1063
|
-
def initialize(entities, options = {})
|
1126
|
+
def initialize(entities, api_only, shallow, options = {})
|
1064
1127
|
@name = entities.to_s
|
1065
1128
|
@path = (options[:path] || @name).to_s
|
1066
1129
|
@controller = (options[:controller] || @name).to_s
|
1067
1130
|
@as = options[:as]
|
1068
1131
|
@param = (options[:param] || :id).to_sym
|
1069
1132
|
@options = options
|
1070
|
-
@shallow =
|
1133
|
+
@shallow = shallow
|
1134
|
+
@api_only = api_only
|
1135
|
+
@only = options.delete :only
|
1136
|
+
@except = options.delete :except
|
1071
1137
|
end
|
1072
1138
|
|
1073
1139
|
def default_actions
|
1074
|
-
|
1140
|
+
if @api_only
|
1141
|
+
[:index, :create, :show, :update, :destroy]
|
1142
|
+
else
|
1143
|
+
[:index, :create, :new, :show, :update, :destroy, :edit]
|
1144
|
+
end
|
1075
1145
|
end
|
1076
1146
|
|
1077
1147
|
def actions
|
1078
|
-
if
|
1079
|
-
Array(only).map(&:to_sym)
|
1080
|
-
elsif
|
1081
|
-
default_actions - Array(except).map(&:to_sym)
|
1148
|
+
if @only
|
1149
|
+
Array(@only).map(&:to_sym)
|
1150
|
+
elsif @except
|
1151
|
+
default_actions - Array(@except).map(&:to_sym)
|
1082
1152
|
else
|
1083
1153
|
default_actions
|
1084
1154
|
end
|
@@ -1105,7 +1175,7 @@ module ActionDispatch
|
|
1105
1175
|
end
|
1106
1176
|
|
1107
1177
|
def resource_scope
|
1108
|
-
|
1178
|
+
controller
|
1109
1179
|
end
|
1110
1180
|
|
1111
1181
|
alias :collection_scope :path
|
@@ -1128,17 +1198,15 @@ module ActionDispatch
|
|
1128
1198
|
"#{path}/:#{nested_param}"
|
1129
1199
|
end
|
1130
1200
|
|
1131
|
-
def shallow=(value)
|
1132
|
-
@shallow = value
|
1133
|
-
end
|
1134
|
-
|
1135
1201
|
def shallow?
|
1136
1202
|
@shallow
|
1137
1203
|
end
|
1204
|
+
|
1205
|
+
def singleton?; false; end
|
1138
1206
|
end
|
1139
1207
|
|
1140
1208
|
class SingletonResource < Resource #:nodoc:
|
1141
|
-
def initialize(entities, options)
|
1209
|
+
def initialize(entities, api_only, shallow, options)
|
1142
1210
|
super
|
1143
1211
|
@as = nil
|
1144
1212
|
@controller = (options[:controller] || plural).to_s
|
@@ -1146,7 +1214,11 @@ module ActionDispatch
|
|
1146
1214
|
end
|
1147
1215
|
|
1148
1216
|
def default_actions
|
1149
|
-
|
1217
|
+
if @api_only
|
1218
|
+
[:show, :create, :update, :destroy]
|
1219
|
+
else
|
1220
|
+
[:show, :create, :update, :destroy, :new, :edit]
|
1221
|
+
end
|
1150
1222
|
end
|
1151
1223
|
|
1152
1224
|
def plural
|
@@ -1162,6 +1234,8 @@ module ActionDispatch
|
|
1162
1234
|
|
1163
1235
|
alias :member_scope :path
|
1164
1236
|
alias :nested_scope :path
|
1237
|
+
|
1238
|
+
def singleton?; true; end
|
1165
1239
|
end
|
1166
1240
|
|
1167
1241
|
def resources_path_names(options)
|
@@ -1181,11 +1255,11 @@ module ActionDispatch
|
|
1181
1255
|
# the plural):
|
1182
1256
|
#
|
1183
1257
|
# GET /profile/new
|
1184
|
-
# POST /profile
|
1185
1258
|
# GET /profile
|
1186
1259
|
# GET /profile/edit
|
1187
1260
|
# PATCH/PUT /profile
|
1188
1261
|
# DELETE /profile
|
1262
|
+
# POST /profile
|
1189
1263
|
#
|
1190
1264
|
# === Options
|
1191
1265
|
# Takes same options as +resources+.
|
@@ -1196,20 +1270,23 @@ module ActionDispatch
|
|
1196
1270
|
return self
|
1197
1271
|
end
|
1198
1272
|
|
1199
|
-
|
1200
|
-
|
1273
|
+
with_scope_level(:resource) do
|
1274
|
+
options = apply_action_options options
|
1275
|
+
resource_scope(SingletonResource.new(resources.pop, api_only?, @scope[:shallow], options)) do
|
1276
|
+
yield if block_given?
|
1201
1277
|
|
1202
|
-
|
1278
|
+
concerns(options[:concerns]) if options[:concerns]
|
1203
1279
|
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1280
|
+
new do
|
1281
|
+
get :new
|
1282
|
+
end if parent_resource.actions.include?(:new)
|
1207
1283
|
|
1208
|
-
|
1209
|
-
get :new
|
1210
|
-
end if parent_resource.actions.include?(:new)
|
1284
|
+
set_member_mappings_for_resource
|
1211
1285
|
|
1212
|
-
|
1286
|
+
collection do
|
1287
|
+
post :create
|
1288
|
+
end if parent_resource.actions.include?(:create)
|
1289
|
+
end
|
1213
1290
|
end
|
1214
1291
|
|
1215
1292
|
self
|
@@ -1354,21 +1431,24 @@ module ActionDispatch
|
|
1354
1431
|
return self
|
1355
1432
|
end
|
1356
1433
|
|
1357
|
-
|
1358
|
-
|
1434
|
+
with_scope_level(:resources) do
|
1435
|
+
options = apply_action_options options
|
1436
|
+
resource_scope(Resource.new(resources.pop, api_only?, @scope[:shallow], options)) do
|
1437
|
+
yield if block_given?
|
1359
1438
|
|
1360
|
-
|
1439
|
+
concerns(options[:concerns]) if options[:concerns]
|
1361
1440
|
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1365
|
-
|
1441
|
+
collection do
|
1442
|
+
get :index if parent_resource.actions.include?(:index)
|
1443
|
+
post :create if parent_resource.actions.include?(:create)
|
1444
|
+
end
|
1366
1445
|
|
1367
|
-
|
1368
|
-
|
1369
|
-
|
1446
|
+
new do
|
1447
|
+
get :new
|
1448
|
+
end if parent_resource.actions.include?(:new)
|
1370
1449
|
|
1371
|
-
|
1450
|
+
set_member_mappings_for_resource
|
1451
|
+
end
|
1372
1452
|
end
|
1373
1453
|
|
1374
1454
|
self
|
@@ -1392,7 +1472,7 @@ module ActionDispatch
|
|
1392
1472
|
end
|
1393
1473
|
|
1394
1474
|
with_scope_level(:collection) do
|
1395
|
-
|
1475
|
+
path_scope(parent_resource.collection_scope) do
|
1396
1476
|
yield
|
1397
1477
|
end
|
1398
1478
|
end
|
@@ -1416,9 +1496,11 @@ module ActionDispatch
|
|
1416
1496
|
|
1417
1497
|
with_scope_level(:member) do
|
1418
1498
|
if shallow?
|
1419
|
-
shallow_scope
|
1499
|
+
shallow_scope {
|
1500
|
+
path_scope(parent_resource.member_scope) { yield }
|
1501
|
+
}
|
1420
1502
|
else
|
1421
|
-
|
1503
|
+
path_scope(parent_resource.member_scope) { yield }
|
1422
1504
|
end
|
1423
1505
|
end
|
1424
1506
|
end
|
@@ -1429,7 +1511,7 @@ module ActionDispatch
|
|
1429
1511
|
end
|
1430
1512
|
|
1431
1513
|
with_scope_level(:new) do
|
1432
|
-
|
1514
|
+
path_scope(parent_resource.new_scope(action_path(:new))) do
|
1433
1515
|
yield
|
1434
1516
|
end
|
1435
1517
|
end
|
@@ -1442,9 +1524,15 @@ module ActionDispatch
|
|
1442
1524
|
|
1443
1525
|
with_scope_level(:nested) do
|
1444
1526
|
if shallow? && shallow_nesting_depth >= 1
|
1445
|
-
shallow_scope
|
1527
|
+
shallow_scope do
|
1528
|
+
path_scope(parent_resource.nested_scope) do
|
1529
|
+
scope(nested_options) { yield }
|
1530
|
+
end
|
1531
|
+
end
|
1446
1532
|
else
|
1447
|
-
|
1533
|
+
path_scope(parent_resource.nested_scope) do
|
1534
|
+
scope(nested_options) { yield }
|
1535
|
+
end
|
1448
1536
|
end
|
1449
1537
|
end
|
1450
1538
|
end
|
@@ -1459,23 +1547,33 @@ module ActionDispatch
|
|
1459
1547
|
end
|
1460
1548
|
|
1461
1549
|
def shallow
|
1462
|
-
scope(:
|
1463
|
-
|
1464
|
-
|
1550
|
+
@scope = @scope.new(shallow: true)
|
1551
|
+
yield
|
1552
|
+
ensure
|
1553
|
+
@scope = @scope.parent
|
1465
1554
|
end
|
1466
1555
|
|
1467
1556
|
def shallow?
|
1468
|
-
parent_resource.
|
1557
|
+
!parent_resource.singleton? && @scope[:shallow]
|
1469
1558
|
end
|
1470
1559
|
|
1471
|
-
#
|
1472
|
-
#
|
1473
|
-
#
|
1474
|
-
|
1560
|
+
# Matches a url pattern to one or more routes.
|
1561
|
+
# For more information, see match[rdoc-ref:Base#match].
|
1562
|
+
#
|
1563
|
+
# match 'path' => 'controller#action', via: patch
|
1564
|
+
# match 'path', to: 'controller#action', via: :post
|
1565
|
+
# match 'path', 'otherpath', on: :member, via: :get
|
1566
|
+
def match(path, *rest, &block)
|
1475
1567
|
if rest.empty? && Hash === path
|
1476
1568
|
options = path
|
1477
1569
|
path, to = options.find { |name, _value| name.is_a?(String) }
|
1478
1570
|
|
1571
|
+
if path.nil?
|
1572
|
+
ActiveSupport::Deprecation.warn 'Omitting the route path is deprecated. '\
|
1573
|
+
'Specify the path with a String or a Symbol instead.'
|
1574
|
+
path = ''
|
1575
|
+
end
|
1576
|
+
|
1479
1577
|
case to
|
1480
1578
|
when Symbol
|
1481
1579
|
options[:action] = to
|
@@ -1496,58 +1594,53 @@ module ActionDispatch
|
|
1496
1594
|
paths = [path] + rest
|
1497
1595
|
end
|
1498
1596
|
|
1499
|
-
|
1500
|
-
|
1501
|
-
|
1502
|
-
|
1503
|
-
end
|
1504
|
-
|
1505
|
-
if @scope[:controller] && @scope[:action]
|
1506
|
-
options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
|
1597
|
+
if options.key?(:defaults)
|
1598
|
+
defaults(options.delete(:defaults)) { map_match(paths, options, &block) }
|
1599
|
+
else
|
1600
|
+
map_match(paths, options, &block)
|
1507
1601
|
end
|
1602
|
+
end
|
1508
1603
|
|
1509
|
-
|
1510
|
-
|
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
|
1604
|
+
def get_to_from_path(path, to, action)
|
1605
|
+
return to if to || action
|
1518
1606
|
|
1519
|
-
|
1607
|
+
path_without_format = path.sub(/\(\.:format\)$/, '')
|
1608
|
+
if using_match_shorthand?(path_without_format)
|
1609
|
+
path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1').tr("-", "_")
|
1610
|
+
else
|
1611
|
+
nil
|
1520
1612
|
end
|
1521
|
-
self
|
1522
1613
|
end
|
1523
1614
|
|
1524
|
-
def using_match_shorthand?(path
|
1525
|
-
path
|
1615
|
+
def using_match_shorthand?(path)
|
1616
|
+
path =~ %r{^/?[-\w]+/[-\w/]+$}
|
1526
1617
|
end
|
1527
1618
|
|
1528
|
-
def decomposed_match(path, options) # :nodoc:
|
1619
|
+
def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc:
|
1529
1620
|
if on = options.delete(:on)
|
1530
|
-
send(on) { decomposed_match(path, options) }
|
1621
|
+
send(on) { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
|
1531
1622
|
else
|
1532
1623
|
case @scope.scope_level
|
1533
1624
|
when :resources
|
1534
|
-
nested { decomposed_match(path, options) }
|
1625
|
+
nested { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
|
1535
1626
|
when :resource
|
1536
|
-
member { decomposed_match(path, options) }
|
1627
|
+
member { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
|
1537
1628
|
else
|
1538
|
-
add_route(path, options)
|
1629
|
+
add_route(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
|
1539
1630
|
end
|
1540
1631
|
end
|
1541
1632
|
end
|
1542
1633
|
|
1543
|
-
def add_route(action, options) # :nodoc:
|
1544
|
-
path = path_for_action(action,
|
1634
|
+
def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc:
|
1635
|
+
path = path_for_action(action, _path)
|
1545
1636
|
raise ArgumentError, "path is required" if path.blank?
|
1546
1637
|
|
1547
|
-
action = action.to_s
|
1638
|
+
action = action.to_s
|
1639
|
+
|
1640
|
+
default_action = options.delete(:action) || @scope[:action]
|
1548
1641
|
|
1549
1642
|
if action =~ /^[\w\-\/]+$/
|
1550
|
-
|
1643
|
+
default_action ||= action.tr('-', '_') unless action.include?("/")
|
1551
1644
|
else
|
1552
1645
|
action = nil
|
1553
1646
|
end
|
@@ -1558,12 +1651,27 @@ module ActionDispatch
|
|
1558
1651
|
name_for_action(options.delete(:as), action)
|
1559
1652
|
end
|
1560
1653
|
|
1561
|
-
|
1562
|
-
|
1563
|
-
|
1654
|
+
path = Mapping.normalize_path URI.parser.escape(path), formatted
|
1655
|
+
ast = Journey::Parser.parse path
|
1656
|
+
|
1657
|
+
mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
|
1658
|
+
@set.add_route(mapping, ast, as, anchor)
|
1564
1659
|
end
|
1565
1660
|
|
1566
|
-
|
1661
|
+
# You can specify what Rails should route "/" to with the root method:
|
1662
|
+
#
|
1663
|
+
# root to: 'pages#main'
|
1664
|
+
#
|
1665
|
+
# For options, see +match+, as +root+ uses it internally.
|
1666
|
+
#
|
1667
|
+
# You can also pass a string which will expand
|
1668
|
+
#
|
1669
|
+
# root 'pages#main'
|
1670
|
+
#
|
1671
|
+
# You should put the root route at the top of <tt>config/routes.rb</tt>,
|
1672
|
+
# because this means it will be matched first. As this is the most popular route
|
1673
|
+
# of most Rails applications, this is beneficial.
|
1674
|
+
def root(path, options = {})
|
1567
1675
|
if path.is_a?(String)
|
1568
1676
|
options[:to] = path
|
1569
1677
|
elsif path.is_a?(Hash) and options.empty?
|
@@ -1574,12 +1682,12 @@ module ActionDispatch
|
|
1574
1682
|
|
1575
1683
|
if @scope.resources?
|
1576
1684
|
with_scope_level(:root) do
|
1577
|
-
|
1578
|
-
|
1685
|
+
path_scope(parent_resource.path) do
|
1686
|
+
match_root_route(options)
|
1579
1687
|
end
|
1580
1688
|
end
|
1581
1689
|
else
|
1582
|
-
|
1690
|
+
match_root_route(options)
|
1583
1691
|
end
|
1584
1692
|
end
|
1585
1693
|
|
@@ -1619,23 +1727,20 @@ module ActionDispatch
|
|
1619
1727
|
return true
|
1620
1728
|
end
|
1621
1729
|
|
1622
|
-
unless action_options?(options)
|
1623
|
-
options.merge!(scope_action_options) if scope_action_options?
|
1624
|
-
end
|
1625
|
-
|
1626
1730
|
false
|
1627
1731
|
end
|
1628
1732
|
|
1629
|
-
def
|
1630
|
-
options
|
1733
|
+
def apply_action_options(options) # :nodoc:
|
1734
|
+
return options if action_options? options
|
1735
|
+
options.merge scope_action_options
|
1631
1736
|
end
|
1632
1737
|
|
1633
|
-
def
|
1634
|
-
|
1738
|
+
def action_options?(options) #:nodoc:
|
1739
|
+
options[:only] || options[:except]
|
1635
1740
|
end
|
1636
1741
|
|
1637
1742
|
def scope_action_options #:nodoc:
|
1638
|
-
@scope[:
|
1743
|
+
@scope[:action_options] || {}
|
1639
1744
|
end
|
1640
1745
|
|
1641
1746
|
def resource_scope? #:nodoc:
|
@@ -1650,18 +1755,6 @@ module ActionDispatch
|
|
1650
1755
|
@scope.nested?
|
1651
1756
|
end
|
1652
1757
|
|
1653
|
-
def with_exclusive_scope
|
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
1758
|
def with_scope_level(kind)
|
1666
1759
|
@scope = @scope.new_level(kind)
|
1667
1760
|
yield
|
@@ -1669,16 +1762,11 @@ module ActionDispatch
|
|
1669
1762
|
@scope = @scope.parent
|
1670
1763
|
end
|
1671
1764
|
|
1672
|
-
def resource_scope(
|
1673
|
-
resource.shallow = @scope[:shallow]
|
1765
|
+
def resource_scope(resource) #:nodoc:
|
1674
1766
|
@scope = @scope.new(:scope_level_resource => resource)
|
1675
|
-
@nesting.push(resource)
|
1676
1767
|
|
1677
|
-
|
1678
|
-
scope(parent_resource.resource_scope) { yield }
|
1679
|
-
end
|
1768
|
+
controller(resource.resource_scope) { yield }
|
1680
1769
|
ensure
|
1681
|
-
@nesting.pop
|
1682
1770
|
@scope = @scope.parent
|
1683
1771
|
end
|
1684
1772
|
|
@@ -1691,12 +1779,10 @@ module ActionDispatch
|
|
1691
1779
|
options
|
1692
1780
|
end
|
1693
1781
|
|
1694
|
-
def nesting_depth #:nodoc:
|
1695
|
-
@nesting.size
|
1696
|
-
end
|
1697
|
-
|
1698
1782
|
def shallow_nesting_depth #:nodoc:
|
1699
|
-
@
|
1783
|
+
@scope.find_all { |node|
|
1784
|
+
node.frame[:scope_level_resource]
|
1785
|
+
}.count { |node| node.frame[:scope_level_resource].shallow? }
|
1700
1786
|
end
|
1701
1787
|
|
1702
1788
|
def param_constraint? #:nodoc:
|
@@ -1711,27 +1797,28 @@ module ActionDispatch
|
|
1711
1797
|
resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
|
1712
1798
|
end
|
1713
1799
|
|
1714
|
-
def shallow_scope
|
1800
|
+
def shallow_scope #:nodoc:
|
1715
1801
|
scope = { :as => @scope[:shallow_prefix],
|
1716
1802
|
:path => @scope[:shallow_path] }
|
1717
1803
|
@scope = @scope.new scope
|
1718
1804
|
|
1719
|
-
|
1805
|
+
yield
|
1720
1806
|
ensure
|
1721
1807
|
@scope = @scope.parent
|
1722
1808
|
end
|
1723
1809
|
|
1724
1810
|
def path_for_action(action, path) #:nodoc:
|
1725
|
-
|
1811
|
+
return "#{@scope[:path]}/#{path}" if path
|
1812
|
+
|
1813
|
+
if canonical_action?(action)
|
1726
1814
|
@scope[:path].to_s
|
1727
1815
|
else
|
1728
|
-
"#{@scope[:path]}/#{action_path(action
|
1816
|
+
"#{@scope[:path]}/#{action_path(action)}"
|
1729
1817
|
end
|
1730
1818
|
end
|
1731
1819
|
|
1732
|
-
def action_path(name
|
1733
|
-
name
|
1734
|
-
path || @scope[:path_names][name] || name.to_s
|
1820
|
+
def action_path(name) #:nodoc:
|
1821
|
+
@scope[:path_names][name.to_sym] || name
|
1735
1822
|
end
|
1736
1823
|
|
1737
1824
|
def prefix_name_for_action(as, action) #:nodoc:
|
@@ -1757,14 +1844,15 @@ module ActionDispatch
|
|
1757
1844
|
member_name = parent_resource.member_name
|
1758
1845
|
end
|
1759
1846
|
|
1760
|
-
|
1847
|
+
action_name = @scope.action_name(name_prefix, prefix, collection_name, member_name)
|
1848
|
+
candidate = action_name.select(&:present?).join('_')
|
1761
1849
|
|
1762
|
-
|
1850
|
+
unless candidate.empty?
|
1763
1851
|
# If a name was not explicitly given, we check if it is valid
|
1764
1852
|
# and return nil in case it isn't. Otherwise, we pass the invalid name
|
1765
1853
|
# forward so the underlying router engine treats it and raises an exception.
|
1766
1854
|
if as.nil?
|
1767
|
-
candidate unless candidate !~ /\A[_a-z]/i ||
|
1855
|
+
candidate unless candidate !~ /\A[_a-z]/i || has_named_route?(candidate)
|
1768
1856
|
else
|
1769
1857
|
candidate
|
1770
1858
|
end
|
@@ -1782,6 +1870,78 @@ module ActionDispatch
|
|
1782
1870
|
delete :destroy if parent_resource.actions.include?(:destroy)
|
1783
1871
|
end
|
1784
1872
|
end
|
1873
|
+
|
1874
|
+
def api_only?
|
1875
|
+
@set.api_only?
|
1876
|
+
end
|
1877
|
+
|
1878
|
+
private
|
1879
|
+
|
1880
|
+
def path_scope(path)
|
1881
|
+
@scope = @scope.new(path: merge_path_scope(@scope[:path], path))
|
1882
|
+
yield
|
1883
|
+
ensure
|
1884
|
+
@scope = @scope.parent
|
1885
|
+
end
|
1886
|
+
|
1887
|
+
def map_match(paths, options)
|
1888
|
+
if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
|
1889
|
+
raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
|
1890
|
+
end
|
1891
|
+
|
1892
|
+
if @scope[:to]
|
1893
|
+
options[:to] ||= @scope[:to]
|
1894
|
+
end
|
1895
|
+
|
1896
|
+
if @scope[:controller] && @scope[:action]
|
1897
|
+
options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
|
1898
|
+
end
|
1899
|
+
|
1900
|
+
controller = options.delete(:controller) || @scope[:controller]
|
1901
|
+
option_path = options.delete :path
|
1902
|
+
to = options.delete :to
|
1903
|
+
via = Mapping.check_via Array(options.delete(:via) {
|
1904
|
+
@scope[:via]
|
1905
|
+
})
|
1906
|
+
formatted = options.delete(:format) { @scope[:format] }
|
1907
|
+
anchor = options.delete(:anchor) { true }
|
1908
|
+
options_constraints = options.delete(:constraints) || {}
|
1909
|
+
|
1910
|
+
path_types = paths.group_by(&:class)
|
1911
|
+
path_types.fetch(String, []).each do |_path|
|
1912
|
+
route_options = options.dup
|
1913
|
+
if _path && option_path
|
1914
|
+
ActiveSupport::Deprecation.warn <<-eowarn
|
1915
|
+
Specifying strings for both :path and the route path is deprecated. Change things like this:
|
1916
|
+
|
1917
|
+
match #{_path.inspect}, :path => #{option_path.inspect}
|
1918
|
+
|
1919
|
+
to this:
|
1920
|
+
|
1921
|
+
match #{option_path.inspect}, :as => #{_path.inspect}, :action => #{_path.inspect}
|
1922
|
+
eowarn
|
1923
|
+
route_options[:action] = _path
|
1924
|
+
route_options[:as] = _path
|
1925
|
+
_path = option_path
|
1926
|
+
end
|
1927
|
+
to = get_to_from_path(_path, to, route_options[:action])
|
1928
|
+
decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints)
|
1929
|
+
end
|
1930
|
+
|
1931
|
+
path_types.fetch(Symbol, []).each do |action|
|
1932
|
+
route_options = options.dup
|
1933
|
+
decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints)
|
1934
|
+
end
|
1935
|
+
|
1936
|
+
self
|
1937
|
+
end
|
1938
|
+
|
1939
|
+
def match_root_route(options)
|
1940
|
+
name = has_named_route?(name_for_action(:root, nil)) ? nil : :root
|
1941
|
+
args = ["/", { as: name, via: :get }.merge!(options)]
|
1942
|
+
|
1943
|
+
match(*args)
|
1944
|
+
end
|
1785
1945
|
end
|
1786
1946
|
|
1787
1947
|
# Routing Concerns allow you to declare common routes that can be reused
|
@@ -1892,14 +2052,14 @@ module ActionDispatch
|
|
1892
2052
|
class Scope # :nodoc:
|
1893
2053
|
OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
|
1894
2054
|
:controller, :action, :path_names, :constraints,
|
1895
|
-
:shallow, :blocks, :defaults, :options]
|
2055
|
+
:shallow, :blocks, :defaults, :via, :format, :options, :to]
|
1896
2056
|
|
1897
2057
|
RESOURCE_SCOPES = [:resource, :resources]
|
1898
2058
|
RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
|
1899
2059
|
|
1900
2060
|
attr_reader :parent, :scope_level
|
1901
2061
|
|
1902
|
-
def initialize(hash, parent =
|
2062
|
+
def initialize(hash, parent = NULL, scope_level = nil)
|
1903
2063
|
@hash = hash
|
1904
2064
|
@parent = parent
|
1905
2065
|
@scope_level = scope_level
|
@@ -1947,27 +2107,34 @@ module ActionDispatch
|
|
1947
2107
|
end
|
1948
2108
|
|
1949
2109
|
def new_level(level)
|
1950
|
-
self.class.new(
|
1951
|
-
end
|
1952
|
-
|
1953
|
-
def fetch(key, &block)
|
1954
|
-
@hash.fetch(key, &block)
|
2110
|
+
self.class.new(frame, self, level)
|
1955
2111
|
end
|
1956
2112
|
|
1957
2113
|
def [](key)
|
1958
|
-
|
2114
|
+
scope = find { |node| node.frame.key? key }
|
2115
|
+
scope && scope.frame[key]
|
1959
2116
|
end
|
1960
2117
|
|
1961
|
-
|
1962
|
-
|
2118
|
+
include Enumerable
|
2119
|
+
|
2120
|
+
def each
|
2121
|
+
node = self
|
2122
|
+
loop do
|
2123
|
+
break if node.equal? NULL
|
2124
|
+
yield node
|
2125
|
+
node = node.parent
|
2126
|
+
end
|
1963
2127
|
end
|
2128
|
+
|
2129
|
+
def frame; @hash; end
|
2130
|
+
|
2131
|
+
NULL = Scope.new(nil, nil)
|
1964
2132
|
end
|
1965
2133
|
|
1966
2134
|
def initialize(set) #:nodoc:
|
1967
2135
|
@set = set
|
1968
2136
|
@scope = Scope.new({ :path_names => @set.resources_path_names })
|
1969
2137
|
@concerns = {}
|
1970
|
-
@nesting = []
|
1971
2138
|
end
|
1972
2139
|
|
1973
2140
|
include Base
|