actionpack 4.1.7 → 4.2.1
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 +311 -527
- data/README.rdoc +7 -2
- data/lib/abstract_controller/base.rb +16 -6
- data/lib/abstract_controller/callbacks.rb +28 -51
- data/lib/abstract_controller/helpers.rb +11 -4
- data/lib/abstract_controller/railties/routes_helpers.rb +3 -3
- data/lib/abstract_controller/url_for.rb +1 -1
- data/lib/action_controller/base.rb +2 -1
- data/lib/action_controller/caching/fragments.rb +7 -1
- data/lib/action_controller/caching.rb +1 -1
- data/lib/action_controller/log_subscriber.rb +26 -26
- data/lib/action_controller/metal/conditional_get.rb +37 -12
- data/lib/action_controller/metal/etag_with_template_digest.rb +50 -0
- data/lib/action_controller/metal/exceptions.rb +1 -1
- data/lib/action_controller/metal/force_ssl.rb +1 -1
- data/lib/action_controller/metal/head.rb +7 -3
- data/lib/action_controller/metal/http_authentication.rb +14 -9
- data/lib/action_controller/metal/instrumentation.rb +8 -5
- data/lib/action_controller/metal/live.rb +57 -6
- data/lib/action_controller/metal/mime_responds.rb +23 -246
- data/lib/action_controller/metal/params_wrapper.rb +2 -2
- data/lib/action_controller/metal/rack_delegation.rb +1 -1
- data/lib/action_controller/metal/redirecting.rb +14 -8
- data/lib/action_controller/metal/renderers.rb +30 -10
- data/lib/action_controller/metal/rendering.rb +2 -6
- data/lib/action_controller/metal/request_forgery_protection.rb +78 -7
- data/lib/action_controller/metal/streaming.rb +1 -1
- data/lib/action_controller/metal/strong_parameters.rb +125 -12
- data/lib/action_controller/metal/url_for.rb +11 -12
- data/lib/action_controller/metal.rb +12 -11
- data/lib/action_controller/model_naming.rb +1 -1
- data/lib/action_controller/railtie.rb +4 -0
- data/lib/action_controller/test_case.rb +112 -75
- data/lib/action_controller.rb +1 -1
- data/lib/action_dispatch/http/cache.rb +5 -4
- data/lib/action_dispatch/http/filter_parameters.rb +2 -2
- data/lib/action_dispatch/http/headers.rb +43 -9
- data/lib/action_dispatch/http/mime_negotiation.rb +10 -3
- data/lib/action_dispatch/http/mime_type.rb +2 -2
- data/lib/action_dispatch/http/parameter_filter.rb +1 -1
- data/lib/action_dispatch/http/parameters.rb +11 -26
- data/lib/action_dispatch/http/request.rb +37 -11
- data/lib/action_dispatch/http/response.rb +70 -18
- data/lib/action_dispatch/http/upload.rb +3 -8
- data/lib/action_dispatch/http/url.rb +88 -69
- data/lib/action_dispatch/journey/formatter.rb +33 -17
- data/lib/action_dispatch/journey/gtg/builder.rb +3 -3
- data/lib/action_dispatch/journey/gtg/simulator.rb +10 -7
- data/lib/action_dispatch/journey/gtg/transition_table.rb +20 -28
- data/lib/action_dispatch/journey/nfa/dot.rb +2 -2
- data/lib/action_dispatch/journey/nfa/simulator.rb +1 -1
- data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -5
- data/lib/action_dispatch/journey/nodes/node.rb +4 -0
- data/lib/action_dispatch/journey/parser.rb +52 -60
- data/lib/action_dispatch/journey/parser.y +11 -10
- data/lib/action_dispatch/journey/path/pattern.rb +16 -19
- data/lib/action_dispatch/journey/route.rb +3 -18
- data/lib/action_dispatch/journey/router/strexp.rb +9 -6
- data/lib/action_dispatch/journey/router.rb +53 -77
- data/lib/action_dispatch/journey/scanner.rb +5 -5
- data/lib/action_dispatch/journey/visitors.rb +81 -92
- data/lib/action_dispatch/journey/visualizer/fsm.css +0 -4
- data/lib/action_dispatch/journey/visualizer/index.html.erb +2 -2
- data/lib/action_dispatch/middleware/callbacks.rb +1 -1
- data/lib/action_dispatch/middleware/cookies.rb +29 -29
- data/lib/action_dispatch/middleware/debug_exceptions.rb +15 -4
- data/lib/action_dispatch/middleware/exception_wrapper.rb +50 -18
- data/lib/action_dispatch/middleware/flash.rb +13 -7
- data/lib/action_dispatch/middleware/params_parser.rb +1 -1
- data/lib/action_dispatch/middleware/public_exceptions.rb +12 -3
- data/lib/action_dispatch/middleware/remote_ip.rb +40 -54
- data/lib/action_dispatch/middleware/request_id.rb +1 -1
- data/lib/action_dispatch/middleware/session/cookie_store.rb +1 -1
- data/lib/action_dispatch/middleware/show_exceptions.rb +1 -0
- data/lib/action_dispatch/middleware/static.rb +66 -37
- data/lib/action_dispatch/middleware/templates/rescues/_source.erb +21 -19
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +37 -9
- data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +2 -8
- data/lib/action_dispatch/middleware/templates/rescues/{diagnostics.erb → diagnostics.html.erb} +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +6 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +4 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +2 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -24
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +0 -1
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +120 -64
- data/lib/action_dispatch/routing/endpoint.rb +10 -0
- data/lib/action_dispatch/routing/inspector.rb +5 -12
- data/lib/action_dispatch/routing/mapper.rb +410 -281
- data/lib/action_dispatch/routing/polymorphic_routes.rb +191 -79
- data/lib/action_dispatch/routing/redirection.rb +10 -12
- data/lib/action_dispatch/routing/route_set.rb +297 -168
- data/lib/action_dispatch/routing/url_for.rb +15 -4
- data/lib/action_dispatch/testing/assertions/dom.rb +2 -26
- data/lib/action_dispatch/testing/assertions/response.rb +2 -7
- data/lib/action_dispatch/testing/assertions/routing.rb +22 -22
- data/lib/action_dispatch/testing/assertions/selector.rb +2 -429
- data/lib/action_dispatch/testing/assertions/tag.rb +2 -134
- data/lib/action_dispatch/testing/assertions.rb +11 -7
- data/lib/action_dispatch/testing/integration.rb +24 -19
- data/lib/action_dispatch/testing/test_request.rb +1 -1
- data/lib/action_dispatch/testing/test_response.rb +7 -0
- data/lib/action_pack/gem_version.rb +3 -3
- metadata +55 -13
- data/lib/action_controller/metal/responder.rb +0 -297
@@ -5,22 +5,15 @@ module ActionDispatch
|
|
5
5
|
module Routing
|
6
6
|
class RouteWrapper < SimpleDelegator
|
7
7
|
def endpoint
|
8
|
-
|
8
|
+
app.dispatcher? ? "#{controller}##{action}" : rack_app.inspect
|
9
9
|
end
|
10
10
|
|
11
11
|
def constraints
|
12
12
|
requirements.except(:controller, :action)
|
13
13
|
end
|
14
14
|
|
15
|
-
def rack_app
|
16
|
-
|
17
|
-
class_name = app.class.name.to_s
|
18
|
-
if class_name == "ActionDispatch::Routing::Mapper::Constraints"
|
19
|
-
rack_app(app.app)
|
20
|
-
elsif ActionDispatch::Routing::Redirect === app || class_name !~ /^ActionDispatch::Routing/
|
21
|
-
app
|
22
|
-
end
|
23
|
-
end
|
15
|
+
def rack_app
|
16
|
+
app.app
|
24
17
|
end
|
25
18
|
|
26
19
|
def verb
|
@@ -55,7 +48,7 @@ module ActionDispatch
|
|
55
48
|
def reqs
|
56
49
|
@reqs ||= begin
|
57
50
|
reqs = endpoint
|
58
|
-
reqs += " #{constraints
|
51
|
+
reqs += " #{constraints}" unless constraints.empty?
|
59
52
|
reqs
|
60
53
|
end
|
61
54
|
end
|
@@ -73,7 +66,7 @@ module ActionDispatch
|
|
73
66
|
end
|
74
67
|
|
75
68
|
def engine?
|
76
|
-
rack_app
|
69
|
+
rack_app.respond_to?(:routes)
|
77
70
|
end
|
78
71
|
end
|
79
72
|
|
@@ -4,132 +4,201 @@ require 'active_support/core_ext/hash/slice'
|
|
4
4
|
require 'active_support/core_ext/enumerable'
|
5
5
|
require 'active_support/core_ext/array/extract_options'
|
6
6
|
require 'active_support/core_ext/module/remove_method'
|
7
|
+
require 'active_support/core_ext/string/filters'
|
7
8
|
require 'active_support/inflector'
|
8
9
|
require 'action_dispatch/routing/redirection'
|
10
|
+
require 'action_dispatch/routing/endpoint'
|
11
|
+
require 'active_support/deprecation'
|
9
12
|
|
10
13
|
module ActionDispatch
|
11
14
|
module Routing
|
12
15
|
class Mapper
|
13
16
|
URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
|
14
|
-
SCOPE_OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
|
15
|
-
:controller, :action, :path_names, :constraints,
|
16
|
-
:shallow, :blocks, :defaults, :options]
|
17
|
-
|
18
|
-
class Constraints #:nodoc:
|
19
|
-
def self.new(app, constraints, request = Rack::Request)
|
20
|
-
if constraints.any?
|
21
|
-
super(app, constraints, request)
|
22
|
-
else
|
23
|
-
app
|
24
|
-
end
|
25
|
-
end
|
26
17
|
|
18
|
+
class Constraints < Endpoint #:nodoc:
|
27
19
|
attr_reader :app, :constraints
|
28
20
|
|
29
|
-
def initialize(app, constraints,
|
30
|
-
|
21
|
+
def initialize(app, constraints, dispatcher_p)
|
22
|
+
# Unwrap Constraints objects. I don't actually think it's possible
|
23
|
+
# to pass a Constraints object to this constructor, but there were
|
24
|
+
# multiple places that kept testing children of this object. I
|
25
|
+
# *think* they were just being defensive, but I have no idea.
|
26
|
+
if app.is_a?(self.class)
|
27
|
+
constraints += app.constraints
|
28
|
+
app = app.app
|
29
|
+
end
|
30
|
+
|
31
|
+
@dispatcher = dispatcher_p
|
32
|
+
|
33
|
+
@app, @constraints, = app, constraints
|
31
34
|
end
|
32
35
|
|
33
|
-
def
|
34
|
-
req = @request.new(env)
|
36
|
+
def dispatcher?; @dispatcher; end
|
35
37
|
|
38
|
+
def matches?(req)
|
36
39
|
@constraints.all? do |constraint|
|
37
40
|
(constraint.respond_to?(:matches?) && constraint.matches?(req)) ||
|
38
41
|
(constraint.respond_to?(:call) && constraint.call(*constraint_args(constraint, req)))
|
39
42
|
end
|
40
|
-
ensure
|
41
|
-
req.reset_parameters
|
42
43
|
end
|
43
44
|
|
44
|
-
def
|
45
|
-
|
45
|
+
def serve(req)
|
46
|
+
return [ 404, {'X-Cascade' => 'pass'}, [] ] unless matches?(req)
|
47
|
+
|
48
|
+
if dispatcher?
|
49
|
+
@app.serve req
|
50
|
+
else
|
51
|
+
@app.call req.env
|
52
|
+
end
|
46
53
|
end
|
47
54
|
|
48
55
|
private
|
49
56
|
def constraint_args(constraint, request)
|
50
|
-
constraint.arity == 1 ? [request] : [request.
|
57
|
+
constraint.arity == 1 ? [request] : [request.path_parameters, request]
|
51
58
|
end
|
52
59
|
end
|
53
60
|
|
54
61
|
class Mapping #:nodoc:
|
55
|
-
IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix, :format]
|
56
62
|
ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
|
57
|
-
WILDCARD_PATH = %r{\*([^/\)]+)\)?$}
|
58
63
|
|
59
|
-
attr_reader :
|
64
|
+
attr_reader :requirements, :conditions, :defaults
|
65
|
+
attr_reader :to, :default_controller, :default_action, :as, :anchor
|
60
66
|
|
61
|
-
def
|
62
|
-
|
63
|
-
@requirements, @conditions, @defaults = {}, {}, {}
|
67
|
+
def self.build(scope, set, path, as, options)
|
68
|
+
options = scope[:options].merge(options) if scope[:options]
|
64
69
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
+
options.delete :only
|
71
|
+
options.delete :except
|
72
|
+
options.delete :shallow_path
|
73
|
+
options.delete :shallow_prefix
|
74
|
+
options.delete :shallow
|
75
|
+
|
76
|
+
defaults = (scope[:defaults] || {}).merge options.delete(:defaults) || {}
|
77
|
+
|
78
|
+
new scope, set, path, defaults, as, options
|
79
|
+
end
|
80
|
+
|
81
|
+
def initialize(scope, set, path, defaults, as, options)
|
82
|
+
@requirements, @conditions = {}, {}
|
83
|
+
@defaults = defaults
|
84
|
+
@set = set
|
85
|
+
|
86
|
+
@to = options.delete :to
|
87
|
+
@default_controller = options.delete(:controller) || scope[:controller]
|
88
|
+
@default_action = options.delete(:action) || scope[:action]
|
89
|
+
@as = as
|
90
|
+
@anchor = options.delete :anchor
|
91
|
+
|
92
|
+
formatted = options.delete :format
|
93
|
+
via = Array(options.delete(:via) { [] })
|
94
|
+
options_constraints = options.delete :constraints
|
95
|
+
|
96
|
+
path = normalize_path! path, formatted
|
97
|
+
ast = path_ast path
|
98
|
+
path_params = path_params ast
|
99
|
+
|
100
|
+
options = normalize_options!(options, formatted, path_params, ast, scope[:module])
|
101
|
+
|
102
|
+
|
103
|
+
split_constraints(path_params, scope[:constraints]) if scope[:constraints]
|
104
|
+
constraints = constraints(options, path_params)
|
105
|
+
|
106
|
+
split_constraints path_params, constraints
|
107
|
+
|
108
|
+
@blocks = blocks(options_constraints, scope[:blocks])
|
109
|
+
|
110
|
+
if options_constraints.is_a?(Hash)
|
111
|
+
split_constraints path_params, options_constraints
|
112
|
+
options_constraints.each do |key, default|
|
113
|
+
if URL_OPTIONS.include?(key) && (String === default || Fixnum === default)
|
114
|
+
@defaults[key] ||= default
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
normalize_format!(formatted)
|
120
|
+
|
121
|
+
@conditions[:path_info] = path
|
122
|
+
@conditions[:parsed_path_info] = ast
|
123
|
+
|
124
|
+
add_request_method(via, @conditions)
|
125
|
+
normalize_defaults!(options)
|
70
126
|
end
|
71
127
|
|
72
128
|
def to_route
|
73
|
-
[ app, conditions, requirements, defaults,
|
129
|
+
[ app(@blocks), conditions, requirements, defaults, as, anchor ]
|
74
130
|
end
|
75
131
|
|
76
132
|
private
|
77
133
|
|
78
|
-
def normalize_path!
|
79
|
-
|
80
|
-
@path = Mapper.normalize_path(@path)
|
134
|
+
def normalize_path!(path, format)
|
135
|
+
path = Mapper.normalize_path(path)
|
81
136
|
|
82
|
-
if
|
83
|
-
|
84
|
-
elsif optional_format?
|
85
|
-
|
137
|
+
if format == true
|
138
|
+
"#{path}.:format"
|
139
|
+
elsif optional_format?(path, format)
|
140
|
+
"#{path}(.:format)"
|
141
|
+
else
|
142
|
+
path
|
86
143
|
end
|
87
144
|
end
|
88
145
|
|
89
|
-
def
|
90
|
-
|
146
|
+
def optional_format?(path, format)
|
147
|
+
format != false && !path.include?(':format') && !path.end_with?('/')
|
91
148
|
end
|
92
149
|
|
93
|
-
def
|
94
|
-
options[:format] != false && !path.include?(':format') && !path.end_with?('/')
|
95
|
-
end
|
96
|
-
|
97
|
-
def normalize_options!
|
98
|
-
@options.reverse_merge!(scope[:options]) if scope[:options]
|
99
|
-
path_without_format = path.sub(/\(\.:format\)$/, '')
|
100
|
-
|
150
|
+
def normalize_options!(options, formatted, path_params, path_ast, modyoule)
|
101
151
|
# Add a constraint for wildcard route to make it non-greedy and match the
|
102
152
|
# optional format part of the route by default
|
103
|
-
if
|
104
|
-
|
153
|
+
if formatted != false
|
154
|
+
path_ast.grep(Journey::Nodes::Star) do |node|
|
155
|
+
options[node.name.to_sym] ||= /.+?/
|
156
|
+
end
|
105
157
|
end
|
106
158
|
|
107
|
-
if
|
108
|
-
raise ArgumentError, ":controller segment is not allowed within a namespace block" if
|
159
|
+
if path_params.include?(:controller)
|
160
|
+
raise ArgumentError, ":controller segment is not allowed within a namespace block" if modyoule
|
109
161
|
|
110
162
|
# Add a default constraint for :controller path segments that matches namespaced
|
111
163
|
# controllers with default routes like :controller/:action/:id(.:format), e.g:
|
112
164
|
# GET /admin/products/show/1
|
113
165
|
# => { controller: 'admin/products', action: 'show', id: '1' }
|
114
|
-
|
166
|
+
options[:controller] ||= /.+?/
|
115
167
|
end
|
116
168
|
|
117
|
-
|
169
|
+
if to.respond_to? :call
|
170
|
+
options
|
171
|
+
else
|
172
|
+
to_endpoint = split_to to
|
173
|
+
controller = to_endpoint[0] || default_controller
|
174
|
+
action = to_endpoint[1] || default_action
|
175
|
+
|
176
|
+
controller = add_controller_module(controller, modyoule)
|
177
|
+
|
178
|
+
options.merge! check_controller_and_action(path_params, controller, action)
|
179
|
+
end
|
118
180
|
end
|
119
181
|
|
120
|
-
def
|
121
|
-
constraints.
|
122
|
-
|
123
|
-
|
124
|
-
|
182
|
+
def split_constraints(path_params, constraints)
|
183
|
+
constraints.each_pair do |key, requirement|
|
184
|
+
if path_params.include?(key) || key == :controller
|
185
|
+
verify_regexp_requirement(requirement) if requirement.is_a?(Regexp)
|
186
|
+
@requirements[key] = requirement
|
187
|
+
else
|
188
|
+
@conditions[key] = requirement
|
189
|
+
end
|
125
190
|
end
|
191
|
+
end
|
126
192
|
|
127
|
-
|
193
|
+
def normalize_format!(formatted)
|
194
|
+
if formatted == true
|
128
195
|
@requirements[:format] ||= /.+/
|
129
|
-
elsif Regexp ===
|
130
|
-
@requirements[:format] =
|
131
|
-
|
132
|
-
|
196
|
+
elsif Regexp === formatted
|
197
|
+
@requirements[:format] = formatted
|
198
|
+
@defaults[:format] = nil
|
199
|
+
elsif String === formatted
|
200
|
+
@requirements[:format] = Regexp.compile(formatted)
|
201
|
+
@defaults[:format] = formatted
|
133
202
|
end
|
134
203
|
end
|
135
204
|
|
@@ -143,169 +212,147 @@ module ActionDispatch
|
|
143
212
|
end
|
144
213
|
end
|
145
214
|
|
146
|
-
def normalize_defaults!
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
options.each do |key, default|
|
151
|
-
unless Regexp === default || IGNORE_OPTIONS.include?(key)
|
215
|
+
def normalize_defaults!(options)
|
216
|
+
options.each_pair do |key, default|
|
217
|
+
unless Regexp === default
|
152
218
|
@defaults[key] = default
|
153
219
|
end
|
154
220
|
end
|
155
|
-
|
156
|
-
if options[:constraints].is_a?(Hash)
|
157
|
-
options[:constraints].each do |key, default|
|
158
|
-
if URL_OPTIONS.include?(key) && (String === default || Fixnum === default)
|
159
|
-
@defaults[key] ||= default
|
160
|
-
end
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
if Regexp === options[:format]
|
165
|
-
@defaults[:format] = nil
|
166
|
-
elsif String === options[:format]
|
167
|
-
@defaults[:format] = options[:format]
|
168
|
-
end
|
169
221
|
end
|
170
222
|
|
171
|
-
def
|
172
|
-
|
173
|
-
|
174
|
-
constraints.each do |key, condition|
|
175
|
-
unless segment_keys.include?(key) || key == :controller
|
176
|
-
@conditions[key] = condition
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
required_defaults = []
|
181
|
-
options.each do |key, required_default|
|
182
|
-
unless segment_keys.include?(key) || IGNORE_OPTIONS.include?(key) || Regexp === required_default
|
183
|
-
required_defaults << key
|
184
|
-
end
|
223
|
+
def verify_callable_constraint(callable_constraint)
|
224
|
+
unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?)
|
225
|
+
raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?"
|
185
226
|
end
|
186
|
-
|
227
|
+
end
|
187
228
|
|
188
|
-
|
229
|
+
def add_request_method(via, conditions)
|
230
|
+
return if via == [:all]
|
189
231
|
|
190
|
-
if
|
232
|
+
if via.empty?
|
191
233
|
msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
|
192
234
|
"If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
|
193
235
|
"If you want to expose your action to GET, use `get` in the router:\n" \
|
194
236
|
" Instead of: match \"controller#action\"\n" \
|
195
237
|
" Do: get \"controller#action\""
|
196
|
-
raise msg
|
238
|
+
raise ArgumentError, msg
|
197
239
|
end
|
198
240
|
|
199
|
-
|
200
|
-
@conditions[:request_method] = Array(via).map { |m| m.to_s.dasherize.upcase }
|
201
|
-
end
|
241
|
+
conditions[:request_method] = via.map { |m| m.to_s.dasherize.upcase }
|
202
242
|
end
|
203
243
|
|
204
|
-
def app
|
205
|
-
Constraints.new(endpoint, blocks, @set.request_class)
|
206
|
-
end
|
207
|
-
|
208
|
-
def default_controller_and_action
|
244
|
+
def app(blocks)
|
209
245
|
if to.respond_to?(:call)
|
210
|
-
|
246
|
+
Constraints.new(to, blocks, false)
|
247
|
+
elsif blocks.any?
|
248
|
+
Constraints.new(dispatcher(defaults), blocks, true)
|
211
249
|
else
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
action = to.to_s
|
216
|
-
end
|
217
|
-
|
218
|
-
controller ||= default_controller
|
219
|
-
action ||= default_action
|
220
|
-
|
221
|
-
if @scope[:module] && !controller.is_a?(Regexp)
|
222
|
-
if controller =~ %r{\A/}
|
223
|
-
controller = controller[1..-1]
|
224
|
-
else
|
225
|
-
controller = [@scope[:module], controller].compact.join("/").presence
|
226
|
-
end
|
227
|
-
end
|
228
|
-
|
229
|
-
if controller.is_a?(String) && controller =~ %r{\A/}
|
230
|
-
raise ArgumentError, "controller name should not start with a slash"
|
231
|
-
end
|
250
|
+
dispatcher(defaults)
|
251
|
+
end
|
252
|
+
end
|
232
253
|
|
233
|
-
|
234
|
-
|
254
|
+
def check_controller_and_action(path_params, controller, action)
|
255
|
+
hash = check_part(:controller, controller, path_params, {}) do |part|
|
256
|
+
translate_controller(part) {
|
257
|
+
message = "'#{part}' is not a supported controller name. This can lead to potential routing problems."
|
258
|
+
message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
|
235
259
|
|
236
|
-
if controller.blank? && segment_keys.exclude?(:controller)
|
237
|
-
message = "Missing :controller key on routes definition, please check your routes."
|
238
260
|
raise ArgumentError, message
|
239
|
-
|
261
|
+
}
|
262
|
+
end
|
240
263
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
264
|
+
check_part(:action, action, path_params, hash) { |part|
|
265
|
+
part.is_a?(Regexp) ? part : part.to_s
|
266
|
+
}
|
267
|
+
end
|
245
268
|
|
246
|
-
|
247
|
-
|
248
|
-
|
269
|
+
def check_part(name, part, path_params, hash)
|
270
|
+
if part
|
271
|
+
hash[name] = yield(part)
|
272
|
+
else
|
273
|
+
unless path_params.include?(name)
|
274
|
+
message = "Missing :#{name} key on routes definition, please check your routes."
|
249
275
|
raise ArgumentError, message
|
250
276
|
end
|
251
|
-
|
252
|
-
hash = {}
|
253
|
-
hash[:controller] = controller unless controller.blank?
|
254
|
-
hash[:action] = action unless action.blank?
|
255
|
-
hash
|
256
277
|
end
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
278
|
+
hash
|
279
|
+
end
|
280
|
+
|
281
|
+
def split_to(to)
|
282
|
+
case to
|
283
|
+
when Symbol
|
284
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
285
|
+
Defining a route where `to` is a symbol is deprecated.
|
286
|
+
Please change `to: :#{to}` to `action: :#{to}`.
|
287
|
+
MSG
|
288
|
+
|
289
|
+
[nil, to.to_s]
|
290
|
+
when /#/ then to.split('#')
|
291
|
+
when String
|
292
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
293
|
+
Defining a route where `to` is a controller without an action is deprecated.
|
294
|
+
Please change `to: :#{to}` to `controller: :#{to}`.
|
295
|
+
MSG
|
296
|
+
|
297
|
+
[to, nil]
|
262
298
|
else
|
263
|
-
|
299
|
+
[]
|
264
300
|
end
|
265
301
|
end
|
266
302
|
|
267
|
-
def
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
303
|
+
def add_controller_module(controller, modyoule)
|
304
|
+
if modyoule && !controller.is_a?(Regexp)
|
305
|
+
if controller =~ %r{\A/}
|
306
|
+
controller[1..-1]
|
307
|
+
else
|
308
|
+
[modyoule, controller].compact.join("/")
|
273
309
|
end
|
274
|
-
|
275
|
-
|
310
|
+
else
|
311
|
+
controller
|
276
312
|
end
|
277
313
|
end
|
278
314
|
|
279
|
-
def
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
def path_pattern
|
284
|
-
Journey::Path::Pattern.new(strexp)
|
285
|
-
end
|
315
|
+
def translate_controller(controller)
|
316
|
+
return controller if Regexp === controller
|
317
|
+
return controller.to_s if controller =~ /\A[a-z_0-9][a-z_0-9\/]*\z/
|
286
318
|
|
287
|
-
|
288
|
-
Journey::Router::Strexp.compile(path, requirements, SEPARATORS)
|
319
|
+
yield
|
289
320
|
end
|
290
321
|
|
291
|
-
def
|
292
|
-
|
322
|
+
def blocks(options_constraints, scope_blocks)
|
323
|
+
if options_constraints && !options_constraints.is_a?(Hash)
|
324
|
+
verify_callable_constraint(options_constraints)
|
325
|
+
[options_constraints]
|
326
|
+
else
|
327
|
+
scope_blocks || []
|
328
|
+
end
|
293
329
|
end
|
294
330
|
|
295
|
-
def
|
296
|
-
|
331
|
+
def constraints(options, path_params)
|
332
|
+
constraints = {}
|
333
|
+
required_defaults = []
|
334
|
+
options.each_pair do |key, option|
|
335
|
+
if Regexp === option
|
336
|
+
constraints[key] = option
|
337
|
+
else
|
338
|
+
required_defaults << key unless path_params.include?(key)
|
339
|
+
end
|
340
|
+
end
|
341
|
+
@conditions[:required_defaults] = required_defaults
|
342
|
+
constraints
|
297
343
|
end
|
298
344
|
|
299
|
-
def
|
300
|
-
|
345
|
+
def path_params(ast)
|
346
|
+
ast.grep(Journey::Nodes::Symbol).map { |n| n.name.to_sym }
|
301
347
|
end
|
302
348
|
|
303
|
-
def
|
304
|
-
|
349
|
+
def path_ast(path)
|
350
|
+
parser = Journey::Parser.new
|
351
|
+
parser.parse path
|
305
352
|
end
|
306
353
|
|
307
|
-
def
|
308
|
-
|
354
|
+
def dispatcher(defaults)
|
355
|
+
@set.dispatcher defaults
|
309
356
|
end
|
310
357
|
end
|
311
358
|
|
@@ -342,19 +389,18 @@ module ActionDispatch
|
|
342
389
|
|
343
390
|
# Matches a url pattern to one or more routes.
|
344
391
|
#
|
345
|
-
# You should not use the
|
392
|
+
# You should not use the +match+ method in your router
|
346
393
|
# without specifying an HTTP method.
|
347
394
|
#
|
348
395
|
# If you want to expose your action to both GET and POST, use:
|
349
|
-
#
|
396
|
+
#
|
350
397
|
# # sets :controller, :action and :id in params
|
351
398
|
# match ':controller/:action/:id', via: [:get, :post]
|
352
399
|
#
|
353
|
-
# Note that +:controller+, +:action
|
354
|
-
# parameters and thus available
|
355
|
-
# in an action.
|
400
|
+
# Note that +:controller+, +:action+ and +:id+ are interpreted as url
|
401
|
+
# query parameters and thus available through +params+ in an action.
|
356
402
|
#
|
357
|
-
# If you want to expose your action to GET, use
|
403
|
+
# If you want to expose your action to GET, use +get+ in the router:
|
358
404
|
#
|
359
405
|
# Instead of:
|
360
406
|
#
|
@@ -374,24 +420,28 @@ module ActionDispatch
|
|
374
420
|
# # params[:category] = 'rock/classic'
|
375
421
|
# # params[:title] = 'stairway-to-heaven'
|
376
422
|
#
|
423
|
+
# To match a wildcard parameter, it must have a name assigned to it.
|
424
|
+
# Without a variable name to attach the glob parameter to, the route
|
425
|
+
# can't be parsed.
|
426
|
+
#
|
377
427
|
# When a pattern points to an internal route, the route's +:action+ and
|
378
428
|
# +:controller+ should be set in options or hash shorthand. Examples:
|
379
429
|
#
|
380
|
-
# match 'photos/:id' => 'photos#show', via:
|
381
|
-
# match 'photos/:id', to: 'photos#show', via:
|
382
|
-
# match 'photos/:id', controller: 'photos', action: 'show', via:
|
430
|
+
# match 'photos/:id' => 'photos#show', via: :get
|
431
|
+
# match 'photos/:id', to: 'photos#show', via: :get
|
432
|
+
# match 'photos/:id', controller: 'photos', action: 'show', via: :get
|
383
433
|
#
|
384
434
|
# A pattern can also point to a +Rack+ endpoint i.e. anything that
|
385
435
|
# responds to +call+:
|
386
436
|
#
|
387
|
-
# match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] }, via:
|
388
|
-
# match 'photos/:id', to: PhotoRackApp, via:
|
437
|
+
# match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] }, via: :get
|
438
|
+
# match 'photos/:id', to: PhotoRackApp, via: :get
|
389
439
|
# # Yes, controller actions are just rack endpoints
|
390
|
-
# match 'photos/:id', to: PhotosController.action(:show), via:
|
440
|
+
# match 'photos/:id', to: PhotosController.action(:show), via: :get
|
391
441
|
#
|
392
442
|
# Because requesting various HTTP verbs with a single action has security
|
393
443
|
# implications, you must either specify the actions in
|
394
|
-
# the via options or use one of the
|
444
|
+
# the via options or use one of the HttpHelpers[rdoc-ref:HttpHelpers]
|
395
445
|
# instead +match+
|
396
446
|
#
|
397
447
|
# === Options
|
@@ -405,7 +455,7 @@ module ActionDispatch
|
|
405
455
|
# The route's action.
|
406
456
|
#
|
407
457
|
# [:param]
|
408
|
-
# Overrides the default resource identifier
|
458
|
+
# Overrides the default resource identifier +:id+ (name of the
|
409
459
|
# dynamic segment used to generate the routes).
|
410
460
|
# You can access that segment from your controller using
|
411
461
|
# <tt>params[<:param>]</tt>.
|
@@ -416,7 +466,7 @@ module ActionDispatch
|
|
416
466
|
# [:module]
|
417
467
|
# The namespace for :controller.
|
418
468
|
#
|
419
|
-
# match 'path', to: 'c#a', module: 'sekret', controller: 'posts', via:
|
469
|
+
# match 'path', to: 'c#a', module: 'sekret', controller: 'posts', via: :get
|
420
470
|
# # => Sekret::PostsController
|
421
471
|
#
|
422
472
|
# See <tt>Scoping#namespace</tt> for its scope equivalent.
|
@@ -435,9 +485,9 @@ module ActionDispatch
|
|
435
485
|
# Points to a +Rack+ endpoint. Can be an object that responds to
|
436
486
|
# +call+ or a string representing a controller's action.
|
437
487
|
#
|
438
|
-
# match 'path', to: 'controller#action', via:
|
439
|
-
# match 'path', to: lambda { |env| [200, {}, ["Success!"]] }, via:
|
440
|
-
# match 'path', to: RackApp, via:
|
488
|
+
# match 'path', to: 'controller#action', via: :get
|
489
|
+
# match 'path', to: lambda { |env| [200, {}, ["Success!"]] }, via: :get
|
490
|
+
# match 'path', to: RackApp, via: :get
|
441
491
|
#
|
442
492
|
# [:on]
|
443
493
|
# Shorthand for wrapping routes in a specific RESTful context. Valid
|
@@ -462,14 +512,14 @@ module ActionDispatch
|
|
462
512
|
# other than path can also be specified with any object
|
463
513
|
# that responds to <tt>===</tt> (eg. String, Array, Range, etc.).
|
464
514
|
#
|
465
|
-
# match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via:
|
515
|
+
# match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via: :get
|
466
516
|
#
|
467
|
-
# match 'json_only', constraints: { format: 'json' }, via:
|
517
|
+
# match 'json_only', constraints: { format: 'json' }, via: :get
|
468
518
|
#
|
469
519
|
# class Whitelist
|
470
520
|
# def matches?(request) request.remote_ip == '1.2.3.4' end
|
471
521
|
# end
|
472
|
-
# match 'path', to: 'c#a', constraints: Whitelist.new, via:
|
522
|
+
# match 'path', to: 'c#a', constraints: Whitelist.new, via: :get
|
473
523
|
#
|
474
524
|
# See <tt>Scoping#constraints</tt> for more examples with its scope
|
475
525
|
# equivalent.
|
@@ -478,7 +528,7 @@ module ActionDispatch
|
|
478
528
|
# Sets defaults for parameters
|
479
529
|
#
|
480
530
|
# # Sets params[:format] to 'jpg' by default
|
481
|
-
# match 'path', to: 'c#a', defaults: { format: 'jpg' }, via:
|
531
|
+
# match 'path', to: 'c#a', defaults: { format: 'jpg' }, via: :get
|
482
532
|
#
|
483
533
|
# See <tt>Scoping#defaults</tt> for its scope equivalent.
|
484
534
|
#
|
@@ -487,7 +537,7 @@ module ActionDispatch
|
|
487
537
|
# false, the pattern matches any request prefixed with the given path.
|
488
538
|
#
|
489
539
|
# # Matches any request starting with 'path'
|
490
|
-
# match 'path', to: 'c#a', anchor: false, via:
|
540
|
+
# match 'path', to: 'c#a', anchor: false, via: :get
|
491
541
|
#
|
492
542
|
# [:format]
|
493
543
|
# Allows you to specify the default value for optional +format+
|
@@ -529,13 +579,15 @@ module ActionDispatch
|
|
529
579
|
|
530
580
|
raise "A rack application must be specified" unless path
|
531
581
|
|
532
|
-
|
582
|
+
rails_app = rails_app? app
|
583
|
+
options[:as] ||= app_name(app, rails_app)
|
584
|
+
|
533
585
|
target_as = name_for_action(options[:as], path)
|
534
586
|
options[:via] ||= :all
|
535
587
|
|
536
588
|
match(path, options.merge(:to => app, :anchor => false, :format => false))
|
537
589
|
|
538
|
-
define_generate_prefix(app, target_as)
|
590
|
+
define_generate_prefix(app, target_as) if rails_app
|
539
591
|
self
|
540
592
|
end
|
541
593
|
|
@@ -556,35 +608,36 @@ module ActionDispatch
|
|
556
608
|
end
|
557
609
|
|
558
610
|
private
|
559
|
-
def
|
560
|
-
|
611
|
+
def rails_app?(app)
|
612
|
+
app.is_a?(Class) && app < Rails::Railtie
|
613
|
+
end
|
561
614
|
|
562
|
-
|
615
|
+
def app_name(app, rails_app)
|
616
|
+
if rails_app
|
563
617
|
app.railtie_name
|
564
|
-
|
565
|
-
class_name = app.
|
618
|
+
elsif app.is_a?(Class)
|
619
|
+
class_name = app.name
|
566
620
|
ActiveSupport::Inflector.underscore(class_name).tr("/", "_")
|
567
621
|
end
|
568
622
|
end
|
569
623
|
|
570
624
|
def define_generate_prefix(app, name)
|
571
|
-
|
572
|
-
|
573
|
-
_route = @set.named_routes.routes[name.to_sym]
|
625
|
+
_route = @set.named_routes.get name
|
574
626
|
_routes = @set
|
575
627
|
app.routes.define_mounted_helper(name)
|
576
|
-
app.routes.
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
628
|
+
app.routes.extend Module.new {
|
629
|
+
def optimize_routes_generation?; false; end
|
630
|
+
define_method :find_script_name do |options|
|
631
|
+
if options.key? :script_name
|
632
|
+
super(options)
|
633
|
+
else
|
634
|
+
prefix_options = options.slice(*_route.segment_keys)
|
635
|
+
# we must actually delete prefix segment keys to avoid passing them to next url_for
|
636
|
+
_route.segment_keys.each { |k| options.delete(k) }
|
637
|
+
_routes.url_helpers.send("#{name}_path", prefix_options)
|
638
|
+
end
|
586
639
|
end
|
587
|
-
|
640
|
+
}
|
588
641
|
end
|
589
642
|
end
|
590
643
|
|
@@ -671,7 +724,7 @@ module ActionDispatch
|
|
671
724
|
# resources :posts, module: "admin"
|
672
725
|
#
|
673
726
|
# If you want to route /admin/posts to +PostsController+
|
674
|
-
# (without the Admin
|
727
|
+
# (without the <tt>Admin::</tt> module prefix), you could use
|
675
728
|
#
|
676
729
|
# scope "/admin" do
|
677
730
|
# resources :posts
|
@@ -725,7 +778,7 @@ module ActionDispatch
|
|
725
778
|
# end
|
726
779
|
def scope(*args)
|
727
780
|
options = args.extract_options!.dup
|
728
|
-
|
781
|
+
scope = {}
|
729
782
|
|
730
783
|
options[:path] = args.flatten.join('/') if args.any?
|
731
784
|
options[:constraints] ||= {}
|
@@ -745,7 +798,7 @@ module ActionDispatch
|
|
745
798
|
block, options[:constraints] = options[:constraints], {}
|
746
799
|
end
|
747
800
|
|
748
|
-
|
801
|
+
@scope.options.each do |option|
|
749
802
|
if option == :blocks
|
750
803
|
value = block
|
751
804
|
elsif option == :options
|
@@ -755,15 +808,15 @@ module ActionDispatch
|
|
755
808
|
end
|
756
809
|
|
757
810
|
if value
|
758
|
-
|
759
|
-
@scope[option] = send("merge_#{option}_scope", @scope[option], value)
|
811
|
+
scope[option] = send("merge_#{option}_scope", @scope[option], value)
|
760
812
|
end
|
761
813
|
end
|
762
814
|
|
815
|
+
@scope = @scope.new scope
|
763
816
|
yield
|
764
817
|
self
|
765
818
|
ensure
|
766
|
-
@scope.
|
819
|
+
@scope = @scope.parent
|
767
820
|
end
|
768
821
|
|
769
822
|
# Scopes routes to a specific controller
|
@@ -1001,8 +1054,6 @@ module ActionDispatch
|
|
1001
1054
|
VALID_ON_OPTIONS = [:new, :collection, :member]
|
1002
1055
|
RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns]
|
1003
1056
|
CANONICAL_ACTIONS = %w(index create new show update destroy)
|
1004
|
-
RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
|
1005
|
-
RESOURCE_SCOPES = [:resource, :resources]
|
1006
1057
|
|
1007
1058
|
class Resource #:nodoc:
|
1008
1059
|
attr_reader :controller, :path, :options, :param
|
@@ -1422,7 +1473,20 @@ module ActionDispatch
|
|
1422
1473
|
if rest.empty? && Hash === path
|
1423
1474
|
options = path
|
1424
1475
|
path, to = options.find { |name, _value| name.is_a?(String) }
|
1425
|
-
|
1476
|
+
|
1477
|
+
case to
|
1478
|
+
when Symbol
|
1479
|
+
options[:action] = to
|
1480
|
+
when String
|
1481
|
+
if to =~ /#/
|
1482
|
+
options[:to] = to
|
1483
|
+
else
|
1484
|
+
options[:controller] = to
|
1485
|
+
end
|
1486
|
+
else
|
1487
|
+
options[:to] = to
|
1488
|
+
end
|
1489
|
+
|
1426
1490
|
options.delete(path)
|
1427
1491
|
paths = [path]
|
1428
1492
|
else
|
@@ -1456,14 +1520,14 @@ module ActionDispatch
|
|
1456
1520
|
end
|
1457
1521
|
|
1458
1522
|
def using_match_shorthand?(path, options)
|
1459
|
-
path && (options[:to] || options[:action]).nil? && path =~ %r{
|
1523
|
+
path && (options[:to] || options[:action]).nil? && path =~ %r{^/?[-\w]+/[-\w/]+$}
|
1460
1524
|
end
|
1461
1525
|
|
1462
1526
|
def decomposed_match(path, options) # :nodoc:
|
1463
1527
|
if on = options.delete(:on)
|
1464
1528
|
send(on) { decomposed_match(path, options) }
|
1465
1529
|
else
|
1466
|
-
case @scope
|
1530
|
+
case @scope.scope_level
|
1467
1531
|
when :resources
|
1468
1532
|
nested { decomposed_match(path, options) }
|
1469
1533
|
when :resource
|
@@ -1476,6 +1540,8 @@ module ActionDispatch
|
|
1476
1540
|
|
1477
1541
|
def add_route(action, options) # :nodoc:
|
1478
1542
|
path = path_for_action(action, options.delete(:path))
|
1543
|
+
raise ArgumentError, "path is required" if path.blank?
|
1544
|
+
|
1479
1545
|
action = action.to_s.dup
|
1480
1546
|
|
1481
1547
|
if action =~ /^[\w\-\/]+$/
|
@@ -1484,13 +1550,13 @@ module ActionDispatch
|
|
1484
1550
|
action = nil
|
1485
1551
|
end
|
1486
1552
|
|
1487
|
-
if !options.fetch(:as, true)
|
1488
|
-
|
1489
|
-
|
1490
|
-
|
1491
|
-
|
1553
|
+
as = if !options.fetch(:as, true) # if it's set to nil or false
|
1554
|
+
options.delete(:as)
|
1555
|
+
else
|
1556
|
+
name_for_action(options.delete(:as), action)
|
1557
|
+
end
|
1492
1558
|
|
1493
|
-
mapping = Mapping.
|
1559
|
+
mapping = Mapping.build(@scope, @set, URI.parser.escape(path), as, options)
|
1494
1560
|
app, conditions, requirements, defaults, as, anchor = mapping.to_route
|
1495
1561
|
@set.add_route(app, conditions, requirements, defaults, as, anchor)
|
1496
1562
|
end
|
@@ -1504,7 +1570,7 @@ module ActionDispatch
|
|
1504
1570
|
raise ArgumentError, "must be called with a path and/or options"
|
1505
1571
|
end
|
1506
1572
|
|
1507
|
-
if @scope
|
1573
|
+
if @scope.resources?
|
1508
1574
|
with_scope_level(:root) do
|
1509
1575
|
scope(parent_resource.path) do
|
1510
1576
|
super(options)
|
@@ -1571,40 +1637,39 @@ module ActionDispatch
|
|
1571
1637
|
end
|
1572
1638
|
|
1573
1639
|
def resource_scope? #:nodoc:
|
1574
|
-
|
1640
|
+
@scope.resource_scope?
|
1575
1641
|
end
|
1576
1642
|
|
1577
1643
|
def resource_method_scope? #:nodoc:
|
1578
|
-
|
1644
|
+
@scope.resource_method_scope?
|
1579
1645
|
end
|
1580
1646
|
|
1581
1647
|
def nested_scope? #:nodoc:
|
1582
|
-
@scope
|
1648
|
+
@scope.nested?
|
1583
1649
|
end
|
1584
1650
|
|
1585
1651
|
def with_exclusive_scope
|
1586
1652
|
begin
|
1587
|
-
|
1588
|
-
@scope[:as], @scope[:path] = nil, nil
|
1653
|
+
@scope = @scope.new(:as => nil, :path => nil)
|
1589
1654
|
|
1590
1655
|
with_scope_level(:exclusive) do
|
1591
1656
|
yield
|
1592
1657
|
end
|
1593
1658
|
ensure
|
1594
|
-
@scope
|
1659
|
+
@scope = @scope.parent
|
1595
1660
|
end
|
1596
1661
|
end
|
1597
1662
|
|
1598
1663
|
def with_scope_level(kind)
|
1599
|
-
|
1664
|
+
@scope = @scope.new_level(kind)
|
1600
1665
|
yield
|
1601
1666
|
ensure
|
1602
|
-
@scope
|
1667
|
+
@scope = @scope.parent
|
1603
1668
|
end
|
1604
1669
|
|
1605
1670
|
def resource_scope(kind, resource) #:nodoc:
|
1606
1671
|
resource.shallow = @scope[:shallow]
|
1607
|
-
|
1672
|
+
@scope = @scope.new(:scope_level_resource => resource)
|
1608
1673
|
@nesting.push(resource)
|
1609
1674
|
|
1610
1675
|
with_scope_level(kind) do
|
@@ -1612,7 +1677,7 @@ module ActionDispatch
|
|
1612
1677
|
end
|
1613
1678
|
ensure
|
1614
1679
|
@nesting.pop
|
1615
|
-
@scope
|
1680
|
+
@scope = @scope.parent
|
1616
1681
|
end
|
1617
1682
|
|
1618
1683
|
def nested_options #:nodoc:
|
@@ -1640,21 +1705,22 @@ module ActionDispatch
|
|
1640
1705
|
@scope[:constraints][parent_resource.param]
|
1641
1706
|
end
|
1642
1707
|
|
1643
|
-
def canonical_action?(action
|
1644
|
-
|
1708
|
+
def canonical_action?(action) #:nodoc:
|
1709
|
+
resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
|
1645
1710
|
end
|
1646
1711
|
|
1647
1712
|
def shallow_scope(path, options = {}) #:nodoc:
|
1648
|
-
|
1649
|
-
|
1713
|
+
scope = { :as => @scope[:shallow_prefix],
|
1714
|
+
:path => @scope[:shallow_path] }
|
1715
|
+
@scope = @scope.new scope
|
1650
1716
|
|
1651
1717
|
scope(path, options) { yield }
|
1652
1718
|
ensure
|
1653
|
-
@scope
|
1719
|
+
@scope = @scope.parent
|
1654
1720
|
end
|
1655
1721
|
|
1656
1722
|
def path_for_action(action, path) #:nodoc:
|
1657
|
-
if canonical_action?(action
|
1723
|
+
if path.blank? && canonical_action?(action)
|
1658
1724
|
@scope[:path].to_s
|
1659
1725
|
else
|
1660
1726
|
"#{@scope[:path]}/#{action_path(action, path)}"
|
@@ -1669,15 +1735,17 @@ module ActionDispatch
|
|
1669
1735
|
def prefix_name_for_action(as, action) #:nodoc:
|
1670
1736
|
if as
|
1671
1737
|
prefix = as
|
1672
|
-
elsif !canonical_action?(action
|
1738
|
+
elsif !canonical_action?(action)
|
1673
1739
|
prefix = action
|
1674
1740
|
end
|
1675
|
-
|
1741
|
+
|
1742
|
+
if prefix && prefix != '/' && !prefix.empty?
|
1743
|
+
Mapper.normalize_name prefix.to_s.tr('-', '_')
|
1744
|
+
end
|
1676
1745
|
end
|
1677
1746
|
|
1678
1747
|
def name_for_action(as, action) #:nodoc:
|
1679
1748
|
prefix = prefix_name_for_action(as, action)
|
1680
|
-
prefix = Mapper.normalize_name(prefix) if prefix
|
1681
1749
|
name_prefix = @scope[:as]
|
1682
1750
|
|
1683
1751
|
if parent_resource
|
@@ -1687,27 +1755,14 @@ module ActionDispatch
|
|
1687
1755
|
member_name = parent_resource.member_name
|
1688
1756
|
end
|
1689
1757
|
|
1690
|
-
name =
|
1691
|
-
when :nested
|
1692
|
-
[name_prefix, prefix]
|
1693
|
-
when :collection
|
1694
|
-
[prefix, name_prefix, collection_name]
|
1695
|
-
when :new
|
1696
|
-
[prefix, :new, name_prefix, member_name]
|
1697
|
-
when :member
|
1698
|
-
[prefix, name_prefix, member_name]
|
1699
|
-
when :root
|
1700
|
-
[name_prefix, collection_name, prefix]
|
1701
|
-
else
|
1702
|
-
[name_prefix, member_name, prefix]
|
1703
|
-
end
|
1758
|
+
name = @scope.action_name(name_prefix, prefix, collection_name, member_name)
|
1704
1759
|
|
1705
|
-
if candidate = name.
|
1760
|
+
if candidate = name.compact.join("_").presence
|
1706
1761
|
# If a name was not explicitly given, we check if it is valid
|
1707
1762
|
# and return nil in case it isn't. Otherwise, we pass the invalid name
|
1708
1763
|
# forward so the underlying router engine treats it and raises an exception.
|
1709
1764
|
if as.nil?
|
1710
|
-
candidate unless
|
1765
|
+
candidate unless candidate !~ /\A[_a-z]/i || @set.named_routes.key?(candidate)
|
1711
1766
|
else
|
1712
1767
|
candidate
|
1713
1768
|
end
|
@@ -1832,9 +1887,83 @@ module ActionDispatch
|
|
1832
1887
|
end
|
1833
1888
|
end
|
1834
1889
|
|
1890
|
+
class Scope # :nodoc:
|
1891
|
+
OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
|
1892
|
+
:controller, :action, :path_names, :constraints,
|
1893
|
+
:shallow, :blocks, :defaults, :options]
|
1894
|
+
|
1895
|
+
RESOURCE_SCOPES = [:resource, :resources]
|
1896
|
+
RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
|
1897
|
+
|
1898
|
+
attr_reader :parent, :scope_level
|
1899
|
+
|
1900
|
+
def initialize(hash, parent = {}, scope_level = nil)
|
1901
|
+
@hash = hash
|
1902
|
+
@parent = parent
|
1903
|
+
@scope_level = scope_level
|
1904
|
+
end
|
1905
|
+
|
1906
|
+
def nested?
|
1907
|
+
scope_level == :nested
|
1908
|
+
end
|
1909
|
+
|
1910
|
+
def resources?
|
1911
|
+
scope_level == :resources
|
1912
|
+
end
|
1913
|
+
|
1914
|
+
def resource_method_scope?
|
1915
|
+
RESOURCE_METHOD_SCOPES.include? scope_level
|
1916
|
+
end
|
1917
|
+
|
1918
|
+
def action_name(name_prefix, prefix, collection_name, member_name)
|
1919
|
+
case scope_level
|
1920
|
+
when :nested
|
1921
|
+
[name_prefix, prefix]
|
1922
|
+
when :collection
|
1923
|
+
[prefix, name_prefix, collection_name]
|
1924
|
+
when :new
|
1925
|
+
[prefix, :new, name_prefix, member_name]
|
1926
|
+
when :member
|
1927
|
+
[prefix, name_prefix, member_name]
|
1928
|
+
when :root
|
1929
|
+
[name_prefix, collection_name, prefix]
|
1930
|
+
else
|
1931
|
+
[name_prefix, member_name, prefix]
|
1932
|
+
end
|
1933
|
+
end
|
1934
|
+
|
1935
|
+
def resource_scope?
|
1936
|
+
RESOURCE_SCOPES.include? scope_level
|
1937
|
+
end
|
1938
|
+
|
1939
|
+
def options
|
1940
|
+
OPTIONS
|
1941
|
+
end
|
1942
|
+
|
1943
|
+
def new(hash)
|
1944
|
+
self.class.new hash, self, scope_level
|
1945
|
+
end
|
1946
|
+
|
1947
|
+
def new_level(level)
|
1948
|
+
self.class.new(self, self, level)
|
1949
|
+
end
|
1950
|
+
|
1951
|
+
def fetch(key, &block)
|
1952
|
+
@hash.fetch(key, &block)
|
1953
|
+
end
|
1954
|
+
|
1955
|
+
def [](key)
|
1956
|
+
@hash.fetch(key) { @parent[key] }
|
1957
|
+
end
|
1958
|
+
|
1959
|
+
def []=(k,v)
|
1960
|
+
@hash[k] = v
|
1961
|
+
end
|
1962
|
+
end
|
1963
|
+
|
1835
1964
|
def initialize(set) #:nodoc:
|
1836
1965
|
@set = set
|
1837
|
-
@scope = { :path_names => @set.resources_path_names }
|
1966
|
+
@scope = Scope.new({ :path_names => @set.resources_path_names })
|
1838
1967
|
@concerns = {}
|
1839
1968
|
@nesting = []
|
1840
1969
|
end
|