actionpack 4.1.7 → 4.2.11
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 +404 -451
- 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/rendering.rb +7 -1
- data/lib/abstract_controller/url_for.rb +1 -1
- data/lib/action_controller/base.rb +3 -2
- 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 +20 -10
- 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 +25 -246
- data/lib/action_controller/metal/params_wrapper.rb +5 -5
- 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 +29 -11
- 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 +129 -14
- 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 +119 -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 +18 -4
- 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 +74 -23
- data/lib/action_dispatch/http/upload.rb +9 -8
- data/lib/action_dispatch/http/url.rb +89 -70
- data/lib/action_dispatch/journey/formatter.rb +34 -18
- 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 +4 -19
- data/lib/action_dispatch/journey/router/strexp.rb +9 -6
- data/lib/action_dispatch/journey/router/utils.rb +1 -1
- data/lib/action_dispatch/journey/router.rb +53 -77
- data/lib/action_dispatch/journey/routes.rb +4 -0
- 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 +34 -34
- 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/ssl.rb +1 -1
- data/lib/action_dispatch/middleware/static.rb +75 -39
- 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/railtie.rb +2 -0
- 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 +414 -283
- 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 +300 -173
- data/lib/action_dispatch/routing/routes_proxy.rb +5 -4
- data/lib/action_dispatch/routing/url_for.rb +17 -5
- 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 +28 -20
- data/lib/action_dispatch/testing/test_request.rb +1 -1
- data/lib/action_dispatch/testing/test_response.rb +1 -5
- data/lib/action_pack/gem_version.rb +3 -3
- metadata +55 -13
- data/lib/action_controller/metal/responder.rb +0 -297
data/lib/action_dispatch/middleware/templates/rescues/{diagnostics.erb → diagnostics.html.erb}
RENAMED
File without changes
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<%= @exception.class.to_s %><%
|
2
|
+
if @request.parameters['controller']
|
3
|
+
%> in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %>
|
4
|
+
<% end %>
|
5
|
+
|
6
|
+
<%= @exception.message %>
|
7
|
+
<%= render template: "rescues/_source" %>
|
8
|
+
<%= render template: "rescues/_trace" %>
|
9
|
+
<%= render template: "rescues/_request_and_response" %>
|
@@ -116,9 +116,15 @@
|
|
116
116
|
background-color: #FFCCCC;
|
117
117
|
}
|
118
118
|
|
119
|
+
.hidden {
|
120
|
+
display: none;
|
121
|
+
}
|
122
|
+
|
119
123
|
a { color: #980905; }
|
120
124
|
a:visited { color: #666; }
|
125
|
+
a.trace-frames { color: #666; }
|
121
126
|
a:hover { color: #C52F24; }
|
127
|
+
a.trace-frames.selected { color: #C52F24 }
|
122
128
|
|
123
129
|
<%= yield :style %>
|
124
130
|
</style>
|
@@ -1,4 +1,3 @@
|
|
1
|
-
<% @source_extract = @exception.source_extract(0, :html) %>
|
2
1
|
<header>
|
3
2
|
<h1>
|
4
3
|
<%= @exception.original_exception.class.to_s %> in
|
@@ -12,29 +11,7 @@
|
|
12
11
|
</p>
|
13
12
|
<pre><code><%= h @exception.message %></code></pre>
|
14
13
|
|
15
|
-
|
16
|
-
<div class="info">
|
17
|
-
<p>Extracted source (around line <strong>#<%= @exception.line_number %></strong>):</p>
|
18
|
-
</div>
|
19
|
-
<div class="data">
|
20
|
-
<table cellpadding="0" cellspacing="0" class="lines">
|
21
|
-
<tr>
|
22
|
-
<td>
|
23
|
-
<pre class="line_numbers">
|
24
|
-
<% @source_extract.keys.each do |line_number| %>
|
25
|
-
<span><%= line_number -%></span>
|
26
|
-
<% end %>
|
27
|
-
</pre>
|
28
|
-
</td>
|
29
|
-
<td width="100%">
|
30
|
-
<pre>
|
31
|
-
<% @source_extract.each do |line, source| -%><div class="line<%= " active" if line == @exception.line_number -%>"><%= source -%></div><% end -%>
|
32
|
-
</pre>
|
33
|
-
</td>
|
34
|
-
</tr>
|
35
|
-
</table>
|
36
|
-
</div>
|
37
|
-
</div>
|
14
|
+
<%= render template: "rescues/_source" %>
|
38
15
|
|
39
16
|
<p><%= @exception.sub_template_message %></p>
|
40
17
|
|
@@ -1,4 +1,3 @@
|
|
1
|
-
<% @source_extract = @exception.source_extract(0, :html) %>
|
2
1
|
<%= @exception.original_exception.class.to_s %> in <%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %>
|
3
2
|
|
4
3
|
Showing <%= @exception.file_name %> where line #<%= @exception.line_number %> raised:
|
@@ -1,24 +1,44 @@
|
|
1
1
|
<% content_for :style do %>
|
2
2
|
#route_table {
|
3
|
-
margin: 0
|
3
|
+
margin: 0;
|
4
4
|
border-collapse: collapse;
|
5
5
|
}
|
6
6
|
|
7
|
-
#route_table
|
8
|
-
|
7
|
+
#route_table thead tr {
|
8
|
+
border-bottom: 2px solid #ddd;
|
9
|
+
}
|
10
|
+
|
11
|
+
#route_table thead tr.bottom {
|
12
|
+
border-bottom: none;
|
9
13
|
}
|
10
14
|
|
11
|
-
#route_table tr.bottom th {
|
12
|
-
padding
|
15
|
+
#route_table thead tr.bottom th {
|
16
|
+
padding: 10px 0;
|
13
17
|
line-height: 15px;
|
14
18
|
}
|
15
19
|
|
16
|
-
#route_table
|
20
|
+
#route_table tbody tr {
|
21
|
+
border-bottom: 1px solid #ddd;
|
22
|
+
}
|
23
|
+
|
24
|
+
#route_table tbody tr:nth-child(odd) {
|
25
|
+
background: #f2f2f2;
|
26
|
+
}
|
27
|
+
|
28
|
+
#route_table tbody.exact_matches,
|
29
|
+
#route_table tbody.fuzzy_matches {
|
17
30
|
background-color: LightGoldenRodYellow;
|
31
|
+
border-bottom: solid 2px SlateGrey;
|
18
32
|
}
|
19
33
|
|
20
|
-
#route_table .
|
21
|
-
|
34
|
+
#route_table tbody.exact_matches tr,
|
35
|
+
#route_table tbody.fuzzy_matches tr {
|
36
|
+
background: none;
|
37
|
+
border-bottom: none;
|
38
|
+
}
|
39
|
+
|
40
|
+
#route_table td {
|
41
|
+
padding: 4px 30px;
|
22
42
|
}
|
23
43
|
|
24
44
|
#path_search {
|
@@ -45,13 +65,15 @@
|
|
45
65
|
<th><%# HTTP Verb %>
|
46
66
|
</th>
|
47
67
|
<th><%# Path %>
|
48
|
-
<%= search_field(:path, nil, id: '
|
68
|
+
<%= search_field(:path, nil, id: 'search', placeholder: "Path Match") %>
|
49
69
|
</th>
|
50
70
|
<th><%# Controller#action %>
|
51
71
|
</th>
|
52
72
|
</tr>
|
53
73
|
</thead>
|
54
|
-
<tbody class='
|
74
|
+
<tbody class='exact_matches' id='exact_matches'>
|
75
|
+
</tbody>
|
76
|
+
<tbody class='fuzzy_matches' id='fuzzy_matches'>
|
55
77
|
</tbody>
|
56
78
|
<tbody>
|
57
79
|
<%= yield %>
|
@@ -59,6 +81,7 @@
|
|
59
81
|
</table>
|
60
82
|
|
61
83
|
<script type='text/javascript'>
|
84
|
+
// Iterates each element through a function
|
62
85
|
function each(elems, func) {
|
63
86
|
if (!elems instanceof Array) { elems = [elems]; }
|
64
87
|
for (var i = 0, len = elems.length; i < len; i++) {
|
@@ -66,77 +89,110 @@
|
|
66
89
|
}
|
67
90
|
}
|
68
91
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
});
|
92
|
+
// Sets innerHTML for an element
|
93
|
+
function setContent(elem, text) {
|
94
|
+
elem.innerHTML = text;
|
73
95
|
}
|
74
96
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
97
|
+
// Enables path search functionality
|
98
|
+
function setupMatchPaths() {
|
99
|
+
// Check if the user input (sanitized as a path) matches the regexp data attribute
|
100
|
+
function checkExactMatch(section, elem, value) {
|
101
|
+
var string = sanitizePath(value),
|
102
|
+
regexp = elem.getAttribute("data-regexp");
|
80
103
|
|
81
|
-
|
82
|
-
|
83
|
-
var toggleLinks = document.querySelectorAll('#route_table [data-route-helper]');
|
84
|
-
onClick(toggleLinks, function(){
|
85
|
-
var helperTxt = this.getAttribute("data-route-helper"),
|
86
|
-
helperElems = document.querySelectorAll('[data-route-name] span.helper');
|
87
|
-
setValOn(helperElems, helperTxt);
|
88
|
-
});
|
89
|
-
}
|
104
|
+
showMatch(string, regexp, section, elem);
|
105
|
+
}
|
90
106
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
each(elems, function(e){
|
96
|
-
var reg = e.getAttribute("data-regexp");
|
97
|
-
if (path.match(RegExp(reg))) {
|
98
|
-
func(e.parentNode.cloneNode(true));
|
99
|
-
}
|
100
|
-
})
|
101
|
-
}
|
107
|
+
// Check if the route path data attribute contains the user input
|
108
|
+
function checkFuzzyMatch(section, elem, value) {
|
109
|
+
var string = elem.getAttribute("data-route-path"),
|
110
|
+
regexp = value;
|
102
111
|
|
103
|
-
|
104
|
-
|
105
|
-
var path = path.charAt(0) == '/' ? path : "/" + path;
|
106
|
-
return path.replace(/\#.*|\?.*/, '');
|
107
|
-
}
|
112
|
+
showMatch(string, regexp, section, elem);
|
113
|
+
}
|
108
114
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
+
// Display the parent <tr> element in the appropriate section when there's a match
|
116
|
+
function showMatch(string, regexp, section, elem) {
|
117
|
+
if(string.match(RegExp(regexp))) {
|
118
|
+
section.appendChild(elem.parentNode.cloneNode(true));
|
119
|
+
}
|
120
|
+
}
|
121
|
+
|
122
|
+
// Check if there are any matched results in a section
|
123
|
+
function checkNoMatch(section, defaultText, noMatchText) {
|
124
|
+
if (section.innerHTML === defaultText) {
|
125
|
+
setContent(section, defaultText + noMatchText);
|
126
|
+
}
|
127
|
+
}
|
115
128
|
|
129
|
+
// Ensure path always starts with a slash "/" and remove params or fragments
|
130
|
+
function sanitizePath(path) {
|
131
|
+
var path = path.charAt(0) == '/' ? path : "/" + path;
|
132
|
+
return path.replace(/\#.*|\?.*/, '');
|
133
|
+
}
|
116
134
|
|
117
|
-
|
118
|
-
|
119
|
-
|
135
|
+
var regexpElems = document.querySelectorAll('#route_table [data-regexp]'),
|
136
|
+
searchElem = document.querySelector('#search'),
|
137
|
+
exactMatches = document.querySelector('#exact_matches'),
|
138
|
+
fuzzyMatches = document.querySelector('#fuzzy_matches');
|
139
|
+
|
140
|
+
// Remove matches when no search value is present
|
141
|
+
searchElem.onblur = function(e) {
|
142
|
+
if (searchElem.value === "") {
|
143
|
+
setContent(exactMatches, "");
|
144
|
+
setContent(fuzzyMatches, "");
|
145
|
+
}
|
120
146
|
}
|
121
147
|
|
122
148
|
// On key press perform a search for matching paths
|
123
|
-
|
124
|
-
var
|
125
|
-
|
149
|
+
searchElem.onkeyup = function(e){
|
150
|
+
var userInput = searchElem.value,
|
151
|
+
defaultExactMatch = '<tr><th colspan="4">Paths Matching (' + escape(sanitizePath(userInput)) +'):</th></tr>',
|
152
|
+
defaultFuzzyMatch = '<tr><th colspan="4">Paths Containing (' + escape(userInput) +'):</th></tr>',
|
153
|
+
noExactMatch = '<tr><th colspan="4">No Exact Matches Found</th></tr>',
|
154
|
+
noFuzzyMatch = '<tr><th colspan="4">No Fuzzy Matches Found</th></tr>';
|
126
155
|
|
127
156
|
// Clear out results section
|
128
|
-
|
157
|
+
setContent(exactMatches, defaultExactMatch);
|
158
|
+
setContent(fuzzyMatches, defaultFuzzyMatch);
|
159
|
+
|
160
|
+
// Display exact matches and fuzzy matches
|
161
|
+
each(regexpElems, function(elem) {
|
162
|
+
checkExactMatch(exactMatches, elem, userInput);
|
163
|
+
checkFuzzyMatch(fuzzyMatches, elem, userInput);
|
164
|
+
})
|
165
|
+
|
166
|
+
// Display 'No Matches' message when no matches are found
|
167
|
+
checkNoMatch(exactMatches, defaultExactMatch, noExactMatch);
|
168
|
+
checkNoMatch(fuzzyMatches, defaultFuzzyMatch, noFuzzyMatch);
|
169
|
+
}
|
170
|
+
}
|
129
171
|
|
130
|
-
|
131
|
-
|
132
|
-
|
172
|
+
// Enables functionality to toggle between `_path` and `_url` helper suffixes
|
173
|
+
function setupRouteToggleHelperLinks() {
|
174
|
+
|
175
|
+
// Sets content for each element
|
176
|
+
function setValOn(elems, val) {
|
177
|
+
each(elems, function(elem) {
|
178
|
+
setContent(elem, val);
|
133
179
|
});
|
180
|
+
}
|
134
181
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
182
|
+
// Sets onClick event for each element
|
183
|
+
function onClick(elems, func) {
|
184
|
+
each(elems, function(elem) {
|
185
|
+
elem.onclick = func;
|
186
|
+
});
|
139
187
|
}
|
188
|
+
|
189
|
+
var toggleLinks = document.querySelectorAll('#route_table [data-route-helper]');
|
190
|
+
onClick(toggleLinks, function(){
|
191
|
+
var helperTxt = this.getAttribute("data-route-helper"),
|
192
|
+
helperElems = document.querySelectorAll('[data-route-name] span.helper');
|
193
|
+
|
194
|
+
setValOn(helperElems, helperTxt);
|
195
|
+
});
|
140
196
|
}
|
141
197
|
|
142
198
|
setupMatchPaths();
|
@@ -40,6 +40,8 @@ module ActionDispatch
|
|
40
40
|
ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie
|
41
41
|
|
42
42
|
ActionDispatch.test_app = app
|
43
|
+
|
44
|
+
ActionDispatch::Routing::RouteSet.relative_url_root = app.config.relative_url_root
|
43
45
|
end
|
44
46
|
end
|
45
47
|
end
|
@@ -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,202 @@ 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
|
-
|
63
|
+
OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z}
|
58
64
|
|
59
|
-
attr_reader :
|
65
|
+
attr_reader :requirements, :conditions, :defaults
|
66
|
+
attr_reader :to, :default_controller, :default_action, :as, :anchor
|
60
67
|
|
61
|
-
def
|
62
|
-
|
63
|
-
@requirements, @conditions, @defaults = {}, {}, {}
|
68
|
+
def self.build(scope, set, path, as, options)
|
69
|
+
options = scope[:options].merge(options) if scope[:options]
|
64
70
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
71
|
+
options.delete :only
|
72
|
+
options.delete :except
|
73
|
+
options.delete :shallow_path
|
74
|
+
options.delete :shallow_prefix
|
75
|
+
options.delete :shallow
|
76
|
+
|
77
|
+
defaults = (scope[:defaults] || {}).merge options.delete(:defaults) || {}
|
78
|
+
|
79
|
+
new scope, set, path, defaults, as, options
|
80
|
+
end
|
81
|
+
|
82
|
+
def initialize(scope, set, path, defaults, as, options)
|
83
|
+
@requirements, @conditions = {}, {}
|
84
|
+
@defaults = defaults
|
85
|
+
@set = set
|
86
|
+
|
87
|
+
@to = options.delete :to
|
88
|
+
@default_controller = options.delete(:controller) || scope[:controller]
|
89
|
+
@default_action = options.delete(:action) || scope[:action]
|
90
|
+
@as = as
|
91
|
+
@anchor = options.delete :anchor
|
92
|
+
|
93
|
+
formatted = options.delete :format
|
94
|
+
via = Array(options.delete(:via) { [] })
|
95
|
+
options_constraints = options.delete :constraints
|
96
|
+
|
97
|
+
path = normalize_path! path, formatted
|
98
|
+
ast = path_ast path
|
99
|
+
path_params = path_params ast
|
100
|
+
|
101
|
+
options = normalize_options!(options, formatted, path_params, ast, scope[:module])
|
102
|
+
|
103
|
+
|
104
|
+
split_constraints(path_params, scope[:constraints]) if scope[:constraints]
|
105
|
+
constraints = constraints(options, path_params)
|
106
|
+
|
107
|
+
split_constraints path_params, constraints
|
108
|
+
|
109
|
+
@blocks = blocks(options_constraints, scope[:blocks])
|
110
|
+
|
111
|
+
if options_constraints.is_a?(Hash)
|
112
|
+
split_constraints path_params, options_constraints
|
113
|
+
options_constraints.each do |key, default|
|
114
|
+
if URL_OPTIONS.include?(key) && (String === default || Integer === default)
|
115
|
+
@defaults[key] ||= default
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
normalize_format!(formatted)
|
121
|
+
|
122
|
+
@conditions[:path_info] = path
|
123
|
+
@conditions[:parsed_path_info] = ast
|
124
|
+
|
125
|
+
add_request_method(via, @conditions)
|
126
|
+
normalize_defaults!(options)
|
70
127
|
end
|
71
128
|
|
72
129
|
def to_route
|
73
|
-
[ app, conditions, requirements, defaults,
|
130
|
+
[ app(@blocks), conditions, requirements, defaults, as, anchor ]
|
74
131
|
end
|
75
132
|
|
76
133
|
private
|
77
134
|
|
78
|
-
def normalize_path!
|
79
|
-
|
80
|
-
@path = Mapper.normalize_path(@path)
|
135
|
+
def normalize_path!(path, format)
|
136
|
+
path = Mapper.normalize_path(path)
|
81
137
|
|
82
|
-
if
|
83
|
-
|
84
|
-
elsif optional_format?
|
85
|
-
|
138
|
+
if format == true
|
139
|
+
"#{path}.:format"
|
140
|
+
elsif optional_format?(path, format)
|
141
|
+
"#{path}(.:format)"
|
142
|
+
else
|
143
|
+
path
|
86
144
|
end
|
87
145
|
end
|
88
146
|
|
89
|
-
def
|
90
|
-
|
147
|
+
def optional_format?(path, format)
|
148
|
+
format != false && path !~ OPTIONAL_FORMAT_REGEX
|
91
149
|
end
|
92
150
|
|
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
|
-
|
151
|
+
def normalize_options!(options, formatted, path_params, path_ast, modyoule)
|
101
152
|
# Add a constraint for wildcard route to make it non-greedy and match the
|
102
153
|
# optional format part of the route by default
|
103
|
-
if
|
104
|
-
|
154
|
+
if formatted != false
|
155
|
+
path_ast.grep(Journey::Nodes::Star) do |node|
|
156
|
+
options[node.name.to_sym] ||= /.+?/
|
157
|
+
end
|
105
158
|
end
|
106
159
|
|
107
|
-
if
|
108
|
-
raise ArgumentError, ":controller segment is not allowed within a namespace block" if
|
160
|
+
if path_params.include?(:controller)
|
161
|
+
raise ArgumentError, ":controller segment is not allowed within a namespace block" if modyoule
|
109
162
|
|
110
163
|
# Add a default constraint for :controller path segments that matches namespaced
|
111
164
|
# controllers with default routes like :controller/:action/:id(.:format), e.g:
|
112
165
|
# GET /admin/products/show/1
|
113
166
|
# => { controller: 'admin/products', action: 'show', id: '1' }
|
114
|
-
|
167
|
+
options[:controller] ||= /.+?/
|
115
168
|
end
|
116
169
|
|
117
|
-
|
170
|
+
if to.respond_to? :call
|
171
|
+
options
|
172
|
+
else
|
173
|
+
to_endpoint = split_to to
|
174
|
+
controller = to_endpoint[0] || default_controller
|
175
|
+
action = to_endpoint[1] || default_action
|
176
|
+
|
177
|
+
controller = add_controller_module(controller, modyoule)
|
178
|
+
|
179
|
+
options.merge! check_controller_and_action(path_params, controller, action)
|
180
|
+
end
|
118
181
|
end
|
119
182
|
|
120
|
-
def
|
121
|
-
constraints.
|
122
|
-
|
123
|
-
|
124
|
-
|
183
|
+
def split_constraints(path_params, constraints)
|
184
|
+
constraints.each_pair do |key, requirement|
|
185
|
+
if path_params.include?(key) || key == :controller
|
186
|
+
verify_regexp_requirement(requirement) if requirement.is_a?(Regexp)
|
187
|
+
@requirements[key] = requirement
|
188
|
+
else
|
189
|
+
@conditions[key] = requirement
|
190
|
+
end
|
125
191
|
end
|
192
|
+
end
|
126
193
|
|
127
|
-
|
194
|
+
def normalize_format!(formatted)
|
195
|
+
if formatted == true
|
128
196
|
@requirements[:format] ||= /.+/
|
129
|
-
elsif Regexp ===
|
130
|
-
@requirements[:format] =
|
131
|
-
|
132
|
-
|
197
|
+
elsif Regexp === formatted
|
198
|
+
@requirements[:format] = formatted
|
199
|
+
@defaults[:format] = nil
|
200
|
+
elsif String === formatted
|
201
|
+
@requirements[:format] = Regexp.compile(formatted)
|
202
|
+
@defaults[:format] = formatted
|
133
203
|
end
|
134
204
|
end
|
135
205
|
|
@@ -143,169 +213,147 @@ module ActionDispatch
|
|
143
213
|
end
|
144
214
|
end
|
145
215
|
|
146
|
-
def normalize_defaults!
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
options.each do |key, default|
|
151
|
-
unless Regexp === default || IGNORE_OPTIONS.include?(key)
|
216
|
+
def normalize_defaults!(options)
|
217
|
+
options.each_pair do |key, default|
|
218
|
+
unless Regexp === default
|
152
219
|
@defaults[key] = default
|
153
220
|
end
|
154
221
|
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
222
|
end
|
170
223
|
|
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
|
224
|
+
def verify_callable_constraint(callable_constraint)
|
225
|
+
unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?)
|
226
|
+
raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?"
|
185
227
|
end
|
186
|
-
|
228
|
+
end
|
187
229
|
|
188
|
-
|
230
|
+
def add_request_method(via, conditions)
|
231
|
+
return if via == [:all]
|
189
232
|
|
190
|
-
if
|
233
|
+
if via.empty?
|
191
234
|
msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
|
192
235
|
"If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
|
193
236
|
"If you want to expose your action to GET, use `get` in the router:\n" \
|
194
237
|
" Instead of: match \"controller#action\"\n" \
|
195
238
|
" Do: get \"controller#action\""
|
196
|
-
raise msg
|
239
|
+
raise ArgumentError, msg
|
197
240
|
end
|
198
241
|
|
199
|
-
|
200
|
-
@conditions[:request_method] = Array(via).map { |m| m.to_s.dasherize.upcase }
|
201
|
-
end
|
242
|
+
conditions[:request_method] = via.map { |m| m.to_s.dasherize.upcase }
|
202
243
|
end
|
203
244
|
|
204
|
-
def app
|
205
|
-
Constraints.new(endpoint, blocks, @set.request_class)
|
206
|
-
end
|
207
|
-
|
208
|
-
def default_controller_and_action
|
245
|
+
def app(blocks)
|
209
246
|
if to.respond_to?(:call)
|
210
|
-
|
247
|
+
Constraints.new(to, blocks, false)
|
248
|
+
elsif blocks.any?
|
249
|
+
Constraints.new(dispatcher(defaults), blocks, true)
|
211
250
|
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
|
251
|
+
dispatcher(defaults)
|
252
|
+
end
|
253
|
+
end
|
232
254
|
|
233
|
-
|
234
|
-
|
255
|
+
def check_controller_and_action(path_params, controller, action)
|
256
|
+
hash = check_part(:controller, controller, path_params, {}) do |part|
|
257
|
+
translate_controller(part) {
|
258
|
+
message = "'#{part}' is not a supported controller name. This can lead to potential routing problems."
|
259
|
+
message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
|
235
260
|
|
236
|
-
if controller.blank? && segment_keys.exclude?(:controller)
|
237
|
-
message = "Missing :controller key on routes definition, please check your routes."
|
238
261
|
raise ArgumentError, message
|
239
|
-
|
262
|
+
}
|
263
|
+
end
|
240
264
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
265
|
+
check_part(:action, action, path_params, hash) { |part|
|
266
|
+
part.is_a?(Regexp) ? part : part.to_s
|
267
|
+
}
|
268
|
+
end
|
245
269
|
|
246
|
-
|
247
|
-
|
248
|
-
|
270
|
+
def check_part(name, part, path_params, hash)
|
271
|
+
if part
|
272
|
+
hash[name] = yield(part)
|
273
|
+
else
|
274
|
+
unless path_params.include?(name)
|
275
|
+
message = "Missing :#{name} key on routes definition, please check your routes."
|
249
276
|
raise ArgumentError, message
|
250
277
|
end
|
251
|
-
|
252
|
-
hash = {}
|
253
|
-
hash[:controller] = controller unless controller.blank?
|
254
|
-
hash[:action] = action unless action.blank?
|
255
|
-
hash
|
256
278
|
end
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
279
|
+
hash
|
280
|
+
end
|
281
|
+
|
282
|
+
def split_to(to)
|
283
|
+
case to
|
284
|
+
when Symbol
|
285
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
286
|
+
Defining a route where `to` is a symbol is deprecated.
|
287
|
+
Please change `to: :#{to}` to `action: :#{to}`.
|
288
|
+
MSG
|
289
|
+
|
290
|
+
[nil, to.to_s]
|
291
|
+
when /#/ then to.split('#')
|
292
|
+
when String
|
293
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
294
|
+
Defining a route where `to` is a controller without an action is deprecated.
|
295
|
+
Please change `to: '#{to}'` to `controller: '#{to}'`.
|
296
|
+
MSG
|
297
|
+
|
298
|
+
[to, nil]
|
262
299
|
else
|
263
|
-
|
300
|
+
[]
|
264
301
|
end
|
265
302
|
end
|
266
303
|
|
267
|
-
def
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
304
|
+
def add_controller_module(controller, modyoule)
|
305
|
+
if modyoule && !controller.is_a?(Regexp)
|
306
|
+
if controller =~ %r{\A/}
|
307
|
+
controller[1..-1]
|
308
|
+
else
|
309
|
+
[modyoule, controller].compact.join("/")
|
273
310
|
end
|
274
|
-
|
275
|
-
|
311
|
+
else
|
312
|
+
controller
|
276
313
|
end
|
277
314
|
end
|
278
315
|
|
279
|
-
def
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
def path_pattern
|
284
|
-
Journey::Path::Pattern.new(strexp)
|
285
|
-
end
|
316
|
+
def translate_controller(controller)
|
317
|
+
return controller if Regexp === controller
|
318
|
+
return controller.to_s if controller =~ /\A[a-z_0-9][a-z_0-9\/]*\z/
|
286
319
|
|
287
|
-
|
288
|
-
Journey::Router::Strexp.compile(path, requirements, SEPARATORS)
|
320
|
+
yield
|
289
321
|
end
|
290
322
|
|
291
|
-
def
|
292
|
-
|
323
|
+
def blocks(options_constraints, scope_blocks)
|
324
|
+
if options_constraints && !options_constraints.is_a?(Hash)
|
325
|
+
verify_callable_constraint(options_constraints)
|
326
|
+
[options_constraints]
|
327
|
+
else
|
328
|
+
scope_blocks || []
|
329
|
+
end
|
293
330
|
end
|
294
331
|
|
295
|
-
def
|
296
|
-
|
332
|
+
def constraints(options, path_params)
|
333
|
+
constraints = {}
|
334
|
+
required_defaults = []
|
335
|
+
options.each_pair do |key, option|
|
336
|
+
if Regexp === option
|
337
|
+
constraints[key] = option
|
338
|
+
else
|
339
|
+
required_defaults << key unless path_params.include?(key)
|
340
|
+
end
|
341
|
+
end
|
342
|
+
@conditions[:required_defaults] = required_defaults
|
343
|
+
constraints
|
297
344
|
end
|
298
345
|
|
299
|
-
def
|
300
|
-
|
346
|
+
def path_params(ast)
|
347
|
+
ast.grep(Journey::Nodes::Symbol).map { |n| n.name.to_sym }
|
301
348
|
end
|
302
349
|
|
303
|
-
def
|
304
|
-
|
350
|
+
def path_ast(path)
|
351
|
+
parser = Journey::Parser.new
|
352
|
+
parser.parse path
|
305
353
|
end
|
306
354
|
|
307
|
-
def
|
308
|
-
|
355
|
+
def dispatcher(defaults)
|
356
|
+
@set.dispatcher defaults
|
309
357
|
end
|
310
358
|
end
|
311
359
|
|
@@ -342,19 +390,18 @@ module ActionDispatch
|
|
342
390
|
|
343
391
|
# Matches a url pattern to one or more routes.
|
344
392
|
#
|
345
|
-
# You should not use the
|
393
|
+
# You should not use the +match+ method in your router
|
346
394
|
# without specifying an HTTP method.
|
347
395
|
#
|
348
396
|
# If you want to expose your action to both GET and POST, use:
|
349
|
-
#
|
397
|
+
#
|
350
398
|
# # sets :controller, :action and :id in params
|
351
399
|
# match ':controller/:action/:id', via: [:get, :post]
|
352
400
|
#
|
353
|
-
# Note that +:controller+, +:action
|
354
|
-
# parameters and thus available
|
355
|
-
# in an action.
|
401
|
+
# Note that +:controller+, +:action+ and +:id+ are interpreted as url
|
402
|
+
# query parameters and thus available through +params+ in an action.
|
356
403
|
#
|
357
|
-
# If you want to expose your action to GET, use
|
404
|
+
# If you want to expose your action to GET, use +get+ in the router:
|
358
405
|
#
|
359
406
|
# Instead of:
|
360
407
|
#
|
@@ -374,24 +421,28 @@ module ActionDispatch
|
|
374
421
|
# # params[:category] = 'rock/classic'
|
375
422
|
# # params[:title] = 'stairway-to-heaven'
|
376
423
|
#
|
424
|
+
# To match a wildcard parameter, it must have a name assigned to it.
|
425
|
+
# Without a variable name to attach the glob parameter to, the route
|
426
|
+
# can't be parsed.
|
427
|
+
#
|
377
428
|
# When a pattern points to an internal route, the route's +:action+ and
|
378
429
|
# +:controller+ should be set in options or hash shorthand. Examples:
|
379
430
|
#
|
380
|
-
# match 'photos/:id' => 'photos#show', via:
|
381
|
-
# match 'photos/:id', to: 'photos#show', via:
|
382
|
-
# match 'photos/:id', controller: 'photos', action: 'show', via:
|
431
|
+
# match 'photos/:id' => 'photos#show', via: :get
|
432
|
+
# match 'photos/:id', to: 'photos#show', via: :get
|
433
|
+
# match 'photos/:id', controller: 'photos', action: 'show', via: :get
|
383
434
|
#
|
384
435
|
# A pattern can also point to a +Rack+ endpoint i.e. anything that
|
385
436
|
# responds to +call+:
|
386
437
|
#
|
387
|
-
# match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] }, via:
|
388
|
-
# match 'photos/:id', to: PhotoRackApp, via:
|
438
|
+
# match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] }, via: :get
|
439
|
+
# match 'photos/:id', to: PhotoRackApp, via: :get
|
389
440
|
# # Yes, controller actions are just rack endpoints
|
390
|
-
# match 'photos/:id', to: PhotosController.action(:show), via:
|
441
|
+
# match 'photos/:id', to: PhotosController.action(:show), via: :get
|
391
442
|
#
|
392
443
|
# Because requesting various HTTP verbs with a single action has security
|
393
444
|
# implications, you must either specify the actions in
|
394
|
-
# the via options or use one of the
|
445
|
+
# the via options or use one of the HttpHelpers[rdoc-ref:HttpHelpers]
|
395
446
|
# instead +match+
|
396
447
|
#
|
397
448
|
# === Options
|
@@ -405,7 +456,7 @@ module ActionDispatch
|
|
405
456
|
# The route's action.
|
406
457
|
#
|
407
458
|
# [:param]
|
408
|
-
# Overrides the default resource identifier
|
459
|
+
# Overrides the default resource identifier +:id+ (name of the
|
409
460
|
# dynamic segment used to generate the routes).
|
410
461
|
# You can access that segment from your controller using
|
411
462
|
# <tt>params[<:param>]</tt>.
|
@@ -416,7 +467,7 @@ module ActionDispatch
|
|
416
467
|
# [:module]
|
417
468
|
# The namespace for :controller.
|
418
469
|
#
|
419
|
-
# match 'path', to: 'c#a', module: 'sekret', controller: 'posts', via:
|
470
|
+
# match 'path', to: 'c#a', module: 'sekret', controller: 'posts', via: :get
|
420
471
|
# # => Sekret::PostsController
|
421
472
|
#
|
422
473
|
# See <tt>Scoping#namespace</tt> for its scope equivalent.
|
@@ -435,9 +486,9 @@ module ActionDispatch
|
|
435
486
|
# Points to a +Rack+ endpoint. Can be an object that responds to
|
436
487
|
# +call+ or a string representing a controller's action.
|
437
488
|
#
|
438
|
-
# match 'path', to: 'controller#action', via:
|
439
|
-
# match 'path', to: lambda { |env| [200, {}, ["Success!"]] }, via:
|
440
|
-
# match 'path', to: RackApp, via:
|
489
|
+
# match 'path', to: 'controller#action', via: :get
|
490
|
+
# match 'path', to: lambda { |env| [200, {}, ["Success!"]] }, via: :get
|
491
|
+
# match 'path', to: RackApp, via: :get
|
441
492
|
#
|
442
493
|
# [:on]
|
443
494
|
# Shorthand for wrapping routes in a specific RESTful context. Valid
|
@@ -462,14 +513,14 @@ module ActionDispatch
|
|
462
513
|
# other than path can also be specified with any object
|
463
514
|
# that responds to <tt>===</tt> (eg. String, Array, Range, etc.).
|
464
515
|
#
|
465
|
-
# match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via:
|
516
|
+
# match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via: :get
|
466
517
|
#
|
467
|
-
# match 'json_only', constraints: { format: 'json' }, via:
|
518
|
+
# match 'json_only', constraints: { format: 'json' }, via: :get
|
468
519
|
#
|
469
520
|
# class Whitelist
|
470
521
|
# def matches?(request) request.remote_ip == '1.2.3.4' end
|
471
522
|
# end
|
472
|
-
# match 'path', to: 'c#a', constraints: Whitelist.new, via:
|
523
|
+
# match 'path', to: 'c#a', constraints: Whitelist.new, via: :get
|
473
524
|
#
|
474
525
|
# See <tt>Scoping#constraints</tt> for more examples with its scope
|
475
526
|
# equivalent.
|
@@ -478,7 +529,7 @@ module ActionDispatch
|
|
478
529
|
# Sets defaults for parameters
|
479
530
|
#
|
480
531
|
# # Sets params[:format] to 'jpg' by default
|
481
|
-
# match 'path', to: 'c#a', defaults: { format: 'jpg' }, via:
|
532
|
+
# match 'path', to: 'c#a', defaults: { format: 'jpg' }, via: :get
|
482
533
|
#
|
483
534
|
# See <tt>Scoping#defaults</tt> for its scope equivalent.
|
484
535
|
#
|
@@ -487,7 +538,7 @@ module ActionDispatch
|
|
487
538
|
# false, the pattern matches any request prefixed with the given path.
|
488
539
|
#
|
489
540
|
# # Matches any request starting with 'path'
|
490
|
-
# match 'path', to: 'c#a', anchor: false, via:
|
541
|
+
# match 'path', to: 'c#a', anchor: false, via: :get
|
491
542
|
#
|
492
543
|
# [:format]
|
493
544
|
# Allows you to specify the default value for optional +format+
|
@@ -529,13 +580,15 @@ module ActionDispatch
|
|
529
580
|
|
530
581
|
raise "A rack application must be specified" unless path
|
531
582
|
|
532
|
-
|
583
|
+
rails_app = rails_app? app
|
584
|
+
options[:as] ||= app_name(app, rails_app)
|
585
|
+
|
533
586
|
target_as = name_for_action(options[:as], path)
|
534
587
|
options[:via] ||= :all
|
535
588
|
|
536
589
|
match(path, options.merge(:to => app, :anchor => false, :format => false))
|
537
590
|
|
538
|
-
define_generate_prefix(app, target_as)
|
591
|
+
define_generate_prefix(app, target_as) if rails_app
|
539
592
|
self
|
540
593
|
end
|
541
594
|
|
@@ -556,35 +609,37 @@ module ActionDispatch
|
|
556
609
|
end
|
557
610
|
|
558
611
|
private
|
559
|
-
def
|
560
|
-
|
612
|
+
def rails_app?(app)
|
613
|
+
app.is_a?(Class) && app < Rails::Railtie
|
614
|
+
end
|
561
615
|
|
562
|
-
|
616
|
+
def app_name(app, rails_app)
|
617
|
+
if rails_app
|
563
618
|
app.railtie_name
|
564
|
-
|
565
|
-
class_name = app.
|
619
|
+
elsif app.is_a?(Class)
|
620
|
+
class_name = app.name
|
566
621
|
ActiveSupport::Inflector.underscore(class_name).tr("/", "_")
|
567
622
|
end
|
568
623
|
end
|
569
624
|
|
570
625
|
def define_generate_prefix(app, name)
|
571
|
-
|
572
|
-
|
573
|
-
_route = @set.named_routes.routes[name.to_sym]
|
626
|
+
_route = @set.named_routes.get name
|
574
627
|
_routes = @set
|
575
628
|
app.routes.define_mounted_helper(name)
|
576
|
-
app.routes.
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
629
|
+
app.routes.extend Module.new {
|
630
|
+
def optimize_routes_generation?; false; end
|
631
|
+
define_method :find_script_name do |options|
|
632
|
+
if options.key? :script_name
|
633
|
+
super(options)
|
634
|
+
else
|
635
|
+
prefix_options = options.slice(*_route.segment_keys)
|
636
|
+
prefix_options[:relative_url_root] = ''.freeze
|
637
|
+
# we must actually delete prefix segment keys to avoid passing them to next url_for
|
638
|
+
_route.segment_keys.each { |k| options.delete(k) }
|
639
|
+
_routes.url_helpers.send("#{name}_path", prefix_options)
|
640
|
+
end
|
586
641
|
end
|
587
|
-
|
642
|
+
}
|
588
643
|
end
|
589
644
|
end
|
590
645
|
|
@@ -671,7 +726,7 @@ module ActionDispatch
|
|
671
726
|
# resources :posts, module: "admin"
|
672
727
|
#
|
673
728
|
# If you want to route /admin/posts to +PostsController+
|
674
|
-
# (without the Admin
|
729
|
+
# (without the <tt>Admin::</tt> module prefix), you could use
|
675
730
|
#
|
676
731
|
# scope "/admin" do
|
677
732
|
# resources :posts
|
@@ -725,7 +780,7 @@ module ActionDispatch
|
|
725
780
|
# end
|
726
781
|
def scope(*args)
|
727
782
|
options = args.extract_options!.dup
|
728
|
-
|
783
|
+
scope = {}
|
729
784
|
|
730
785
|
options[:path] = args.flatten.join('/') if args.any?
|
731
786
|
options[:constraints] ||= {}
|
@@ -736,8 +791,8 @@ module ActionDispatch
|
|
736
791
|
end
|
737
792
|
|
738
793
|
if options[:constraints].is_a?(Hash)
|
739
|
-
defaults = options[:constraints].select do
|
740
|
-
|
794
|
+
defaults = options[:constraints].select do |k, v|
|
795
|
+
URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Integer))
|
741
796
|
end
|
742
797
|
|
743
798
|
(options[:defaults] ||= {}).reverse_merge!(defaults)
|
@@ -745,7 +800,7 @@ module ActionDispatch
|
|
745
800
|
block, options[:constraints] = options[:constraints], {}
|
746
801
|
end
|
747
802
|
|
748
|
-
|
803
|
+
@scope.options.each do |option|
|
749
804
|
if option == :blocks
|
750
805
|
value = block
|
751
806
|
elsif option == :options
|
@@ -755,15 +810,15 @@ module ActionDispatch
|
|
755
810
|
end
|
756
811
|
|
757
812
|
if value
|
758
|
-
|
759
|
-
@scope[option] = send("merge_#{option}_scope", @scope[option], value)
|
813
|
+
scope[option] = send("merge_#{option}_scope", @scope[option], value)
|
760
814
|
end
|
761
815
|
end
|
762
816
|
|
817
|
+
@scope = @scope.new scope
|
763
818
|
yield
|
764
819
|
self
|
765
820
|
ensure
|
766
|
-
@scope.
|
821
|
+
@scope = @scope.parent
|
767
822
|
end
|
768
823
|
|
769
824
|
# Scopes routes to a specific controller
|
@@ -1001,8 +1056,6 @@ module ActionDispatch
|
|
1001
1056
|
VALID_ON_OPTIONS = [:new, :collection, :member]
|
1002
1057
|
RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns]
|
1003
1058
|
CANONICAL_ACTIONS = %w(index create new show update destroy)
|
1004
|
-
RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
|
1005
|
-
RESOURCE_SCOPES = [:resource, :resources]
|
1006
1059
|
|
1007
1060
|
class Resource #:nodoc:
|
1008
1061
|
attr_reader :controller, :path, :options, :param
|
@@ -1422,7 +1475,20 @@ module ActionDispatch
|
|
1422
1475
|
if rest.empty? && Hash === path
|
1423
1476
|
options = path
|
1424
1477
|
path, to = options.find { |name, _value| name.is_a?(String) }
|
1425
|
-
|
1478
|
+
|
1479
|
+
case to
|
1480
|
+
when Symbol
|
1481
|
+
options[:action] = to
|
1482
|
+
when String
|
1483
|
+
if to =~ /#/
|
1484
|
+
options[:to] = to
|
1485
|
+
else
|
1486
|
+
options[:controller] = to
|
1487
|
+
end
|
1488
|
+
else
|
1489
|
+
options[:to] = to
|
1490
|
+
end
|
1491
|
+
|
1426
1492
|
options.delete(path)
|
1427
1493
|
paths = [path]
|
1428
1494
|
else
|
@@ -1456,14 +1522,14 @@ module ActionDispatch
|
|
1456
1522
|
end
|
1457
1523
|
|
1458
1524
|
def using_match_shorthand?(path, options)
|
1459
|
-
path && (options[:to] || options[:action]).nil? && path =~ %r{
|
1525
|
+
path && (options[:to] || options[:action]).nil? && path =~ %r{^/?[-\w]+/[-\w/]+$}
|
1460
1526
|
end
|
1461
1527
|
|
1462
1528
|
def decomposed_match(path, options) # :nodoc:
|
1463
1529
|
if on = options.delete(:on)
|
1464
1530
|
send(on) { decomposed_match(path, options) }
|
1465
1531
|
else
|
1466
|
-
case @scope
|
1532
|
+
case @scope.scope_level
|
1467
1533
|
when :resources
|
1468
1534
|
nested { decomposed_match(path, options) }
|
1469
1535
|
when :resource
|
@@ -1476,6 +1542,8 @@ module ActionDispatch
|
|
1476
1542
|
|
1477
1543
|
def add_route(action, options) # :nodoc:
|
1478
1544
|
path = path_for_action(action, options.delete(:path))
|
1545
|
+
raise ArgumentError, "path is required" if path.blank?
|
1546
|
+
|
1479
1547
|
action = action.to_s.dup
|
1480
1548
|
|
1481
1549
|
if action =~ /^[\w\-\/]+$/
|
@@ -1484,13 +1552,13 @@ module ActionDispatch
|
|
1484
1552
|
action = nil
|
1485
1553
|
end
|
1486
1554
|
|
1487
|
-
if !options.fetch(:as, true)
|
1488
|
-
|
1489
|
-
|
1490
|
-
|
1491
|
-
|
1555
|
+
as = if !options.fetch(:as, true) # if it's set to nil or false
|
1556
|
+
options.delete(:as)
|
1557
|
+
else
|
1558
|
+
name_for_action(options.delete(:as), action)
|
1559
|
+
end
|
1492
1560
|
|
1493
|
-
mapping = Mapping.
|
1561
|
+
mapping = Mapping.build(@scope, @set, URI.parser.escape(path), as, options)
|
1494
1562
|
app, conditions, requirements, defaults, as, anchor = mapping.to_route
|
1495
1563
|
@set.add_route(app, conditions, requirements, defaults, as, anchor)
|
1496
1564
|
end
|
@@ -1504,7 +1572,7 @@ module ActionDispatch
|
|
1504
1572
|
raise ArgumentError, "must be called with a path and/or options"
|
1505
1573
|
end
|
1506
1574
|
|
1507
|
-
if @scope
|
1575
|
+
if @scope.resources?
|
1508
1576
|
with_scope_level(:root) do
|
1509
1577
|
scope(parent_resource.path) do
|
1510
1578
|
super(options)
|
@@ -1571,40 +1639,39 @@ module ActionDispatch
|
|
1571
1639
|
end
|
1572
1640
|
|
1573
1641
|
def resource_scope? #:nodoc:
|
1574
|
-
|
1642
|
+
@scope.resource_scope?
|
1575
1643
|
end
|
1576
1644
|
|
1577
1645
|
def resource_method_scope? #:nodoc:
|
1578
|
-
|
1646
|
+
@scope.resource_method_scope?
|
1579
1647
|
end
|
1580
1648
|
|
1581
1649
|
def nested_scope? #:nodoc:
|
1582
|
-
@scope
|
1650
|
+
@scope.nested?
|
1583
1651
|
end
|
1584
1652
|
|
1585
1653
|
def with_exclusive_scope
|
1586
1654
|
begin
|
1587
|
-
|
1588
|
-
@scope[:as], @scope[:path] = nil, nil
|
1655
|
+
@scope = @scope.new(:as => nil, :path => nil)
|
1589
1656
|
|
1590
1657
|
with_scope_level(:exclusive) do
|
1591
1658
|
yield
|
1592
1659
|
end
|
1593
1660
|
ensure
|
1594
|
-
@scope
|
1661
|
+
@scope = @scope.parent
|
1595
1662
|
end
|
1596
1663
|
end
|
1597
1664
|
|
1598
1665
|
def with_scope_level(kind)
|
1599
|
-
|
1666
|
+
@scope = @scope.new_level(kind)
|
1600
1667
|
yield
|
1601
1668
|
ensure
|
1602
|
-
@scope
|
1669
|
+
@scope = @scope.parent
|
1603
1670
|
end
|
1604
1671
|
|
1605
1672
|
def resource_scope(kind, resource) #:nodoc:
|
1606
1673
|
resource.shallow = @scope[:shallow]
|
1607
|
-
|
1674
|
+
@scope = @scope.new(:scope_level_resource => resource)
|
1608
1675
|
@nesting.push(resource)
|
1609
1676
|
|
1610
1677
|
with_scope_level(kind) do
|
@@ -1612,7 +1679,7 @@ module ActionDispatch
|
|
1612
1679
|
end
|
1613
1680
|
ensure
|
1614
1681
|
@nesting.pop
|
1615
|
-
@scope
|
1682
|
+
@scope = @scope.parent
|
1616
1683
|
end
|
1617
1684
|
|
1618
1685
|
def nested_options #:nodoc:
|
@@ -1640,21 +1707,22 @@ module ActionDispatch
|
|
1640
1707
|
@scope[:constraints][parent_resource.param]
|
1641
1708
|
end
|
1642
1709
|
|
1643
|
-
def canonical_action?(action
|
1644
|
-
|
1710
|
+
def canonical_action?(action) #:nodoc:
|
1711
|
+
resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
|
1645
1712
|
end
|
1646
1713
|
|
1647
1714
|
def shallow_scope(path, options = {}) #:nodoc:
|
1648
|
-
|
1649
|
-
|
1715
|
+
scope = { :as => @scope[:shallow_prefix],
|
1716
|
+
:path => @scope[:shallow_path] }
|
1717
|
+
@scope = @scope.new scope
|
1650
1718
|
|
1651
1719
|
scope(path, options) { yield }
|
1652
1720
|
ensure
|
1653
|
-
@scope
|
1721
|
+
@scope = @scope.parent
|
1654
1722
|
end
|
1655
1723
|
|
1656
1724
|
def path_for_action(action, path) #:nodoc:
|
1657
|
-
if canonical_action?(action
|
1725
|
+
if path.blank? && canonical_action?(action)
|
1658
1726
|
@scope[:path].to_s
|
1659
1727
|
else
|
1660
1728
|
"#{@scope[:path]}/#{action_path(action, path)}"
|
@@ -1669,15 +1737,17 @@ module ActionDispatch
|
|
1669
1737
|
def prefix_name_for_action(as, action) #:nodoc:
|
1670
1738
|
if as
|
1671
1739
|
prefix = as
|
1672
|
-
elsif !canonical_action?(action
|
1740
|
+
elsif !canonical_action?(action)
|
1673
1741
|
prefix = action
|
1674
1742
|
end
|
1675
|
-
|
1743
|
+
|
1744
|
+
if prefix && prefix != '/' && !prefix.empty?
|
1745
|
+
Mapper.normalize_name prefix.to_s.tr('-', '_')
|
1746
|
+
end
|
1676
1747
|
end
|
1677
1748
|
|
1678
1749
|
def name_for_action(as, action) #:nodoc:
|
1679
1750
|
prefix = prefix_name_for_action(as, action)
|
1680
|
-
prefix = Mapper.normalize_name(prefix) if prefix
|
1681
1751
|
name_prefix = @scope[:as]
|
1682
1752
|
|
1683
1753
|
if parent_resource
|
@@ -1687,27 +1757,14 @@ module ActionDispatch
|
|
1687
1757
|
member_name = parent_resource.member_name
|
1688
1758
|
end
|
1689
1759
|
|
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
|
1760
|
+
name = @scope.action_name(name_prefix, prefix, collection_name, member_name)
|
1704
1761
|
|
1705
|
-
if candidate = name.
|
1762
|
+
if candidate = name.compact.join("_").presence
|
1706
1763
|
# If a name was not explicitly given, we check if it is valid
|
1707
1764
|
# and return nil in case it isn't. Otherwise, we pass the invalid name
|
1708
1765
|
# forward so the underlying router engine treats it and raises an exception.
|
1709
1766
|
if as.nil?
|
1710
|
-
candidate unless
|
1767
|
+
candidate unless candidate !~ /\A[_a-z]/i || @set.named_routes.key?(candidate)
|
1711
1768
|
else
|
1712
1769
|
candidate
|
1713
1770
|
end
|
@@ -1832,9 +1889,83 @@ module ActionDispatch
|
|
1832
1889
|
end
|
1833
1890
|
end
|
1834
1891
|
|
1892
|
+
class Scope # :nodoc:
|
1893
|
+
OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
|
1894
|
+
:controller, :action, :path_names, :constraints,
|
1895
|
+
:shallow, :blocks, :defaults, :options]
|
1896
|
+
|
1897
|
+
RESOURCE_SCOPES = [:resource, :resources]
|
1898
|
+
RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
|
1899
|
+
|
1900
|
+
attr_reader :parent, :scope_level
|
1901
|
+
|
1902
|
+
def initialize(hash, parent = {}, scope_level = nil)
|
1903
|
+
@hash = hash
|
1904
|
+
@parent = parent
|
1905
|
+
@scope_level = scope_level
|
1906
|
+
end
|
1907
|
+
|
1908
|
+
def nested?
|
1909
|
+
scope_level == :nested
|
1910
|
+
end
|
1911
|
+
|
1912
|
+
def resources?
|
1913
|
+
scope_level == :resources
|
1914
|
+
end
|
1915
|
+
|
1916
|
+
def resource_method_scope?
|
1917
|
+
RESOURCE_METHOD_SCOPES.include? scope_level
|
1918
|
+
end
|
1919
|
+
|
1920
|
+
def action_name(name_prefix, prefix, collection_name, member_name)
|
1921
|
+
case scope_level
|
1922
|
+
when :nested
|
1923
|
+
[name_prefix, prefix]
|
1924
|
+
when :collection
|
1925
|
+
[prefix, name_prefix, collection_name]
|
1926
|
+
when :new
|
1927
|
+
[prefix, :new, name_prefix, member_name]
|
1928
|
+
when :member
|
1929
|
+
[prefix, name_prefix, member_name]
|
1930
|
+
when :root
|
1931
|
+
[name_prefix, collection_name, prefix]
|
1932
|
+
else
|
1933
|
+
[name_prefix, member_name, prefix]
|
1934
|
+
end
|
1935
|
+
end
|
1936
|
+
|
1937
|
+
def resource_scope?
|
1938
|
+
RESOURCE_SCOPES.include? scope_level
|
1939
|
+
end
|
1940
|
+
|
1941
|
+
def options
|
1942
|
+
OPTIONS
|
1943
|
+
end
|
1944
|
+
|
1945
|
+
def new(hash)
|
1946
|
+
self.class.new hash, self, scope_level
|
1947
|
+
end
|
1948
|
+
|
1949
|
+
def new_level(level)
|
1950
|
+
self.class.new(self, self, level)
|
1951
|
+
end
|
1952
|
+
|
1953
|
+
def fetch(key, &block)
|
1954
|
+
@hash.fetch(key, &block)
|
1955
|
+
end
|
1956
|
+
|
1957
|
+
def [](key)
|
1958
|
+
@hash.fetch(key) { @parent[key] }
|
1959
|
+
end
|
1960
|
+
|
1961
|
+
def []=(k,v)
|
1962
|
+
@hash[k] = v
|
1963
|
+
end
|
1964
|
+
end
|
1965
|
+
|
1835
1966
|
def initialize(set) #:nodoc:
|
1836
1967
|
@set = set
|
1837
|
-
@scope = { :path_names => @set.resources_path_names }
|
1968
|
+
@scope = Scope.new({ :path_names => @set.resources_path_names })
|
1838
1969
|
@concerns = {}
|
1839
1970
|
@nesting = []
|
1840
1971
|
end
|