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