actionpack 4.2.10 → 5.0.0
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 +553 -401
- 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 +52 -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 +10 -13
- 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 +11 -32
- data/lib/action_controller/metal/etag_with_template_digest.rb +1 -1
- data/lib/action_controller/metal/exceptions.rb +11 -6
- data/lib/action_controller/metal/force_ssl.rb +10 -10
- 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 +66 -88
- data/lib/action_controller/metal/mime_responds.rb +27 -42
- data/lib/action_controller/metal/params_wrapper.rb +8 -8
- data/lib/action_controller/metal/redirecting.rb +32 -9
- data/lib/action_controller/metal/renderers.rb +85 -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 +293 -90
- 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/renderer.rb +111 -0
- data/lib/action_controller/template_assertions.rb +9 -0
- data/lib/action_controller/test_case.rb +288 -368
- data/lib/action_controller.rb +12 -9
- data/lib/action_dispatch/http/cache.rb +73 -34
- data/lib/action_dispatch/http/filter_parameters.rb +15 -11
- data/lib/action_dispatch/http/filter_redirect.rb +7 -8
- data/lib/action_dispatch/http/headers.rb +44 -13
- data/lib/action_dispatch/http/mime_negotiation.rb +41 -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 +18 -8
- data/lib/action_dispatch/http/parameters.rb +54 -41
- data/lib/action_dispatch/http/request.rb +149 -82
- data/lib/action_dispatch/http/response.rb +206 -102
- data/lib/action_dispatch/http/url.rb +117 -8
- data/lib/action_dispatch/journey/formatter.rb +39 -28
- 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_extras.rb +4 -0
- data/lib/action_dispatch/journey/path/pattern.rb +38 -42
- data/lib/action_dispatch/journey/route.rb +74 -19
- data/lib/action_dispatch/journey/router/utils.rb +5 -5
- data/lib/action_dispatch/journey/router.rb +5 -9
- data/lib/action_dispatch/journey/routes.rb +14 -15
- data/lib/action_dispatch/journey/visitors.rb +86 -43
- data/lib/action_dispatch/middleware/callbacks.rb +10 -1
- data/lib/action_dispatch/middleware/cookies.rb +189 -135
- data/lib/action_dispatch/middleware/debug_exceptions.rb +124 -49
- 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 +115 -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 +491 -338
- data/lib/action_dispatch/routing/polymorphic_routes.rb +8 -14
- data/lib/action_dispatch/routing/redirection.rb +3 -3
- data/lib/action_dispatch/routing/route_set.rb +145 -238
- 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 +11 -10
- data/lib/action_dispatch/testing/assertions.rb +1 -1
- data/lib/action_dispatch/testing/integration.rb +368 -97
- data/lib/action_dispatch/testing/test_process.rb +5 -6
- data/lib/action_dispatch/testing/test_request.rb +22 -31
- data/lib/action_dispatch/testing/test_response.rb +7 -4
- data/lib/action_dispatch.rb +3 -1
- data/lib/action_pack/gem_version.rb +3 -3
- data/lib/action_pack.rb +1 -1
- metadata +30 -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
|
@@ -60,103 +54,181 @@ module ActionDispatch
|
|
60
54
|
|
61
55
|
class Mapping #:nodoc:
|
62
56
|
ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
|
63
|
-
OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z}
|
64
57
|
|
65
|
-
attr_reader :requirements, :
|
66
|
-
attr_reader :to, :default_controller, :default_action
|
58
|
+
attr_reader :requirements, :defaults
|
59
|
+
attr_reader :to, :default_controller, :default_action
|
60
|
+
attr_reader :required_defaults, :ast
|
67
61
|
|
68
|
-
def self.build(scope, set,
|
62
|
+
def self.build(scope, set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
|
69
63
|
options = scope[:options].merge(options) if scope[:options]
|
70
64
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
65
|
+
defaults = (scope[:defaults] || {}).dup
|
66
|
+
scope_constraints = scope[:constraints] || {}
|
67
|
+
|
68
|
+
new set, ast, defaults, controller, default_action, scope[:module], to, formatted, scope_constraints, scope[:blocks] || [], via, options_constraints, anchor, options
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.check_via(via)
|
72
|
+
if via.empty?
|
73
|
+
msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
|
74
|
+
"If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
|
75
|
+
"If you want to expose your action to GET, use `get` in the router:\n" \
|
76
|
+
" Instead of: match \"controller#action\"\n" \
|
77
|
+
" Do: get \"controller#action\""
|
78
|
+
raise ArgumentError, msg
|
79
|
+
end
|
80
|
+
via
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.normalize_path(path, format)
|
84
|
+
path = Mapper.normalize_path(path)
|
76
85
|
|
77
|
-
|
86
|
+
if format == true
|
87
|
+
"#{path}.:format"
|
88
|
+
elsif optional_format?(path, format)
|
89
|
+
"#{path}(.:format)"
|
90
|
+
else
|
91
|
+
path
|
92
|
+
end
|
93
|
+
end
|
78
94
|
|
79
|
-
|
95
|
+
def self.optional_format?(path, format)
|
96
|
+
format != false && !path.include?(':format') && !path.end_with?('/')
|
80
97
|
end
|
81
98
|
|
82
|
-
def initialize(
|
83
|
-
@requirements, @conditions = {}, {}
|
99
|
+
def initialize(set, ast, defaults, controller, default_action, modyoule, to, formatted, scope_constraints, blocks, via, options_constraints, anchor, options)
|
84
100
|
@defaults = defaults
|
85
101
|
@set = set
|
86
102
|
|
87
|
-
@to =
|
88
|
-
@default_controller =
|
89
|
-
@default_action =
|
90
|
-
@
|
91
|
-
@anchor =
|
103
|
+
@to = to
|
104
|
+
@default_controller = controller
|
105
|
+
@default_action = default_action
|
106
|
+
@ast = ast
|
107
|
+
@anchor = anchor
|
108
|
+
@via = via
|
109
|
+
@internal = options[:internal]
|
92
110
|
|
93
|
-
|
94
|
-
via = Array(options.delete(:via) { [] })
|
95
|
-
options_constraints = options.delete :constraints
|
111
|
+
path_params = ast.find_all(&:symbol?).map(&:to_sym)
|
96
112
|
|
97
|
-
|
98
|
-
ast = path_ast path
|
99
|
-
path_params = path_params ast
|
113
|
+
options = add_wildcard_options(options, formatted, ast)
|
100
114
|
|
101
|
-
options = normalize_options!(options,
|
115
|
+
options = normalize_options!(options, path_params, modyoule)
|
102
116
|
|
117
|
+
split_options = constraints(options, path_params)
|
103
118
|
|
104
|
-
|
105
|
-
constraints = constraints(options, path_params)
|
119
|
+
constraints = scope_constraints.merge Hash[split_options[:constraints] || []]
|
106
120
|
|
107
|
-
|
121
|
+
if options_constraints.is_a?(Hash)
|
122
|
+
@defaults = Hash[options_constraints.find_all { |key, default|
|
123
|
+
URL_OPTIONS.include?(key) && (String === default || Integer === default)
|
124
|
+
}].merge @defaults
|
125
|
+
@blocks = blocks
|
126
|
+
constraints.merge! options_constraints
|
127
|
+
else
|
128
|
+
@blocks = blocks(options_constraints)
|
129
|
+
end
|
108
130
|
|
109
|
-
|
131
|
+
requirements, conditions = split_constraints path_params, constraints
|
132
|
+
verify_regexp_requirements requirements.map(&:last).grep(Regexp)
|
110
133
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
134
|
+
formats = normalize_format(formatted)
|
135
|
+
|
136
|
+
@requirements = formats[:requirements].merge Hash[requirements]
|
137
|
+
@conditions = Hash[conditions]
|
138
|
+
@defaults = formats[:defaults].merge(@defaults).merge(normalize_defaults(options))
|
139
|
+
|
140
|
+
if path_params.include?(:action) && !@requirements.key?(:action)
|
141
|
+
@defaults[:action] ||= 'index'
|
118
142
|
end
|
119
143
|
|
120
|
-
|
144
|
+
@required_defaults = (split_options[:required_defaults] || []).map(&:first)
|
145
|
+
end
|
121
146
|
|
122
|
-
|
123
|
-
|
147
|
+
def make_route(name, precedence)
|
148
|
+
route = Journey::Route.new(name,
|
149
|
+
application,
|
150
|
+
path,
|
151
|
+
conditions,
|
152
|
+
required_defaults,
|
153
|
+
defaults,
|
154
|
+
request_method,
|
155
|
+
precedence,
|
156
|
+
@internal)
|
157
|
+
|
158
|
+
route
|
159
|
+
end
|
124
160
|
|
125
|
-
|
126
|
-
|
161
|
+
def application
|
162
|
+
app(@blocks)
|
127
163
|
end
|
128
164
|
|
129
|
-
def
|
130
|
-
|
165
|
+
def path
|
166
|
+
build_path @ast, requirements, @anchor
|
131
167
|
end
|
132
168
|
|
133
|
-
|
169
|
+
def conditions
|
170
|
+
build_conditions @conditions, @set.request_class
|
171
|
+
end
|
134
172
|
|
135
|
-
|
136
|
-
|
173
|
+
def build_conditions(current_conditions, request_class)
|
174
|
+
conditions = current_conditions.dup
|
175
|
+
|
176
|
+
conditions.keep_if do |k, _|
|
177
|
+
request_class.public_method_defined?(k)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
private :build_conditions
|
137
181
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
182
|
+
def request_method
|
183
|
+
@via.map { |x| Journey::Route.verb_matcher(x) }
|
184
|
+
end
|
185
|
+
private :request_method
|
186
|
+
|
187
|
+
JOINED_SEPARATORS = SEPARATORS.join # :nodoc:
|
188
|
+
|
189
|
+
def build_path(ast, requirements, anchor)
|
190
|
+
pattern = Journey::Path::Pattern.new(ast, requirements, JOINED_SEPARATORS, anchor)
|
191
|
+
|
192
|
+
# Find all the symbol nodes that are adjacent to literal nodes and alter
|
193
|
+
# the regexp so that Journey will partition them into custom routes.
|
194
|
+
ast.find_all { |node|
|
195
|
+
next unless node.cat?
|
196
|
+
|
197
|
+
if node.left.literal? && node.right.symbol?
|
198
|
+
symbol = node.right
|
199
|
+
elsif node.left.literal? && node.right.cat? && node.right.left.symbol?
|
200
|
+
symbol = node.right.left
|
201
|
+
elsif node.left.symbol? && node.right.literal?
|
202
|
+
symbol = node.left
|
203
|
+
elsif node.left.symbol? && node.right.cat? && node.right.left.literal?
|
204
|
+
symbol = node.left
|
142
205
|
else
|
143
|
-
|
206
|
+
next
|
144
207
|
end
|
145
|
-
end
|
146
208
|
|
147
|
-
|
148
|
-
|
149
|
-
|
209
|
+
if symbol
|
210
|
+
symbol.regexp = /(?:#{Regexp.union(symbol.regexp, '-')})+/
|
211
|
+
end
|
212
|
+
}
|
213
|
+
|
214
|
+
pattern
|
215
|
+
end
|
216
|
+
private :build_path
|
150
217
|
|
151
|
-
|
218
|
+
private
|
219
|
+
def add_wildcard_options(options, formatted, path_ast)
|
152
220
|
# Add a constraint for wildcard route to make it non-greedy and match the
|
153
221
|
# optional format part of the route by default
|
154
222
|
if formatted != false
|
155
|
-
path_ast.grep(Journey::Nodes::Star)
|
156
|
-
|
157
|
-
|
223
|
+
path_ast.grep(Journey::Nodes::Star).each_with_object({}) { |node, hash|
|
224
|
+
hash[node.name.to_sym] ||= /.+?/
|
225
|
+
}.merge options
|
226
|
+
else
|
227
|
+
options
|
158
228
|
end
|
229
|
+
end
|
159
230
|
|
231
|
+
def normalize_options!(options, path_params, modyoule)
|
160
232
|
if path_params.include?(:controller)
|
161
233
|
raise ArgumentError, ":controller segment is not allowed within a namespace block" if modyoule
|
162
234
|
|
@@ -181,74 +253,54 @@ module ActionDispatch
|
|
181
253
|
end
|
182
254
|
|
183
255
|
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
|
256
|
+
constraints.partition do |key, requirement|
|
257
|
+
path_params.include?(key) || key == :controller
|
191
258
|
end
|
192
259
|
end
|
193
260
|
|
194
|
-
def normalize_format
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
261
|
+
def normalize_format(formatted)
|
262
|
+
case formatted
|
263
|
+
when true
|
264
|
+
{ requirements: { format: /.+/ },
|
265
|
+
defaults: {} }
|
266
|
+
when Regexp
|
267
|
+
{ requirements: { format: formatted },
|
268
|
+
defaults: { format: nil } }
|
269
|
+
when String
|
270
|
+
{ requirements: { format: Regexp.compile(formatted) },
|
271
|
+
defaults: { format: formatted } }
|
272
|
+
else
|
273
|
+
{ requirements: { }, defaults: { } }
|
203
274
|
end
|
204
275
|
end
|
205
276
|
|
206
|
-
def
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
if requirement.multiline?
|
212
|
-
raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
|
213
|
-
end
|
214
|
-
end
|
277
|
+
def verify_regexp_requirements(requirements)
|
278
|
+
requirements.each do |requirement|
|
279
|
+
if requirement.source =~ ANCHOR_CHARACTERS_REGEX
|
280
|
+
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
|
281
|
+
end
|
215
282
|
|
216
|
-
|
217
|
-
|
218
|
-
unless Regexp === default
|
219
|
-
@defaults[key] = default
|
283
|
+
if requirement.multiline?
|
284
|
+
raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
|
220
285
|
end
|
221
286
|
end
|
222
287
|
end
|
223
288
|
|
224
|
-
def
|
225
|
-
|
226
|
-
raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?"
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
def add_request_method(via, conditions)
|
231
|
-
return if via == [:all]
|
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 }
|
289
|
+
def normalize_defaults(options)
|
290
|
+
Hash[options.reject { |_, default| Regexp === default }]
|
243
291
|
end
|
244
292
|
|
245
293
|
def app(blocks)
|
246
|
-
if to.
|
247
|
-
|
248
|
-
elsif blocks.any?
|
249
|
-
Constraints.new(dispatcher(defaults), blocks, true)
|
294
|
+
if to.is_a?(Class) && to < ActionController::Metal
|
295
|
+
Routing::RouteSet::StaticDispatcher.new to
|
250
296
|
else
|
251
|
-
|
297
|
+
if to.respond_to?(:call)
|
298
|
+
Constraints.new(to, blocks, Constraints::CALL)
|
299
|
+
elsif blocks.any?
|
300
|
+
Constraints.new(dispatcher(defaults.key?(:controller)), blocks, Constraints::SERVE)
|
301
|
+
else
|
302
|
+
dispatcher(defaults.key?(:controller))
|
303
|
+
end
|
252
304
|
end
|
253
305
|
end
|
254
306
|
|
@@ -280,22 +332,8 @@ module ActionDispatch
|
|
280
332
|
end
|
281
333
|
|
282
334
|
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]
|
335
|
+
if to =~ /#/
|
336
|
+
to.split('#')
|
299
337
|
else
|
300
338
|
[]
|
301
339
|
end
|
@@ -320,40 +358,29 @@ module ActionDispatch
|
|
320
358
|
yield
|
321
359
|
end
|
322
360
|
|
323
|
-
def blocks(
|
324
|
-
|
325
|
-
|
326
|
-
[options_constraints]
|
327
|
-
else
|
328
|
-
scope_blocks || []
|
361
|
+
def blocks(callable_constraint)
|
362
|
+
unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?)
|
363
|
+
raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?"
|
329
364
|
end
|
365
|
+
[callable_constraint]
|
330
366
|
end
|
331
367
|
|
332
368
|
def constraints(options, path_params)
|
333
|
-
|
334
|
-
required_defaults = []
|
335
|
-
options.each_pair do |key, option|
|
369
|
+
options.group_by do |key, option|
|
336
370
|
if Regexp === option
|
337
|
-
constraints
|
371
|
+
:constraints
|
338
372
|
else
|
339
|
-
|
373
|
+
if path_params.include?(key)
|
374
|
+
:path_params
|
375
|
+
else
|
376
|
+
:required_defaults
|
377
|
+
end
|
340
378
|
end
|
341
379
|
end
|
342
|
-
@conditions[:required_defaults] = required_defaults
|
343
|
-
constraints
|
344
|
-
end
|
345
|
-
|
346
|
-
def path_params(ast)
|
347
|
-
ast.grep(Journey::Nodes::Symbol).map { |n| n.name.to_sym }
|
348
380
|
end
|
349
381
|
|
350
|
-
def
|
351
|
-
|
352
|
-
parser.parse path
|
353
|
-
end
|
354
|
-
|
355
|
-
def dispatcher(defaults)
|
356
|
-
@set.dispatcher defaults
|
382
|
+
def dispatcher(raise_on_name_error)
|
383
|
+
Routing::RouteSet::Dispatcher.new raise_on_name_error
|
357
384
|
end
|
358
385
|
end
|
359
386
|
|
@@ -371,23 +398,6 @@ module ActionDispatch
|
|
371
398
|
end
|
372
399
|
|
373
400
|
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
401
|
# Matches a url pattern to one or more routes.
|
392
402
|
#
|
393
403
|
# You should not use the +match+ method in your router
|
@@ -435,7 +445,7 @@ module ActionDispatch
|
|
435
445
|
# A pattern can also point to a +Rack+ endpoint i.e. anything that
|
436
446
|
# responds to +call+:
|
437
447
|
#
|
438
|
-
# match 'photos/:id', to:
|
448
|
+
# match 'photos/:id', to: -> (hash) { [200, {}, ["Coming soon"]] }, via: :get
|
439
449
|
# match 'photos/:id', to: PhotoRackApp, via: :get
|
440
450
|
# # Yes, controller actions are just rack endpoints
|
441
451
|
# match 'photos/:id', to: PhotosController.action(:show), via: :get
|
@@ -460,6 +470,21 @@ module ActionDispatch
|
|
460
470
|
# dynamic segment used to generate the routes).
|
461
471
|
# You can access that segment from your controller using
|
462
472
|
# <tt>params[<:param>]</tt>.
|
473
|
+
# In your router:
|
474
|
+
#
|
475
|
+
# resources :user, param: :name
|
476
|
+
#
|
477
|
+
# You can override <tt>ActiveRecord::Base#to_param</tt> of a related
|
478
|
+
# model to construct a URL:
|
479
|
+
#
|
480
|
+
# class User < ActiveRecord::Base
|
481
|
+
# def to_param
|
482
|
+
# name
|
483
|
+
# end
|
484
|
+
# end
|
485
|
+
#
|
486
|
+
# user = User.find_by(name: 'Phusion')
|
487
|
+
# user_path(user) # => "/users/Phusion"
|
463
488
|
#
|
464
489
|
# [:path]
|
465
490
|
# The path prefix for the routes.
|
@@ -487,7 +512,7 @@ module ActionDispatch
|
|
487
512
|
# +call+ or a string representing a controller's action.
|
488
513
|
#
|
489
514
|
# match 'path', to: 'controller#action', via: :get
|
490
|
-
# match 'path', to:
|
515
|
+
# match 'path', to: -> (env) { [200, {}, ["Success!"]] }, via: :get
|
491
516
|
# match 'path', to: RackApp, via: :get
|
492
517
|
#
|
493
518
|
# [:on]
|
@@ -568,17 +593,20 @@ module ActionDispatch
|
|
568
593
|
def mount(app, options = nil)
|
569
594
|
if options
|
570
595
|
path = options.delete(:at)
|
571
|
-
|
572
|
-
unless Hash === app
|
573
|
-
raise ArgumentError, "must be called with mount point"
|
574
|
-
end
|
575
|
-
|
596
|
+
elsif Hash === app
|
576
597
|
options = app
|
577
598
|
app, path = options.find { |k, _| k.respond_to?(:call) }
|
578
599
|
options.delete(app) if app
|
579
600
|
end
|
580
601
|
|
581
|
-
raise "A rack application must be specified" unless
|
602
|
+
raise ArgumentError, "A rack application must be specified" unless app.respond_to?(:call)
|
603
|
+
raise ArgumentError, <<-MSG.strip_heredoc unless path
|
604
|
+
Must be called with mount point
|
605
|
+
|
606
|
+
mount SomeRackApp, at: "some_route"
|
607
|
+
or
|
608
|
+
mount(SomeRackApp => "some_route")
|
609
|
+
MSG
|
582
610
|
|
583
611
|
rails_app = rails_app? app
|
584
612
|
options[:as] ||= app_name(app, rails_app)
|
@@ -605,7 +633,7 @@ module ActionDispatch
|
|
605
633
|
|
606
634
|
# Query if the following named route was already defined.
|
607
635
|
def has_named_route?(name)
|
608
|
-
@set.named_routes.
|
636
|
+
@set.named_routes.key? name
|
609
637
|
end
|
610
638
|
|
611
639
|
private
|
@@ -688,7 +716,11 @@ module ActionDispatch
|
|
688
716
|
def map_method(method, args, &block)
|
689
717
|
options = args.extract_options!
|
690
718
|
options[:via] = method
|
691
|
-
|
719
|
+
if options.key?(:defaults)
|
720
|
+
defaults(options.delete(:defaults)) { match(*args, options, &block) }
|
721
|
+
else
|
722
|
+
match(*args, options, &block)
|
723
|
+
end
|
692
724
|
self
|
693
725
|
end
|
694
726
|
end
|
@@ -795,21 +827,30 @@ module ActionDispatch
|
|
795
827
|
URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Integer))
|
796
828
|
end
|
797
829
|
|
798
|
-
(options[:defaults]
|
830
|
+
options[:defaults] = defaults.merge(options[:defaults] || {})
|
799
831
|
else
|
800
832
|
block, options[:constraints] = options[:constraints], {}
|
801
833
|
end
|
802
834
|
|
835
|
+
if options.key?(:only) || options.key?(:except)
|
836
|
+
scope[:action_options] = { only: options.delete(:only),
|
837
|
+
except: options.delete(:except) }
|
838
|
+
end
|
839
|
+
|
840
|
+
if options.key? :anchor
|
841
|
+
raise ArgumentError, 'anchor is ignored unless passed to `match`'
|
842
|
+
end
|
843
|
+
|
803
844
|
@scope.options.each do |option|
|
804
845
|
if option == :blocks
|
805
846
|
value = block
|
806
847
|
elsif option == :options
|
807
848
|
value = options
|
808
849
|
else
|
809
|
-
value = options.delete(option)
|
850
|
+
value = options.delete(option) { POISON }
|
810
851
|
end
|
811
852
|
|
812
|
-
|
853
|
+
unless POISON == value
|
813
854
|
scope[option] = send("merge_#{option}_scope", @scope[option], value)
|
814
855
|
end
|
815
856
|
end
|
@@ -821,14 +862,27 @@ module ActionDispatch
|
|
821
862
|
@scope = @scope.parent
|
822
863
|
end
|
823
864
|
|
865
|
+
POISON = Object.new # :nodoc:
|
866
|
+
|
824
867
|
# Scopes routes to a specific controller
|
825
868
|
#
|
826
869
|
# controller "food" do
|
827
|
-
# match "bacon", action:
|
870
|
+
# match "bacon", action: :bacon, via: :get
|
828
871
|
# end
|
829
|
-
def controller(controller, options={})
|
830
|
-
options
|
831
|
-
|
872
|
+
def controller(controller, options = {})
|
873
|
+
if options.empty?
|
874
|
+
begin
|
875
|
+
@scope = @scope.new(controller: controller)
|
876
|
+
yield
|
877
|
+
ensure
|
878
|
+
@scope = @scope.parent
|
879
|
+
end
|
880
|
+
else
|
881
|
+
ActiveSupport::Deprecation.warn "#controller with options is deprecated. If you need to pass more options than the controller name use #scope."
|
882
|
+
|
883
|
+
options[:controller] = controller
|
884
|
+
scope(options) { yield }
|
885
|
+
end
|
832
886
|
end
|
833
887
|
|
834
888
|
# Scopes routes to a specific namespace. For example:
|
@@ -874,13 +928,14 @@ module ActionDispatch
|
|
874
928
|
|
875
929
|
defaults = {
|
876
930
|
module: path,
|
877
|
-
path: options.fetch(:path, path),
|
878
931
|
as: options.fetch(:as, path),
|
879
932
|
shallow_path: options.fetch(:path, path),
|
880
933
|
shallow_prefix: options.fetch(:as, path)
|
881
934
|
}
|
882
935
|
|
883
|
-
|
936
|
+
path_scope(options.delete(:path) { path }) do
|
937
|
+
scope(defaults.merge!(options)) { yield }
|
938
|
+
end
|
884
939
|
end
|
885
940
|
|
886
941
|
# === Parameter Restriction
|
@@ -917,7 +972,7 @@ module ActionDispatch
|
|
917
972
|
#
|
918
973
|
# Requests to routes can be constrained based on specific criteria:
|
919
974
|
#
|
920
|
-
# constraints(
|
975
|
+
# constraints(-> (req) { req.env["HTTP_USER_AGENT"] =~ /iPhone/ }) do
|
921
976
|
# resources :iphones
|
922
977
|
# end
|
923
978
|
#
|
@@ -948,7 +1003,10 @@ module ActionDispatch
|
|
948
1003
|
# end
|
949
1004
|
# Using this, the +:id+ parameter here will default to 'home'.
|
950
1005
|
def defaults(defaults = {})
|
951
|
-
scope(:defaults
|
1006
|
+
@scope = @scope.new(defaults: merge_defaults_scope(@scope[:defaults], defaults))
|
1007
|
+
yield
|
1008
|
+
ensure
|
1009
|
+
@scope = @scope.parent
|
952
1010
|
end
|
953
1011
|
|
954
1012
|
private
|
@@ -980,6 +1038,14 @@ module ActionDispatch
|
|
980
1038
|
child
|
981
1039
|
end
|
982
1040
|
|
1041
|
+
def merge_via_scope(parent, child) #:nodoc:
|
1042
|
+
child
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
def merge_format_scope(parent, child) #:nodoc:
|
1046
|
+
child
|
1047
|
+
end
|
1048
|
+
|
983
1049
|
def merge_path_names_scope(parent, child) #:nodoc:
|
984
1050
|
merge_options_scope(parent, child)
|
985
1051
|
end
|
@@ -999,15 +1065,15 @@ module ActionDispatch
|
|
999
1065
|
end
|
1000
1066
|
|
1001
1067
|
def merge_options_scope(parent, child) #:nodoc:
|
1002
|
-
(parent || {}).
|
1068
|
+
(parent || {}).merge(child)
|
1003
1069
|
end
|
1004
1070
|
|
1005
1071
|
def merge_shallow_scope(parent, child) #:nodoc:
|
1006
1072
|
child ? true : false
|
1007
1073
|
end
|
1008
1074
|
|
1009
|
-
def
|
1010
|
-
child
|
1075
|
+
def merge_to_scope(parent, child)
|
1076
|
+
child
|
1011
1077
|
end
|
1012
1078
|
end
|
1013
1079
|
|
@@ -1058,27 +1124,34 @@ module ActionDispatch
|
|
1058
1124
|
CANONICAL_ACTIONS = %w(index create new show update destroy)
|
1059
1125
|
|
1060
1126
|
class Resource #:nodoc:
|
1061
|
-
attr_reader :controller, :path, :
|
1127
|
+
attr_reader :controller, :path, :param
|
1062
1128
|
|
1063
|
-
def initialize(entities, options = {})
|
1129
|
+
def initialize(entities, api_only, shallow, options = {})
|
1064
1130
|
@name = entities.to_s
|
1065
1131
|
@path = (options[:path] || @name).to_s
|
1066
1132
|
@controller = (options[:controller] || @name).to_s
|
1067
1133
|
@as = options[:as]
|
1068
1134
|
@param = (options[:param] || :id).to_sym
|
1069
1135
|
@options = options
|
1070
|
-
@shallow =
|
1136
|
+
@shallow = shallow
|
1137
|
+
@api_only = api_only
|
1138
|
+
@only = options.delete :only
|
1139
|
+
@except = options.delete :except
|
1071
1140
|
end
|
1072
1141
|
|
1073
1142
|
def default_actions
|
1074
|
-
|
1143
|
+
if @api_only
|
1144
|
+
[:index, :create, :show, :update, :destroy]
|
1145
|
+
else
|
1146
|
+
[:index, :create, :new, :show, :update, :destroy, :edit]
|
1147
|
+
end
|
1075
1148
|
end
|
1076
1149
|
|
1077
1150
|
def actions
|
1078
|
-
if
|
1079
|
-
Array(only).map(&:to_sym)
|
1080
|
-
elsif
|
1081
|
-
default_actions - Array(except).map(&:to_sym)
|
1151
|
+
if @only
|
1152
|
+
Array(@only).map(&:to_sym)
|
1153
|
+
elsif @except
|
1154
|
+
default_actions - Array(@except).map(&:to_sym)
|
1082
1155
|
else
|
1083
1156
|
default_actions
|
1084
1157
|
end
|
@@ -1105,7 +1178,7 @@ module ActionDispatch
|
|
1105
1178
|
end
|
1106
1179
|
|
1107
1180
|
def resource_scope
|
1108
|
-
|
1181
|
+
controller
|
1109
1182
|
end
|
1110
1183
|
|
1111
1184
|
alias :collection_scope :path
|
@@ -1128,17 +1201,15 @@ module ActionDispatch
|
|
1128
1201
|
"#{path}/:#{nested_param}"
|
1129
1202
|
end
|
1130
1203
|
|
1131
|
-
def shallow=(value)
|
1132
|
-
@shallow = value
|
1133
|
-
end
|
1134
|
-
|
1135
1204
|
def shallow?
|
1136
1205
|
@shallow
|
1137
1206
|
end
|
1207
|
+
|
1208
|
+
def singleton?; false; end
|
1138
1209
|
end
|
1139
1210
|
|
1140
1211
|
class SingletonResource < Resource #:nodoc:
|
1141
|
-
def initialize(entities, options)
|
1212
|
+
def initialize(entities, api_only, shallow, options)
|
1142
1213
|
super
|
1143
1214
|
@as = nil
|
1144
1215
|
@controller = (options[:controller] || plural).to_s
|
@@ -1146,7 +1217,11 @@ module ActionDispatch
|
|
1146
1217
|
end
|
1147
1218
|
|
1148
1219
|
def default_actions
|
1149
|
-
|
1220
|
+
if @api_only
|
1221
|
+
[:show, :create, :update, :destroy]
|
1222
|
+
else
|
1223
|
+
[:show, :create, :update, :destroy, :new, :edit]
|
1224
|
+
end
|
1150
1225
|
end
|
1151
1226
|
|
1152
1227
|
def plural
|
@@ -1162,6 +1237,8 @@ module ActionDispatch
|
|
1162
1237
|
|
1163
1238
|
alias :member_scope :path
|
1164
1239
|
alias :nested_scope :path
|
1240
|
+
|
1241
|
+
def singleton?; true; end
|
1165
1242
|
end
|
1166
1243
|
|
1167
1244
|
def resources_path_names(options)
|
@@ -1196,20 +1273,23 @@ module ActionDispatch
|
|
1196
1273
|
return self
|
1197
1274
|
end
|
1198
1275
|
|
1199
|
-
|
1200
|
-
|
1276
|
+
with_scope_level(:resource) do
|
1277
|
+
options = apply_action_options options
|
1278
|
+
resource_scope(SingletonResource.new(resources.pop, api_only?, @scope[:shallow], options)) do
|
1279
|
+
yield if block_given?
|
1201
1280
|
|
1202
|
-
|
1281
|
+
concerns(options[:concerns]) if options[:concerns]
|
1203
1282
|
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1283
|
+
collection do
|
1284
|
+
post :create
|
1285
|
+
end if parent_resource.actions.include?(:create)
|
1207
1286
|
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1287
|
+
new do
|
1288
|
+
get :new
|
1289
|
+
end if parent_resource.actions.include?(:new)
|
1211
1290
|
|
1212
|
-
|
1291
|
+
set_member_mappings_for_resource
|
1292
|
+
end
|
1213
1293
|
end
|
1214
1294
|
|
1215
1295
|
self
|
@@ -1354,21 +1434,24 @@ module ActionDispatch
|
|
1354
1434
|
return self
|
1355
1435
|
end
|
1356
1436
|
|
1357
|
-
|
1358
|
-
|
1437
|
+
with_scope_level(:resources) do
|
1438
|
+
options = apply_action_options options
|
1439
|
+
resource_scope(Resource.new(resources.pop, api_only?, @scope[:shallow], options)) do
|
1440
|
+
yield if block_given?
|
1359
1441
|
|
1360
|
-
|
1442
|
+
concerns(options[:concerns]) if options[:concerns]
|
1361
1443
|
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1365
|
-
|
1444
|
+
collection do
|
1445
|
+
get :index if parent_resource.actions.include?(:index)
|
1446
|
+
post :create if parent_resource.actions.include?(:create)
|
1447
|
+
end
|
1366
1448
|
|
1367
|
-
|
1368
|
-
|
1369
|
-
|
1449
|
+
new do
|
1450
|
+
get :new
|
1451
|
+
end if parent_resource.actions.include?(:new)
|
1370
1452
|
|
1371
|
-
|
1453
|
+
set_member_mappings_for_resource
|
1454
|
+
end
|
1372
1455
|
end
|
1373
1456
|
|
1374
1457
|
self
|
@@ -1392,7 +1475,7 @@ module ActionDispatch
|
|
1392
1475
|
end
|
1393
1476
|
|
1394
1477
|
with_scope_level(:collection) do
|
1395
|
-
|
1478
|
+
path_scope(parent_resource.collection_scope) do
|
1396
1479
|
yield
|
1397
1480
|
end
|
1398
1481
|
end
|
@@ -1416,9 +1499,11 @@ module ActionDispatch
|
|
1416
1499
|
|
1417
1500
|
with_scope_level(:member) do
|
1418
1501
|
if shallow?
|
1419
|
-
shallow_scope
|
1502
|
+
shallow_scope {
|
1503
|
+
path_scope(parent_resource.member_scope) { yield }
|
1504
|
+
}
|
1420
1505
|
else
|
1421
|
-
|
1506
|
+
path_scope(parent_resource.member_scope) { yield }
|
1422
1507
|
end
|
1423
1508
|
end
|
1424
1509
|
end
|
@@ -1429,7 +1514,7 @@ module ActionDispatch
|
|
1429
1514
|
end
|
1430
1515
|
|
1431
1516
|
with_scope_level(:new) do
|
1432
|
-
|
1517
|
+
path_scope(parent_resource.new_scope(action_path(:new))) do
|
1433
1518
|
yield
|
1434
1519
|
end
|
1435
1520
|
end
|
@@ -1442,9 +1527,15 @@ module ActionDispatch
|
|
1442
1527
|
|
1443
1528
|
with_scope_level(:nested) do
|
1444
1529
|
if shallow? && shallow_nesting_depth >= 1
|
1445
|
-
shallow_scope
|
1530
|
+
shallow_scope do
|
1531
|
+
path_scope(parent_resource.nested_scope) do
|
1532
|
+
scope(nested_options) { yield }
|
1533
|
+
end
|
1534
|
+
end
|
1446
1535
|
else
|
1447
|
-
|
1536
|
+
path_scope(parent_resource.nested_scope) do
|
1537
|
+
scope(nested_options) { yield }
|
1538
|
+
end
|
1448
1539
|
end
|
1449
1540
|
end
|
1450
1541
|
end
|
@@ -1459,18 +1550,22 @@ module ActionDispatch
|
|
1459
1550
|
end
|
1460
1551
|
|
1461
1552
|
def shallow
|
1462
|
-
scope(:
|
1463
|
-
|
1464
|
-
|
1553
|
+
@scope = @scope.new(shallow: true)
|
1554
|
+
yield
|
1555
|
+
ensure
|
1556
|
+
@scope = @scope.parent
|
1465
1557
|
end
|
1466
1558
|
|
1467
1559
|
def shallow?
|
1468
|
-
parent_resource.
|
1560
|
+
!parent_resource.singleton? && @scope[:shallow]
|
1469
1561
|
end
|
1470
1562
|
|
1471
|
-
#
|
1472
|
-
#
|
1473
|
-
#
|
1563
|
+
# Matches a url pattern to one or more routes.
|
1564
|
+
# For more information, see match[rdoc-ref:Base#match].
|
1565
|
+
#
|
1566
|
+
# match 'path' => 'controller#action', via: patch
|
1567
|
+
# match 'path', to: 'controller#action', via: :post
|
1568
|
+
# match 'path', 'otherpath', on: :member, via: :get
|
1474
1569
|
def match(path, *rest)
|
1475
1570
|
if rest.empty? && Hash === path
|
1476
1571
|
options = path
|
@@ -1496,58 +1591,97 @@ module ActionDispatch
|
|
1496
1591
|
paths = [path] + rest
|
1497
1592
|
end
|
1498
1593
|
|
1499
|
-
options[:anchor] = true unless options.key?(:anchor)
|
1500
|
-
|
1501
1594
|
if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
|
1502
1595
|
raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
|
1503
1596
|
end
|
1504
1597
|
|
1598
|
+
if @scope[:to]
|
1599
|
+
options[:to] ||= @scope[:to]
|
1600
|
+
end
|
1601
|
+
|
1505
1602
|
if @scope[:controller] && @scope[:action]
|
1506
1603
|
options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
|
1507
1604
|
end
|
1508
1605
|
|
1509
|
-
|
1606
|
+
controller = options.delete(:controller) || @scope[:controller]
|
1607
|
+
option_path = options.delete :path
|
1608
|
+
to = options.delete :to
|
1609
|
+
via = Mapping.check_via Array(options.delete(:via) {
|
1610
|
+
@scope[:via]
|
1611
|
+
})
|
1612
|
+
formatted = options.delete(:format) { @scope[:format] }
|
1613
|
+
anchor = options.delete(:anchor) { true }
|
1614
|
+
options_constraints = options.delete(:constraints) || {}
|
1615
|
+
|
1616
|
+
path_types = paths.group_by(&:class)
|
1617
|
+
path_types.fetch(String, []).each do |_path|
|
1510
1618
|
route_options = options.dup
|
1511
|
-
|
1619
|
+
if _path && option_path
|
1620
|
+
ActiveSupport::Deprecation.warn <<-eowarn
|
1621
|
+
Specifying strings for both :path and the route path is deprecated. Change things like this:
|
1622
|
+
|
1623
|
+
match #{_path.inspect}, :path => #{option_path.inspect}
|
1624
|
+
|
1625
|
+
to this:
|
1512
1626
|
|
1513
|
-
|
1514
|
-
|
1515
|
-
route_options[:
|
1516
|
-
route_options[:
|
1627
|
+
match #{option_path.inspect}, :as => #{_path.inspect}, :action => #{path.inspect}
|
1628
|
+
eowarn
|
1629
|
+
route_options[:action] = _path
|
1630
|
+
route_options[:as] = _path
|
1631
|
+
_path = option_path
|
1517
1632
|
end
|
1633
|
+
to = get_to_from_path(_path, to, route_options[:action])
|
1634
|
+
decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints)
|
1635
|
+
end
|
1518
1636
|
|
1519
|
-
|
1637
|
+
path_types.fetch(Symbol, []).each do |action|
|
1638
|
+
route_options = options.dup
|
1639
|
+
decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints)
|
1520
1640
|
end
|
1641
|
+
|
1521
1642
|
self
|
1522
1643
|
end
|
1523
1644
|
|
1524
|
-
def
|
1525
|
-
|
1645
|
+
def get_to_from_path(path, to, action)
|
1646
|
+
return to if to || action
|
1647
|
+
|
1648
|
+
path_without_format = path.sub(/\(\.:format\)$/, '')
|
1649
|
+
if using_match_shorthand?(path_without_format)
|
1650
|
+
path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1').tr("-", "_")
|
1651
|
+
else
|
1652
|
+
nil
|
1653
|
+
end
|
1526
1654
|
end
|
1527
1655
|
|
1528
|
-
def
|
1656
|
+
def using_match_shorthand?(path)
|
1657
|
+
path =~ %r{^/?[-\w]+/[-\w/]+$}
|
1658
|
+
end
|
1659
|
+
|
1660
|
+
def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc:
|
1529
1661
|
if on = options.delete(:on)
|
1530
|
-
send(on) { decomposed_match(path, options) }
|
1662
|
+
send(on) { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
|
1531
1663
|
else
|
1532
1664
|
case @scope.scope_level
|
1533
1665
|
when :resources
|
1534
|
-
nested { decomposed_match(path, options) }
|
1666
|
+
nested { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
|
1535
1667
|
when :resource
|
1536
|
-
member { decomposed_match(path, options) }
|
1668
|
+
member { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
|
1537
1669
|
else
|
1538
|
-
add_route(path, options)
|
1670
|
+
add_route(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
|
1539
1671
|
end
|
1540
1672
|
end
|
1541
1673
|
end
|
1542
1674
|
|
1543
|
-
def add_route(action, options) # :nodoc:
|
1544
|
-
path = path_for_action(action,
|
1675
|
+
def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc:
|
1676
|
+
path = path_for_action(action, _path)
|
1545
1677
|
raise ArgumentError, "path is required" if path.blank?
|
1546
1678
|
|
1547
|
-
action = action.to_s
|
1679
|
+
action = action.to_s
|
1680
|
+
|
1681
|
+
default_action = options.delete(:action) || @scope[:action]
|
1548
1682
|
|
1549
1683
|
if action =~ /^[\w\-\/]+$/
|
1550
|
-
|
1684
|
+
default_action ||= action.tr('-', '_') unless action.include?("/")
|
1551
1685
|
else
|
1552
1686
|
action = nil
|
1553
1687
|
end
|
@@ -1558,12 +1692,27 @@ module ActionDispatch
|
|
1558
1692
|
name_for_action(options.delete(:as), action)
|
1559
1693
|
end
|
1560
1694
|
|
1561
|
-
|
1562
|
-
|
1563
|
-
|
1695
|
+
path = Mapping.normalize_path URI.parser.escape(path), formatted
|
1696
|
+
ast = Journey::Parser.parse path
|
1697
|
+
|
1698
|
+
mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
|
1699
|
+
@set.add_route(mapping, ast, as, anchor)
|
1564
1700
|
end
|
1565
1701
|
|
1566
|
-
|
1702
|
+
# You can specify what Rails should route "/" to with the root method:
|
1703
|
+
#
|
1704
|
+
# root to: 'pages#main'
|
1705
|
+
#
|
1706
|
+
# For options, see +match+, as +root+ uses it internally.
|
1707
|
+
#
|
1708
|
+
# You can also pass a string which will expand
|
1709
|
+
#
|
1710
|
+
# root 'pages#main'
|
1711
|
+
#
|
1712
|
+
# You should put the root route at the top of <tt>config/routes.rb</tt>,
|
1713
|
+
# because this means it will be matched first. As this is the most popular route
|
1714
|
+
# of most Rails applications, this is beneficial.
|
1715
|
+
def root(path, options = {})
|
1567
1716
|
if path.is_a?(String)
|
1568
1717
|
options[:to] = path
|
1569
1718
|
elsif path.is_a?(Hash) and options.empty?
|
@@ -1574,12 +1723,12 @@ module ActionDispatch
|
|
1574
1723
|
|
1575
1724
|
if @scope.resources?
|
1576
1725
|
with_scope_level(:root) do
|
1577
|
-
|
1578
|
-
|
1726
|
+
path_scope(parent_resource.path) do
|
1727
|
+
match_root_route(options)
|
1579
1728
|
end
|
1580
1729
|
end
|
1581
1730
|
else
|
1582
|
-
|
1731
|
+
match_root_route(options)
|
1583
1732
|
end
|
1584
1733
|
end
|
1585
1734
|
|
@@ -1619,23 +1768,20 @@ module ActionDispatch
|
|
1619
1768
|
return true
|
1620
1769
|
end
|
1621
1770
|
|
1622
|
-
unless action_options?(options)
|
1623
|
-
options.merge!(scope_action_options) if scope_action_options?
|
1624
|
-
end
|
1625
|
-
|
1626
1771
|
false
|
1627
1772
|
end
|
1628
1773
|
|
1629
|
-
def
|
1630
|
-
options
|
1774
|
+
def apply_action_options(options) # :nodoc:
|
1775
|
+
return options if action_options? options
|
1776
|
+
options.merge scope_action_options
|
1631
1777
|
end
|
1632
1778
|
|
1633
|
-
def
|
1634
|
-
|
1779
|
+
def action_options?(options) #:nodoc:
|
1780
|
+
options[:only] || options[:except]
|
1635
1781
|
end
|
1636
1782
|
|
1637
1783
|
def scope_action_options #:nodoc:
|
1638
|
-
@scope[:
|
1784
|
+
@scope[:action_options] || {}
|
1639
1785
|
end
|
1640
1786
|
|
1641
1787
|
def resource_scope? #:nodoc:
|
@@ -1650,18 +1796,6 @@ module ActionDispatch
|
|
1650
1796
|
@scope.nested?
|
1651
1797
|
end
|
1652
1798
|
|
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
1799
|
def with_scope_level(kind)
|
1666
1800
|
@scope = @scope.new_level(kind)
|
1667
1801
|
yield
|
@@ -1669,16 +1803,11 @@ module ActionDispatch
|
|
1669
1803
|
@scope = @scope.parent
|
1670
1804
|
end
|
1671
1805
|
|
1672
|
-
def resource_scope(
|
1673
|
-
resource.shallow = @scope[:shallow]
|
1806
|
+
def resource_scope(resource) #:nodoc:
|
1674
1807
|
@scope = @scope.new(:scope_level_resource => resource)
|
1675
|
-
@nesting.push(resource)
|
1676
1808
|
|
1677
|
-
|
1678
|
-
scope(parent_resource.resource_scope) { yield }
|
1679
|
-
end
|
1809
|
+
controller(resource.resource_scope) { yield }
|
1680
1810
|
ensure
|
1681
|
-
@nesting.pop
|
1682
1811
|
@scope = @scope.parent
|
1683
1812
|
end
|
1684
1813
|
|
@@ -1691,12 +1820,10 @@ module ActionDispatch
|
|
1691
1820
|
options
|
1692
1821
|
end
|
1693
1822
|
|
1694
|
-
def nesting_depth #:nodoc:
|
1695
|
-
@nesting.size
|
1696
|
-
end
|
1697
|
-
|
1698
1823
|
def shallow_nesting_depth #:nodoc:
|
1699
|
-
@
|
1824
|
+
@scope.find_all { |node|
|
1825
|
+
node.frame[:scope_level_resource]
|
1826
|
+
}.count { |node| node.frame[:scope_level_resource].shallow? }
|
1700
1827
|
end
|
1701
1828
|
|
1702
1829
|
def param_constraint? #:nodoc:
|
@@ -1711,27 +1838,28 @@ module ActionDispatch
|
|
1711
1838
|
resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
|
1712
1839
|
end
|
1713
1840
|
|
1714
|
-
def shallow_scope
|
1841
|
+
def shallow_scope #:nodoc:
|
1715
1842
|
scope = { :as => @scope[:shallow_prefix],
|
1716
1843
|
:path => @scope[:shallow_path] }
|
1717
1844
|
@scope = @scope.new scope
|
1718
1845
|
|
1719
|
-
|
1846
|
+
yield
|
1720
1847
|
ensure
|
1721
1848
|
@scope = @scope.parent
|
1722
1849
|
end
|
1723
1850
|
|
1724
1851
|
def path_for_action(action, path) #:nodoc:
|
1725
|
-
|
1852
|
+
return "#{@scope[:path]}/#{path}" if path
|
1853
|
+
|
1854
|
+
if canonical_action?(action)
|
1726
1855
|
@scope[:path].to_s
|
1727
1856
|
else
|
1728
|
-
"#{@scope[:path]}/#{action_path(action
|
1857
|
+
"#{@scope[:path]}/#{action_path(action)}"
|
1729
1858
|
end
|
1730
1859
|
end
|
1731
1860
|
|
1732
|
-
def action_path(name
|
1733
|
-
name
|
1734
|
-
path || @scope[:path_names][name] || name.to_s
|
1861
|
+
def action_path(name) #:nodoc:
|
1862
|
+
@scope[:path_names][name.to_sym] || name
|
1735
1863
|
end
|
1736
1864
|
|
1737
1865
|
def prefix_name_for_action(as, action) #:nodoc:
|
@@ -1757,14 +1885,15 @@ module ActionDispatch
|
|
1757
1885
|
member_name = parent_resource.member_name
|
1758
1886
|
end
|
1759
1887
|
|
1760
|
-
|
1888
|
+
action_name = @scope.action_name(name_prefix, prefix, collection_name, member_name)
|
1889
|
+
candidate = action_name.select(&:present?).join('_')
|
1761
1890
|
|
1762
|
-
|
1891
|
+
unless candidate.empty?
|
1763
1892
|
# If a name was not explicitly given, we check if it is valid
|
1764
1893
|
# and return nil in case it isn't. Otherwise, we pass the invalid name
|
1765
1894
|
# forward so the underlying router engine treats it and raises an exception.
|
1766
1895
|
if as.nil?
|
1767
|
-
candidate unless candidate !~ /\A[_a-z]/i ||
|
1896
|
+
candidate unless candidate !~ /\A[_a-z]/i || has_named_route?(candidate)
|
1768
1897
|
else
|
1769
1898
|
candidate
|
1770
1899
|
end
|
@@ -1782,6 +1911,23 @@ module ActionDispatch
|
|
1782
1911
|
delete :destroy if parent_resource.actions.include?(:destroy)
|
1783
1912
|
end
|
1784
1913
|
end
|
1914
|
+
|
1915
|
+
def api_only?
|
1916
|
+
@set.api_only?
|
1917
|
+
end
|
1918
|
+
private
|
1919
|
+
|
1920
|
+
def path_scope(path)
|
1921
|
+
@scope = @scope.new(path: merge_path_scope(@scope[:path], path))
|
1922
|
+
yield
|
1923
|
+
ensure
|
1924
|
+
@scope = @scope.parent
|
1925
|
+
end
|
1926
|
+
|
1927
|
+
def match_root_route(options)
|
1928
|
+
name = has_named_route?(:root) ? nil : :root
|
1929
|
+
match '/', { :as => name, :via => :get }.merge!(options)
|
1930
|
+
end
|
1785
1931
|
end
|
1786
1932
|
|
1787
1933
|
# Routing Concerns allow you to declare common routes that can be reused
|
@@ -1892,14 +2038,14 @@ module ActionDispatch
|
|
1892
2038
|
class Scope # :nodoc:
|
1893
2039
|
OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
|
1894
2040
|
:controller, :action, :path_names, :constraints,
|
1895
|
-
:shallow, :blocks, :defaults, :options]
|
2041
|
+
:shallow, :blocks, :defaults, :via, :format, :options, :to]
|
1896
2042
|
|
1897
2043
|
RESOURCE_SCOPES = [:resource, :resources]
|
1898
2044
|
RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
|
1899
2045
|
|
1900
2046
|
attr_reader :parent, :scope_level
|
1901
2047
|
|
1902
|
-
def initialize(hash, parent =
|
2048
|
+
def initialize(hash, parent = NULL, scope_level = nil)
|
1903
2049
|
@hash = hash
|
1904
2050
|
@parent = parent
|
1905
2051
|
@scope_level = scope_level
|
@@ -1947,27 +2093,34 @@ module ActionDispatch
|
|
1947
2093
|
end
|
1948
2094
|
|
1949
2095
|
def new_level(level)
|
1950
|
-
self.class.new(
|
1951
|
-
end
|
1952
|
-
|
1953
|
-
def fetch(key, &block)
|
1954
|
-
@hash.fetch(key, &block)
|
2096
|
+
self.class.new(frame, self, level)
|
1955
2097
|
end
|
1956
2098
|
|
1957
2099
|
def [](key)
|
1958
|
-
|
2100
|
+
scope = find { |node| node.frame.key? key }
|
2101
|
+
scope && scope.frame[key]
|
1959
2102
|
end
|
1960
2103
|
|
1961
|
-
|
1962
|
-
|
2104
|
+
include Enumerable
|
2105
|
+
|
2106
|
+
def each
|
2107
|
+
node = self
|
2108
|
+
loop do
|
2109
|
+
break if node.equal? NULL
|
2110
|
+
yield node
|
2111
|
+
node = node.parent
|
2112
|
+
end
|
1963
2113
|
end
|
2114
|
+
|
2115
|
+
def frame; @hash; end
|
2116
|
+
|
2117
|
+
NULL = Scope.new(nil, nil)
|
1964
2118
|
end
|
1965
2119
|
|
1966
2120
|
def initialize(set) #:nodoc:
|
1967
2121
|
@set = set
|
1968
2122
|
@scope = Scope.new({ :path_names => @set.resources_path_names })
|
1969
2123
|
@concerns = {}
|
1970
|
-
@nesting = []
|
1971
2124
|
end
|
1972
2125
|
|
1973
2126
|
include Base
|