actionpack 8.0.3 → 8.1.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +335 -158
- data/lib/abstract_controller/asset_paths.rb +4 -2
- data/lib/abstract_controller/base.rb +12 -3
- data/lib/abstract_controller/caching.rb +6 -3
- data/lib/abstract_controller/logger.rb +2 -1
- data/lib/action_controller/api.rb +1 -0
- data/lib/action_controller/base.rb +2 -1
- data/lib/action_controller/caching.rb +1 -2
- data/lib/action_controller/form_builder.rb +1 -1
- data/lib/action_controller/log_subscriber.rb +18 -3
- data/lib/action_controller/metal/allow_browser.rb +1 -1
- data/lib/action_controller/metal/conditional_get.rb +25 -0
- data/lib/action_controller/metal/data_streaming.rb +1 -3
- data/lib/action_controller/metal/exceptions.rb +5 -0
- data/lib/action_controller/metal/flash.rb +1 -4
- data/lib/action_controller/metal/head.rb +3 -1
- data/lib/action_controller/metal/live.rb +9 -18
- data/lib/action_controller/metal/permissions_policy.rb +9 -0
- data/lib/action_controller/metal/rate_limiting.rb +30 -9
- data/lib/action_controller/metal/redirecting.rb +104 -9
- data/lib/action_controller/metal/renderers.rb +27 -6
- data/lib/action_controller/metal/rendering.rb +7 -1
- data/lib/action_controller/metal/request_forgery_protection.rb +18 -10
- data/lib/action_controller/metal/rescue.rb +9 -0
- data/lib/action_controller/railtie.rb +27 -8
- data/lib/action_controller/structured_event_subscriber.rb +107 -0
- data/lib/action_dispatch/http/cache.rb +111 -1
- data/lib/action_dispatch/http/filter_parameters.rb +5 -3
- data/lib/action_dispatch/http/mime_negotiation.rb +55 -1
- data/lib/action_dispatch/http/mime_types.rb +1 -0
- data/lib/action_dispatch/http/param_builder.rb +28 -27
- data/lib/action_dispatch/http/parameters.rb +3 -3
- data/lib/action_dispatch/http/permissions_policy.rb +4 -0
- data/lib/action_dispatch/http/query_parser.rb +12 -10
- data/lib/action_dispatch/http/request.rb +10 -5
- data/lib/action_dispatch/http/response.rb +16 -3
- data/lib/action_dispatch/http/url.rb +110 -14
- data/lib/action_dispatch/journey/gtg/simulator.rb +33 -12
- data/lib/action_dispatch/journey/gtg/transition_table.rb +33 -41
- data/lib/action_dispatch/journey/nodes/node.rb +2 -1
- data/lib/action_dispatch/journey/route.rb +45 -31
- data/lib/action_dispatch/journey/router/utils.rb +8 -14
- data/lib/action_dispatch/journey/router.rb +59 -81
- data/lib/action_dispatch/journey/routes.rb +7 -0
- data/lib/action_dispatch/journey/visitors.rb +55 -23
- data/lib/action_dispatch/journey/visualizer/fsm.js +4 -6
- data/lib/action_dispatch/log_subscriber.rb +7 -3
- data/lib/action_dispatch/middleware/cookies.rb +4 -2
- data/lib/action_dispatch/middleware/debug_exceptions.rb +7 -1
- data/lib/action_dispatch/middleware/debug_view.rb +11 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +11 -5
- data/lib/action_dispatch/middleware/executor.rb +12 -2
- data/lib/action_dispatch/middleware/public_exceptions.rb +1 -5
- data/lib/action_dispatch/middleware/remote_ip.rb +9 -4
- data/lib/action_dispatch/middleware/session/cache_store.rb +17 -0
- data/lib/action_dispatch/middleware/templates/rescues/_copy_button.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +3 -2
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +9 -5
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +4 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +50 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -0
- data/lib/action_dispatch/railtie.rb +14 -2
- data/lib/action_dispatch/routing/inspector.rb +79 -56
- data/lib/action_dispatch/routing/mapper.rb +323 -173
- data/lib/action_dispatch/routing/redirection.rb +10 -7
- data/lib/action_dispatch/routing/route_set.rb +2 -4
- data/lib/action_dispatch/structured_event_subscriber.rb +20 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +2 -2
- data/lib/action_dispatch/testing/assertions/response.rb +14 -0
- data/lib/action_dispatch/testing/assertions/routing.rb +11 -3
- data/lib/action_dispatch/testing/integration.rb +1 -1
- data/lib/action_dispatch.rb +8 -0
- data/lib/action_pack/gem_version.rb +3 -3
- metadata +13 -10
|
@@ -44,6 +44,8 @@ module ActionDispatch
|
|
|
44
44
|
"10.0.0.0/8", # private IPv4 range 10.x.x.x
|
|
45
45
|
"172.16.0.0/12", # private IPv4 range 172.16.0.0 .. 172.31.255.255
|
|
46
46
|
"192.168.0.0/16", # private IPv4 range 192.168.x.x
|
|
47
|
+
"169.254.0.0/16", # link-local IPv4 range 169.254.x.x
|
|
48
|
+
"fe80::/10", # link-local IPv6 range fe80::/10
|
|
47
49
|
].map { |proxy| IPAddr.new(proxy) }
|
|
48
50
|
|
|
49
51
|
attr_reader :check_ip, :proxies
|
|
@@ -126,11 +128,11 @@ module ActionDispatch
|
|
|
126
128
|
# left, which was presumably set by one of those proxies.
|
|
127
129
|
def calculate_ip
|
|
128
130
|
# Set by the Rack web server, this is a single value.
|
|
129
|
-
remote_addr = ips_from(@req.remote_addr).last
|
|
131
|
+
remote_addr = sanitize_ips(ips_from(@req.remote_addr)).last
|
|
130
132
|
|
|
131
133
|
# Could be a CSV list and/or repeated headers that were concatenated.
|
|
132
|
-
client_ips = ips_from(@req.client_ip).reverse!
|
|
133
|
-
forwarded_ips =
|
|
134
|
+
client_ips = sanitize_ips(ips_from(@req.client_ip)).reverse!
|
|
135
|
+
forwarded_ips = sanitize_ips(@req.forwarded_for || []).reverse!
|
|
134
136
|
|
|
135
137
|
# `Client-Ip` and `X-Forwarded-For` should not, generally, both be set. If they
|
|
136
138
|
# are both set, it means that either:
|
|
@@ -176,7 +178,10 @@ module ActionDispatch
|
|
|
176
178
|
def ips_from(header) # :doc:
|
|
177
179
|
return [] unless header
|
|
178
180
|
# Split the comma-separated list into an array of strings.
|
|
179
|
-
|
|
181
|
+
header.strip.split(/[,\s]+/)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def sanitize_ips(ips) # :doc:
|
|
180
185
|
ips.select! do |ip|
|
|
181
186
|
# Only return IPs that are valid according to the IPAddr#new method.
|
|
182
187
|
range = IPAddr.new(ip).to_range
|
|
@@ -18,11 +18,16 @@ module ActionDispatch
|
|
|
18
18
|
# * `expire_after` - The length of time a session will be stored before
|
|
19
19
|
# automatically expiring. By default, the `:expires_in` option of the cache
|
|
20
20
|
# is used.
|
|
21
|
+
# * `check_collisions` - Check if newly generated session ids aren't already in use.
|
|
22
|
+
# If for some reason 128 bits of randomness aren't considered secure enough to avoid
|
|
23
|
+
# collisions, this option can be enabled to ensure newly generated ids aren't in use.
|
|
24
|
+
# By default, it is set to `false` to avoid additional cache write operations.
|
|
21
25
|
#
|
|
22
26
|
class CacheStore < AbstractSecureStore
|
|
23
27
|
def initialize(app, options = {})
|
|
24
28
|
@cache = options[:cache] || Rails.cache
|
|
25
29
|
options[:expire_after] ||= @cache.options[:expires_in]
|
|
30
|
+
@check_collisions = options[:check_collisions] || false
|
|
26
31
|
super
|
|
27
32
|
end
|
|
28
33
|
|
|
@@ -61,6 +66,18 @@ module ActionDispatch
|
|
|
61
66
|
def get_session_with_fallback(sid)
|
|
62
67
|
@cache.read(cache_key(sid.private_id)) || @cache.read(cache_key(sid.public_id))
|
|
63
68
|
end
|
|
69
|
+
|
|
70
|
+
def generate_sid
|
|
71
|
+
if @check_collisions
|
|
72
|
+
loop do
|
|
73
|
+
sid = super
|
|
74
|
+
key = cache_key(sid.private_id)
|
|
75
|
+
break sid if @cache.write(key, {}, unless_exist: true, expires_in: default_options[:expire_after])
|
|
76
|
+
end
|
|
77
|
+
else
|
|
78
|
+
super
|
|
79
|
+
end
|
|
80
|
+
end
|
|
64
81
|
end
|
|
65
82
|
end
|
|
66
83
|
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<button onclick="copyAsText.bind(this)()">Copy as text</button>
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
<tr>
|
|
12
12
|
<td>
|
|
13
13
|
<pre class="line_numbers">
|
|
14
|
-
<% source_extract[:code].each_key do |
|
|
15
|
-
|
|
14
|
+
<% source_extract[:code].each_key do |line| %>
|
|
15
|
+
<% file_url = editor_url(source_extract[:trace], line: line) %>
|
|
16
|
+
<span><%= link_to_if file_url, line, file_url -%></span>
|
|
16
17
|
<% end %>
|
|
17
18
|
</pre>
|
|
18
19
|
</td>
|
|
@@ -13,13 +13,17 @@
|
|
|
13
13
|
<% end %>
|
|
14
14
|
|
|
15
15
|
<% traces.each do |name, trace| %>
|
|
16
|
-
<div id="<%= "#{name.gsub(/\s/, '-')}-#{error_index}" %>" style="display: <%= (name == trace_to_show) ? 'block' : 'none' %>;">
|
|
16
|
+
<div id="<%= "#{name.gsub(/\s/, '-')}-#{error_index}" %>" class="trace-container" style="display: <%= (name == trace_to_show) ? 'block' : 'none' %>;">
|
|
17
17
|
<code class="traces">
|
|
18
18
|
<% trace.each do |frame| %>
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
<div class="trace">
|
|
20
|
+
<% file_url = editor_url(frame[:trace]) %>
|
|
21
|
+
<%= file_url && link_to("✏️", file_url, class: "edit-icon") %>
|
|
22
|
+
<a class="trace-frames trace-frames-<%= error_index %>" data-exception-object-id="<%= frame[:exception_object_id] %>" data-frame-id="<%= frame[:id] %>" href="#">
|
|
23
|
+
<%= frame[:trace] %>
|
|
24
|
+
</a>
|
|
25
|
+
<br>
|
|
26
|
+
</div>
|
|
23
27
|
<% end %>
|
|
24
28
|
</code>
|
|
25
29
|
</div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<header role="banner">
|
|
2
|
+
<%= render "rescues/copy_button" %>
|
|
2
3
|
<h1>
|
|
3
4
|
<%= @exception.class.to_s %>
|
|
4
5
|
<% if @request.parameters['controller'] %>
|
|
@@ -10,6 +11,9 @@
|
|
|
10
11
|
<main role="main" id="container">
|
|
11
12
|
<h2>
|
|
12
13
|
<%= h @exception.message %>
|
|
14
|
+
<% if defined?(ActionText) && @exception.message.match?(%r{#{ActionText::RichText.table_name}}) %>
|
|
15
|
+
<br />To resolve this issue run: bin/rails action_text:install
|
|
16
|
+
<% end %>
|
|
13
17
|
<% if defined?(ActiveStorage) && @exception.message.match?(%r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}}) %>
|
|
14
18
|
<br />To resolve this issue run: bin/rails active_storage:install
|
|
15
19
|
<% end %>
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
<% end %>
|
|
5
5
|
|
|
6
6
|
<%= @exception.message %>
|
|
7
|
+
<% if defined?(ActionText) && @exception.message.match?(%r{#{ActionText::RichText.table_name}}) %>
|
|
8
|
+
To resolve this issue run: bin/rails action_text:install
|
|
9
|
+
<% end %>
|
|
7
10
|
<% if defined?(ActiveStorage) && @exception.message.match?(%r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}}) %>
|
|
8
11
|
To resolve this issue run: bin/rails active_storage:install
|
|
9
12
|
<% end %>
|
|
@@ -38,6 +38,22 @@
|
|
|
38
38
|
padding: 0.5em 1.5em;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
header button {
|
|
42
|
+
appearance: none;
|
|
43
|
+
background-color: hsl(0 0% 0% / 0.2);
|
|
44
|
+
border: 0;
|
|
45
|
+
border-radius: 14px;
|
|
46
|
+
color: white;
|
|
47
|
+
float: right;
|
|
48
|
+
font-weight: 500;
|
|
49
|
+
height: 28px;
|
|
50
|
+
padding-inline: 14px;
|
|
51
|
+
margin: 0.35em 0;
|
|
52
|
+
}
|
|
53
|
+
header button:active {
|
|
54
|
+
background-color: hsl(0 0% 0% / 0.25);
|
|
55
|
+
}
|
|
56
|
+
|
|
41
57
|
h1 {
|
|
42
58
|
overflow-wrap: break-word;
|
|
43
59
|
margin: 0.2em 0;
|
|
@@ -54,6 +70,30 @@
|
|
|
54
70
|
font-size: 11px;
|
|
55
71
|
}
|
|
56
72
|
|
|
73
|
+
.trace-container {
|
|
74
|
+
margin-top: 10px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
code.traces .trace {
|
|
78
|
+
display: flex;
|
|
79
|
+
align-items: center;
|
|
80
|
+
gap: 2px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.edit-icon {
|
|
84
|
+
width: 16px;
|
|
85
|
+
height: 16px;
|
|
86
|
+
display: flex;
|
|
87
|
+
font-size: 13px;
|
|
88
|
+
align-items: center;
|
|
89
|
+
justify-content: center;
|
|
90
|
+
text-decoration: none;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.edit-icon:hover {
|
|
94
|
+
scale: 1.05;
|
|
95
|
+
}
|
|
96
|
+
|
|
57
97
|
.response-heading, .request-heading {
|
|
58
98
|
margin-top: 30px;
|
|
59
99
|
}
|
|
@@ -274,11 +314,21 @@
|
|
|
274
314
|
var toggleEnvDump = function() {
|
|
275
315
|
return toggle('env_dump');
|
|
276
316
|
}
|
|
317
|
+
var copyAsText = function() {
|
|
318
|
+
const text = document.getElementById("exception-message-for-copy").textContent;
|
|
319
|
+
|
|
320
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
321
|
+
const beforeText = this.innerText;
|
|
322
|
+
this.innerText = "Copied!"
|
|
323
|
+
setTimeout(() => this.innerText = beforeText, 1000)
|
|
324
|
+
})
|
|
325
|
+
}
|
|
277
326
|
</script>
|
|
278
327
|
</head>
|
|
279
328
|
<body>
|
|
280
329
|
|
|
281
330
|
<%= yield %>
|
|
331
|
+
<script type="text/plain" id="exception-message-for-copy"><%= raw @exception_message_for_copy %></script>
|
|
282
332
|
|
|
283
333
|
</body>
|
|
284
334
|
</html>
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
require "action_dispatch"
|
|
6
6
|
require "action_dispatch/log_subscriber"
|
|
7
|
+
require "action_dispatch/structured_event_subscriber"
|
|
7
8
|
require "active_support/messages/rotation_configuration"
|
|
8
9
|
|
|
9
10
|
module ActionDispatch
|
|
@@ -33,6 +34,7 @@ module ActionDispatch
|
|
|
33
34
|
|
|
34
35
|
config.action_dispatch.ignore_leading_brackets = nil
|
|
35
36
|
config.action_dispatch.strict_query_string_separator = nil
|
|
37
|
+
config.action_dispatch.verbose_redirect_logs = false
|
|
36
38
|
|
|
37
39
|
config.action_dispatch.default_headers = {
|
|
38
40
|
"X-Frame-Options" => "SAMEORIGIN",
|
|
@@ -55,8 +57,18 @@ module ActionDispatch
|
|
|
55
57
|
ActionDispatch::Http::URL.secure_protocol = app.config.force_ssl
|
|
56
58
|
ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
unless app.config.action_dispatch.domain_extractor.nil?
|
|
61
|
+
ActionDispatch::Http::URL.domain_extractor = app.config.action_dispatch.domain_extractor
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
unless app.config.action_dispatch.ignore_leading_brackets.nil?
|
|
65
|
+
ActionDispatch::ParamBuilder.ignore_leading_brackets = app.config.action_dispatch.ignore_leading_brackets
|
|
66
|
+
end
|
|
67
|
+
unless app.config.action_dispatch.strict_query_string_separator.nil?
|
|
68
|
+
ActionDispatch::QueryParser.strict_query_string_separator = app.config.action_dispatch.strict_query_string_separator
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
ActionDispatch.verbose_redirect_logs = app.config.action_dispatch.verbose_redirect_logs
|
|
60
72
|
|
|
61
73
|
ActiveSupport.on_load(:action_dispatch_request) do
|
|
62
74
|
self.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
|
|
@@ -64,6 +64,14 @@ module ActionDispatch
|
|
|
64
64
|
def engine?
|
|
65
65
|
app.engine?
|
|
66
66
|
end
|
|
67
|
+
|
|
68
|
+
def to_h
|
|
69
|
+
{ name: name,
|
|
70
|
+
verb: verb,
|
|
71
|
+
path: path,
|
|
72
|
+
reqs: reqs,
|
|
73
|
+
source_location: source_location }
|
|
74
|
+
end
|
|
67
75
|
end
|
|
68
76
|
|
|
69
77
|
##
|
|
@@ -72,30 +80,51 @@ module ActionDispatch
|
|
|
72
80
|
# not use this class.
|
|
73
81
|
class RoutesInspector # :nodoc:
|
|
74
82
|
def initialize(routes)
|
|
75
|
-
@
|
|
76
|
-
@
|
|
83
|
+
@routes = wrap_routes(routes)
|
|
84
|
+
@engines = load_engines_routes
|
|
77
85
|
end
|
|
78
86
|
|
|
79
87
|
def format(formatter, filter = {})
|
|
80
|
-
|
|
81
|
-
routes = collect_routes(routes_to_display)
|
|
82
|
-
if routes.none?
|
|
83
|
-
formatter.no_routes(collect_routes(@routes), filter)
|
|
84
|
-
return formatter.result
|
|
85
|
-
end
|
|
88
|
+
all_routes = { nil => @routes }.merge(@engines)
|
|
86
89
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
@engines.each do |name, engine_routes|
|
|
91
|
-
formatter.section_title "Routes for #{name}"
|
|
92
|
-
formatter.section engine_routes
|
|
90
|
+
all_routes.each do |engine_name, routes|
|
|
91
|
+
format_routes(formatter, filter, engine_name, routes)
|
|
93
92
|
end
|
|
94
93
|
|
|
95
94
|
formatter.result
|
|
96
95
|
end
|
|
97
96
|
|
|
98
97
|
private
|
|
98
|
+
def format_routes(formatter, filter, engine_name, routes)
|
|
99
|
+
routes = filter_routes(routes, normalize_filter(filter)).map(&:to_h)
|
|
100
|
+
|
|
101
|
+
formatter.section_title "Routes for #{engine_name || "application"}" if @engines.any?
|
|
102
|
+
if routes.any?
|
|
103
|
+
formatter.header routes
|
|
104
|
+
formatter.section routes
|
|
105
|
+
else
|
|
106
|
+
formatter.no_routes engine_name, routes, filter
|
|
107
|
+
end
|
|
108
|
+
formatter.footer routes
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def wrap_routes(routes)
|
|
112
|
+
routes.routes.map { |route| RouteWrapper.new(route) }.reject(&:internal?)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def load_engines_routes
|
|
116
|
+
engine_routes = @routes.select(&:engine?)
|
|
117
|
+
|
|
118
|
+
engines = engine_routes.to_h do |engine_route|
|
|
119
|
+
engine_app_routes = engine_route.rack_app.routes
|
|
120
|
+
engine_app_routes = engine_app_routes.routes if engine_app_routes.is_a?(ActionDispatch::Routing::RouteSet)
|
|
121
|
+
|
|
122
|
+
[engine_route.endpoint, wrap_routes(engine_app_routes)]
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
engines
|
|
126
|
+
end
|
|
127
|
+
|
|
99
128
|
def normalize_filter(filter)
|
|
100
129
|
if filter[:controller]
|
|
101
130
|
{ controller: /#{filter[:controller].underscore.sub(/_?controller\z/, "")}/ }
|
|
@@ -115,39 +144,13 @@ module ActionDispatch
|
|
|
115
144
|
end
|
|
116
145
|
end
|
|
117
146
|
|
|
118
|
-
def filter_routes(filter)
|
|
147
|
+
def filter_routes(routes, filter)
|
|
119
148
|
if filter
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
filter.any? { |filter_type, value| route_wrapper.matches_filter?(filter_type, value) }
|
|
149
|
+
routes.select do |route|
|
|
150
|
+
filter.any? { |filter_type, value| route.matches_filter?(filter_type, value) }
|
|
123
151
|
end
|
|
124
152
|
else
|
|
125
|
-
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def collect_routes(routes)
|
|
130
|
-
routes.collect do |route|
|
|
131
|
-
RouteWrapper.new(route)
|
|
132
|
-
end.reject(&:internal?).collect do |route|
|
|
133
|
-
collect_engine_routes(route)
|
|
134
|
-
|
|
135
|
-
{ name: route.name,
|
|
136
|
-
verb: route.verb,
|
|
137
|
-
path: route.path,
|
|
138
|
-
reqs: route.reqs,
|
|
139
|
-
source_location: route.source_location }
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def collect_engine_routes(route)
|
|
144
|
-
name = route.endpoint
|
|
145
|
-
return unless route.engine?
|
|
146
|
-
return if @engines[name]
|
|
147
|
-
|
|
148
|
-
routes = route.rack_app.routes
|
|
149
|
-
if routes.is_a?(ActionDispatch::Routing::RouteSet)
|
|
150
|
-
@engines[name] = collect_routes(routes.routes)
|
|
153
|
+
routes
|
|
151
154
|
end
|
|
152
155
|
end
|
|
153
156
|
end
|
|
@@ -171,27 +174,36 @@ module ActionDispatch
|
|
|
171
174
|
def header(routes)
|
|
172
175
|
end
|
|
173
176
|
|
|
174
|
-
def
|
|
175
|
-
|
|
176
|
-
if routes.none?
|
|
177
|
-
<<~MESSAGE
|
|
178
|
-
You don't have any routes defined!
|
|
177
|
+
def footer(routes)
|
|
178
|
+
end
|
|
179
179
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
180
|
+
def no_routes(engine, routes, filter)
|
|
181
|
+
@buffer <<
|
|
182
|
+
if filter.key?(:controller)
|
|
183
183
|
"No routes were found for this controller."
|
|
184
184
|
elsif filter.key?(:grep)
|
|
185
185
|
"No routes were found for this grep pattern."
|
|
186
|
+
elsif routes.none?
|
|
187
|
+
if engine
|
|
188
|
+
"No routes defined."
|
|
189
|
+
else
|
|
190
|
+
<<~MESSAGE
|
|
191
|
+
You don't have any routes defined!
|
|
192
|
+
|
|
193
|
+
Please add some routes in config/routes.rb.
|
|
194
|
+
MESSAGE
|
|
195
|
+
end
|
|
186
196
|
end
|
|
187
197
|
|
|
188
|
-
|
|
198
|
+
unless engine
|
|
199
|
+
@buffer << "For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html."
|
|
200
|
+
end
|
|
189
201
|
end
|
|
190
202
|
end
|
|
191
203
|
|
|
192
204
|
class Sheet < Base
|
|
193
205
|
def section_title(title)
|
|
194
|
-
@buffer << "
|
|
206
|
+
@buffer << "#{title}:"
|
|
195
207
|
end
|
|
196
208
|
|
|
197
209
|
def section(routes)
|
|
@@ -202,6 +214,10 @@ module ActionDispatch
|
|
|
202
214
|
@buffer << draw_header(routes)
|
|
203
215
|
end
|
|
204
216
|
|
|
217
|
+
def footer(routes)
|
|
218
|
+
@buffer << ""
|
|
219
|
+
end
|
|
220
|
+
|
|
205
221
|
private
|
|
206
222
|
def draw_section(routes)
|
|
207
223
|
header_lengths = ["Prefix", "Verb", "URI Pattern"].map(&:length)
|
|
@@ -232,13 +248,17 @@ module ActionDispatch
|
|
|
232
248
|
end
|
|
233
249
|
|
|
234
250
|
def section_title(title)
|
|
235
|
-
@buffer << "
|
|
251
|
+
@buffer << "#{"[ #{title} ]"}"
|
|
236
252
|
end
|
|
237
253
|
|
|
238
254
|
def section(routes)
|
|
239
255
|
@buffer << draw_expanded_section(routes)
|
|
240
256
|
end
|
|
241
257
|
|
|
258
|
+
def footer(routes)
|
|
259
|
+
@buffer << ""
|
|
260
|
+
end
|
|
261
|
+
|
|
242
262
|
private
|
|
243
263
|
def draw_expanded_section(routes)
|
|
244
264
|
routes.map.each_with_index do |r, i|
|
|
@@ -269,7 +289,7 @@ module ActionDispatch
|
|
|
269
289
|
super
|
|
270
290
|
end
|
|
271
291
|
|
|
272
|
-
def no_routes(routes, filter)
|
|
292
|
+
def no_routes(engine, routes, filter)
|
|
273
293
|
@buffer <<
|
|
274
294
|
if filter.none?
|
|
275
295
|
"No unused routes found."
|
|
@@ -300,6 +320,9 @@ module ActionDispatch
|
|
|
300
320
|
def header(routes)
|
|
301
321
|
end
|
|
302
322
|
|
|
323
|
+
def footer(routes)
|
|
324
|
+
end
|
|
325
|
+
|
|
303
326
|
def no_routes(*)
|
|
304
327
|
@buffer << <<~MESSAGE
|
|
305
328
|
<p>You don't have any routes defined!</p>
|