actionpack 5.2.5 → 6.0.0.beta1

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.

Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +111 -399
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/abstract_controller/base.rb +4 -2
  6. data/lib/abstract_controller/caching/fragments.rb +6 -21
  7. data/lib/abstract_controller/callbacks.rb +12 -0
  8. data/lib/abstract_controller/collector.rb +1 -1
  9. data/lib/abstract_controller/helpers.rb +2 -2
  10. data/lib/abstract_controller/railties/routes_helpers.rb +1 -1
  11. data/lib/action_controller.rb +1 -0
  12. data/lib/action_controller/api.rb +2 -1
  13. data/lib/action_controller/base.rb +2 -7
  14. data/lib/action_controller/caching.rb +1 -1
  15. data/lib/action_controller/log_subscriber.rb +8 -5
  16. data/lib/action_controller/metal.rb +2 -2
  17. data/lib/action_controller/metal/conditional_get.rb +9 -3
  18. data/lib/action_controller/metal/data_streaming.rb +5 -6
  19. data/lib/action_controller/metal/default_headers.rb +17 -0
  20. data/lib/action_controller/metal/exceptions.rb +22 -1
  21. data/lib/action_controller/metal/flash.rb +5 -5
  22. data/lib/action_controller/metal/force_ssl.rb +17 -57
  23. data/lib/action_controller/metal/head.rb +1 -1
  24. data/lib/action_controller/metal/helpers.rb +1 -2
  25. data/lib/action_controller/metal/http_authentication.rb +20 -21
  26. data/lib/action_controller/metal/implicit_render.rb +2 -12
  27. data/lib/action_controller/metal/instrumentation.rb +3 -5
  28. data/lib/action_controller/metal/live.rb +28 -26
  29. data/lib/action_controller/metal/mime_responds.rb +13 -2
  30. data/lib/action_controller/metal/params_wrapper.rb +18 -14
  31. data/lib/action_controller/metal/redirecting.rb +32 -11
  32. data/lib/action_controller/metal/rendering.rb +1 -1
  33. data/lib/action_controller/metal/request_forgery_protection.rb +32 -41
  34. data/lib/action_controller/metal/strong_parameters.rb +57 -34
  35. data/lib/action_controller/metal/url_for.rb +1 -1
  36. data/lib/action_controller/railties/helpers.rb +1 -1
  37. data/lib/action_controller/renderer.rb +15 -2
  38. data/lib/action_controller/test_case.rb +5 -9
  39. data/lib/action_dispatch.rb +7 -6
  40. data/lib/action_dispatch/http/cache.rb +14 -10
  41. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  42. data/lib/action_dispatch/http/content_security_policy.rb +9 -8
  43. data/lib/action_dispatch/http/filter_parameters.rb +8 -6
  44. data/lib/action_dispatch/http/filter_redirect.rb +1 -1
  45. data/lib/action_dispatch/http/headers.rb +1 -1
  46. data/lib/action_dispatch/http/mime_negotiation.rb +7 -10
  47. data/lib/action_dispatch/http/mime_type.rb +1 -5
  48. data/lib/action_dispatch/http/parameter_filter.rb +5 -79
  49. data/lib/action_dispatch/http/parameters.rb +13 -3
  50. data/lib/action_dispatch/http/request.rb +10 -13
  51. data/lib/action_dispatch/http/response.rb +14 -14
  52. data/lib/action_dispatch/http/upload.rb +5 -0
  53. data/lib/action_dispatch/http/url.rb +81 -81
  54. data/lib/action_dispatch/journey/formatter.rb +1 -1
  55. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -2
  56. data/lib/action_dispatch/journey/nodes/node.rb +9 -8
  57. data/lib/action_dispatch/journey/path/pattern.rb +3 -4
  58. data/lib/action_dispatch/journey/router.rb +0 -3
  59. data/lib/action_dispatch/journey/router/utils.rb +10 -10
  60. data/lib/action_dispatch/journey/scanner.rb +11 -4
  61. data/lib/action_dispatch/journey/visitors.rb +1 -1
  62. data/lib/action_dispatch/middleware/callbacks.rb +2 -4
  63. data/lib/action_dispatch/middleware/cookies.rb +49 -70
  64. data/lib/action_dispatch/middleware/debug_exceptions.rb +32 -58
  65. data/lib/action_dispatch/middleware/debug_locks.rb +5 -5
  66. data/lib/action_dispatch/middleware/debug_view.rb +50 -0
  67. data/lib/action_dispatch/middleware/exception_wrapper.rb +36 -7
  68. data/lib/action_dispatch/middleware/flash.rb +1 -1
  69. data/lib/action_dispatch/middleware/host_authorization.rb +103 -0
  70. data/lib/action_dispatch/middleware/remote_ip.rb +6 -8
  71. data/lib/action_dispatch/middleware/request_id.rb +2 -2
  72. data/lib/action_dispatch/middleware/session/abstract_store.rb +0 -14
  73. data/lib/action_dispatch/middleware/session/cache_store.rb +6 -11
  74. data/lib/action_dispatch/middleware/session/cookie_store.rb +11 -27
  75. data/lib/action_dispatch/middleware/ssl.rb +8 -8
  76. data/lib/action_dispatch/middleware/stack.rb +2 -2
  77. data/lib/action_dispatch/middleware/static.rb +5 -6
  78. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
  79. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
  80. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
  81. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
  82. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +20 -2
  83. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +4 -4
  84. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +2 -2
  85. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  86. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  87. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +2 -2
  88. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
  89. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +2 -2
  90. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +3 -0
  91. data/lib/action_dispatch/railtie.rb +1 -0
  92. data/lib/action_dispatch/request/session.rb +8 -6
  93. data/lib/action_dispatch/routing.rb +3 -2
  94. data/lib/action_dispatch/routing/inspector.rb +99 -50
  95. data/lib/action_dispatch/routing/mapper.rb +36 -29
  96. data/lib/action_dispatch/routing/polymorphic_routes.rb +3 -4
  97. data/lib/action_dispatch/routing/route_set.rb +11 -12
  98. data/lib/action_dispatch/routing/url_for.rb +1 -0
  99. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +3 -3
  100. data/lib/action_dispatch/testing/assertions/response.rb +2 -3
  101. data/lib/action_dispatch/testing/assertions/routing.rb +7 -2
  102. data/lib/action_dispatch/testing/integration.rb +11 -5
  103. data/lib/action_dispatch/testing/test_process.rb +2 -2
  104. data/lib/action_dispatch/testing/test_response.rb +4 -32
  105. data/lib/action_pack.rb +1 -1
  106. data/lib/action_pack/gem_version.rb +4 -4
  107. metadata +22 -20
@@ -83,7 +83,7 @@ module ActionDispatch
83
83
 
84
84
  private
85
85
  def set_hsts_header!(headers)
86
- headers["Strict-Transport-Security".freeze] ||= @hsts_header
86
+ headers["Strict-Transport-Security"] ||= @hsts_header
87
87
  end
88
88
 
89
89
  def normalize_hsts_options(options)
@@ -102,23 +102,23 @@ module ActionDispatch
102
102
 
103
103
  # https://tools.ietf.org/html/rfc6797#section-6.1
104
104
  def build_hsts_header(hsts)
105
- value = "max-age=#{hsts[:expires].to_i}".dup
105
+ value = +"max-age=#{hsts[:expires].to_i}"
106
106
  value << "; includeSubDomains" if hsts[:subdomains]
107
107
  value << "; preload" if hsts[:preload]
108
108
  value
109
109
  end
110
110
 
111
111
  def flag_cookies_as_secure!(headers)
112
- if cookies = headers["Set-Cookie".freeze]
113
- cookies = cookies.split("\n".freeze)
112
+ if cookies = headers["Set-Cookie"]
113
+ cookies = cookies.split("\n")
114
114
 
115
- headers["Set-Cookie".freeze] = cookies.map { |cookie|
116
- if cookie !~ /;\s*secure\s*(;|$)/i
115
+ headers["Set-Cookie"] = cookies.map { |cookie|
116
+ if !/;\s*secure\s*(;|$)/i.match?(cookie)
117
117
  "#{cookie}; secure"
118
118
  else
119
119
  cookie
120
120
  end
121
- }.join("\n".freeze)
121
+ }.join("\n")
122
122
  end
123
123
  end
124
124
 
@@ -141,7 +141,7 @@ module ActionDispatch
141
141
  host = @redirect[:host] || request.host
142
142
  port = @redirect[:port] || request.port
143
143
 
144
- location = "https://#{host}".dup
144
+ location = +"https://#{host}"
145
145
  location << ":#{port}" if port != 80 && port != 443
146
146
  location << request.fullpath
147
147
  location
@@ -97,8 +97,8 @@ module ActionDispatch
97
97
  middlewares.push(build_middleware(klass, args, block))
98
98
  end
99
99
 
100
- def build(app = nil, &block)
101
- middlewares.freeze.reverse.inject(app || block) { |a, e| e.build(a) }
100
+ def build(app = Proc.new)
101
+ middlewares.freeze.reverse.inject(app) { |a, e| e.build(a) }
102
102
  end
103
103
 
104
104
  private
@@ -41,7 +41,6 @@ module ActionDispatch
41
41
  rescue SystemCallError
42
42
  false
43
43
  end
44
-
45
44
  }
46
45
  return ::Rack::Utils.escape_path(match).b
47
46
  end
@@ -69,7 +68,7 @@ module ActionDispatch
69
68
 
70
69
  headers["Vary"] = "Accept-Encoding" if gzip_path
71
70
 
72
- return [status, headers, body]
71
+ [status, headers, body]
73
72
  ensure
74
73
  request.path_info = path
75
74
  end
@@ -80,7 +79,7 @@ module ActionDispatch
80
79
  end
81
80
 
82
81
  def content_type(path)
83
- ::Rack::Mime.mime_type(::File.extname(path), "text/plain".freeze)
82
+ ::Rack::Mime.mime_type(::File.extname(path), "text/plain")
84
83
  end
85
84
 
86
85
  def gzip_encoding_accepted?(request)
@@ -90,8 +89,8 @@ module ActionDispatch
90
89
  def gzip_file_path(path)
91
90
  can_gzip_mime = content_type(path) =~ /\A(?:text\/|application\/javascript)/
92
91
  gzip_path = "#{path}.gz"
93
- if can_gzip_mime && File.exist?(File.join(@root, ::Rack::Utils.unescape_path(gzip_path).b))
94
- gzip_path.b
92
+ if can_gzip_mime && File.exist?(File.join(@root, ::Rack::Utils.unescape_path(gzip_path)))
93
+ gzip_path
95
94
  else
96
95
  false
97
96
  end
@@ -117,7 +116,7 @@ module ActionDispatch
117
116
  req = Rack::Request.new env
118
117
 
119
118
  if req.get? || req.head?
120
- path = req.path_info.chomp("/".freeze)
119
+ path = req.path_info.chomp("/")
121
120
  if match = @file_handler.match?(path)
122
121
  req.path_info = match
123
122
  return @file_handler.serve(req)
@@ -1,6 +1,8 @@
1
- <% @source_extracts.each_with_index do |source_extract, index| %>
1
+ <% error_index = local_assigns[:error_index] || 0 %>
2
+
3
+ <% source_extracts.each_with_index do |source_extract, index| %>
2
4
  <% if source_extract[:code] %>
3
- <div class="source <%="hidden" if @show_source_idx != index%>" id="frame-source-<%=index%>">
5
+ <div class="source <%= "hidden" if show_source_idx != index %>" id="frame-source-<%= error_index %>-<%= index %>">
4
6
  <div class="info">
5
7
  Extracted source (around line <strong>#<%= source_extract[:line_number] %></strong>):
6
8
  </div>
@@ -1,52 +1,62 @@
1
- <% names = @traces.keys %>
1
+ <% names = traces.keys %>
2
+ <% error_index = local_assigns[:error_index] || 0 %>
2
3
 
3
4
  <p><code>Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %></code></p>
4
5
 
5
- <div id="traces">
6
+ <div id="traces-<%= error_index %>">
6
7
  <% names.each do |name| %>
7
8
  <%
8
- show = "show('#{name.gsub(/\s/, '-')}');"
9
- hide = (names - [name]).collect {|hide_name| "hide('#{hide_name.gsub(/\s/, '-')}');"}
9
+ show = "show('#{name.gsub(/\s/, '-')}-#{error_index}');"
10
+ hide = (names - [name]).collect {|hide_name| "hide('#{hide_name.gsub(/\s/, '-')}-#{error_index}');"}
10
11
  %>
11
12
  <a href="#" onclick="<%= hide.join %><%= show %>; return false;"><%= name %></a> <%= '|' unless names.last == name %>
12
13
  <% end %>
13
14
 
14
- <% @traces.each do |name, trace| %>
15
- <div id="<%= name.gsub(/\s/, '-') %>" style="display: <%= (name == @trace_to_show) ? 'block' : 'none' %>;">
16
- <pre><code><% trace.each do |frame| %><a class="trace-frames" data-frame-id="<%= frame[:id] %>" href="#"><%= frame[:trace] %></a><br><% end %></code></pre>
15
+ <% traces.each do |name, trace| %>
16
+ <div id="<%= "#{name.gsub(/\s/, '-')}-#{error_index}" %>" style="display: <%= (name == trace_to_show) ? 'block' : 'none' %>;">
17
+ <code style="font-size: 11px;">
18
+ <% trace.each do |frame| %>
19
+ <a class="trace-frames trace-frames-<%= error_index %>" data-exception-object-id="<%= frame[:exception_object_id] %>" data-frame-id="<%= frame[:id] %>" href="#">
20
+ <%= frame[:trace] %>
21
+ </a>
22
+ <br>
23
+ <% end %>
24
+ </code>
17
25
  </div>
18
26
  <% end %>
19
27
 
20
28
  <script type="text/javascript">
21
- var traceFrames = document.getElementsByClassName('trace-frames');
22
- var selectedFrame, currentSource = document.getElementById('frame-source-0');
23
-
24
- // Add click listeners for all stack frames
25
- for (var i = 0; i < traceFrames.length; i++) {
26
- traceFrames[i].addEventListener('click', function(e) {
27
- e.preventDefault();
28
- var target = e.target;
29
- var frame_id = target.dataset.frameId;
30
-
31
- if (selectedFrame) {
32
- selectedFrame.className = selectedFrame.className.replace("selected", "");
33
- }
34
-
35
- target.className += " selected";
36
- selectedFrame = target;
37
-
38
- // Change the extracted source code
39
- changeSourceExtract(frame_id);
40
- });
41
-
42
- function changeSourceExtract(frame_id) {
43
- var el = document.getElementById('frame-source-' + frame_id);
44
- if (currentSource && el) {
45
- currentSource.className += " hidden";
46
- el.className = el.className.replace(" hidden", "");
47
- currentSource = el;
29
+ (function() {
30
+ var traceFrames = document.getElementsByClassName('trace-frames-<%= error_index %>');
31
+ var selectedFrame, currentSource = document.getElementById('frame-source-<%= error_index %>-0');
32
+
33
+ // Add click listeners for all stack frames
34
+ for (var i = 0; i < traceFrames.length; i++) {
35
+ traceFrames[i].addEventListener('click', function(e) {
36
+ e.preventDefault();
37
+ var target = e.target;
38
+ var frame_id = target.dataset.frameId;
39
+
40
+ if (selectedFrame) {
41
+ selectedFrame.className = selectedFrame.className.replace("selected", "");
42
+ }
43
+
44
+ target.className += " selected";
45
+ selectedFrame = target;
46
+
47
+ // Change the extracted source code
48
+ changeSourceExtract(frame_id);
49
+ });
50
+
51
+ function changeSourceExtract(frame_id) {
52
+ var el = document.getElementById('frame-source-<%= error_index %>-' + frame_id);
53
+ if (currentSource && el) {
54
+ currentSource.className += " hidden";
55
+ el.className = el.className.replace(" hidden", "");
56
+ currentSource = el;
57
+ }
48
58
  }
49
59
  }
50
- }
60
+ })();
51
61
  </script>
52
62
  </div>
@@ -0,0 +1,7 @@
1
+ <header>
2
+ <h1>Blocked host: <%= @host %></h1>
3
+ </header>
4
+ <div id="container">
5
+ <h2>To allow requests to <%= @host %>, add the following configuration:</h2>
6
+ <pre>Rails.application.config.hosts &lt;&lt; "<%= @host %>"</pre>
7
+ </div>
@@ -0,0 +1,5 @@
1
+ Blocked host: <%= @host %>
2
+
3
+ To allow requests to <%= @host %>, add the following configuration:
4
+
5
+ Rails.application.config.hosts << "<%= @host %>"
@@ -10,7 +10,25 @@
10
10
  <div id="container">
11
11
  <h2><%= h @exception.message %></h2>
12
12
 
13
- <%= render template: "rescues/_source" %>
14
- <%= render template: "rescues/_trace" %>
13
+ <%= render "rescues/source", source_extracts: @source_extracts, show_source_idx: @show_source_idx, error_index: 0 %>
14
+ <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show, error_index: 0 %>
15
+
16
+ <% if @exception.cause %>
17
+ <h2>Exception Causes</h2>
18
+ <% end %>
19
+
20
+ <% @exception_wrapper.wrapped_causes.each.with_index(1) do |wrapper, index| %>
21
+ <div class="details">
22
+ <a class="summary" href="#" style="color: #F0F0F0; text-decoration: none; background: #C52F24; border-bottom: none;" onclick="return toggle(<%= wrapper.exception.object_id %>)">
23
+ <%= wrapper.exception.class.name %>: <%= h wrapper.exception.message %>
24
+ </a>
25
+ </div>
26
+
27
+ <div id="<%= wrapper.exception.object_id %>" style="display: none;">
28
+ <%= render "rescues/source", source_extracts: wrapper.source_extracts, show_source_idx: wrapper.source_to_show_id, error_index: index %>
29
+ <%= render "rescues/trace", traces: wrapper.traces, trace_to_show: wrapper.trace_to_show, error_index: index %>
30
+ </div>
31
+ <% end %>
32
+
15
33
  <%= render template: "rescues/_request_and_response" %>
16
34
  </div>
@@ -10,12 +10,12 @@
10
10
  <div id="container">
11
11
  <h2>
12
12
  <%= h @exception.message %>
13
- <% if %r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}}.match?(@exception.message) %>
14
- <br />To resolve this issue run: bin/rails active_storage:install
13
+ <% if @exception.message.match? %r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}} %>
14
+ <br />To resolve this issue run: rails active_storage:install
15
15
  <% end %>
16
16
  </h2>
17
17
 
18
- <%= render template: "rescues/_source" %>
19
- <%= render template: "rescues/_trace" %>
18
+ <%= render "rescues/source", source_extracts: @source_extracts, show_source_idx: @show_source_idx %>
19
+ <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show %>
20
20
  <%= render template: "rescues/_request_and_response" %>
21
21
  </div>
@@ -4,8 +4,8 @@
4
4
  <% end %>
5
5
 
6
6
  <%= @exception.message %>
7
- <% if %r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}}.match?(@exception.message) %>
8
- To resolve this issue run: bin/rails active_storage:install
7
+ <% if @exception.message.match? %r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}} %>
8
+ To resolve this issue run: rails active_storage:install
9
9
  <% end %>
10
10
 
11
11
  <%= render template: "rescues/_source" %>
@@ -0,0 +1,19 @@
1
+ <header>
2
+ <h1>No template for interactive request</h1>
3
+ </header>
4
+
5
+ <div id="container">
6
+ <h2><%= h @exception.message %></h2>
7
+
8
+ <p class="summary">
9
+ <strong>NOTE!</strong><br>
10
+ Unless told otherwise, Rails expects an action to render a template with the same name,<br>
11
+ contained in a folder named after its controller.
12
+
13
+ If this controller is an API responding with 204 (No Content), <br>
14
+ which does not require a template,
15
+ then this error will occur when trying to access it via browser,<br>
16
+ since we expect an HTML template
17
+ to be rendered for such requests. If that's the case, carry on.
18
+ </p>
19
+ </div>
@@ -0,0 +1,3 @@
1
+ Missing exact template
2
+
3
+ <%= @exception.message %>
@@ -5,7 +5,7 @@
5
5
  <div id="container">
6
6
  <h2><%= h @exception.message %></h2>
7
7
 
8
- <%= render template: "rescues/_source" %>
9
- <%= render template: "rescues/_trace" %>
8
+ <%= render "rescues/source", source_extracts: @source_extracts, show_source_idx: @show_source_idx %>
9
+ <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show %>
10
10
  <%= render template: "rescues/_request_and_response" %>
11
11
  </div>
@@ -14,7 +14,7 @@
14
14
  </p>
15
15
  <% end %>
16
16
 
17
- <%= render template: "rescues/_trace" %>
17
+ <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show %>
18
18
 
19
19
  <% if @routes_inspector %>
20
20
  <h2>
@@ -11,10 +11,10 @@
11
11
  </p>
12
12
  <pre><code><%= h @exception.message %></code></pre>
13
13
 
14
- <%= render template: "rescues/_source" %>
14
+ <%= render "rescues/source", source_extracts: @source_extracts, show_source_idx: @show_source_idx %>
15
15
 
16
16
  <p><%= @exception.sub_template_message %></p>
17
17
 
18
- <%= render template: "rescues/_trace" %>
18
+ <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show %>
19
19
  <%= render template: "rescues/_request_and_response" %>
20
20
  </div>
@@ -197,4 +197,7 @@
197
197
 
198
198
  setupMatchPaths();
199
199
  setupRouteToggleHelperLinks();
200
+
201
+ // Focus the search input after page has loaded
202
+ document.getElementById('search').focus();
200
203
  </script>
@@ -21,6 +21,7 @@ module ActionDispatch
21
21
  config.action_dispatch.encrypted_signed_cookie_salt = "signed encrypted cookie"
22
22
  config.action_dispatch.authenticated_encrypted_cookie_salt = "authenticated encrypted cookie"
23
23
  config.action_dispatch.use_authenticated_cookie_encryption = false
24
+ config.action_dispatch.use_cookies_with_metadata = false
24
25
  config.action_dispatch.perform_deep_munge = true
25
26
 
26
27
  config.action_dispatch.default_headers = {
@@ -90,13 +90,15 @@ module ActionDispatch
90
90
  # +nil+ if the given key is not found in the session.
91
91
  def [](key)
92
92
  load_for_read!
93
- key = key.to_s
93
+ @delegate[key.to_s]
94
+ end
94
95
 
95
- if key == "session_id"
96
- id && id.public_id
97
- else
98
- @delegate[key]
99
- end
96
+ # Returns the nested value specified by the sequence of keys, returning
97
+ # +nil+ if any intermediate step is +nil+.
98
+ def dig(*keys)
99
+ load_for_read!
100
+ keys = keys.map.with_index { |key, i| i.zero? ? key.to_s : key }
101
+ @delegate.dig(*keys)
100
102
  end
101
103
 
102
104
  # Returns true if the session has the given key or false.
@@ -243,8 +243,9 @@ module ActionDispatch
243
243
  #
244
244
  # rails routes
245
245
  #
246
- # Target specific controllers by prefixing the command with <tt>-c</tt> option.
247
- #
246
+ # Target a specific controller with <tt>-c</tt>, or grep routes
247
+ # using <tt>-g</tt>. Useful in conjunction with <tt>--expanded</tt>
248
+ # which displays routes vertically.
248
249
  module Routing
249
250
  extend ActiveSupport::Autoload
250
251
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "delegate"
4
- require "active_support/core_ext/string/strip"
4
+ require "io/console/size"
5
5
 
6
6
  module ActionDispatch
7
7
  module Routing
@@ -61,11 +61,11 @@ module ActionDispatch
61
61
  @routes = routes
62
62
  end
63
63
 
64
- def format(formatter, filter = nil)
64
+ def format(formatter, filter = {})
65
65
  routes_to_display = filter_routes(normalize_filter(filter))
66
66
  routes = collect_routes(routes_to_display)
67
67
  if routes.none?
68
- formatter.no_routes(collect_routes(@routes))
68
+ formatter.no_routes(collect_routes(@routes), filter)
69
69
  return formatter.result
70
70
  end
71
71
 
@@ -81,12 +81,12 @@ module ActionDispatch
81
81
  end
82
82
 
83
83
  private
84
-
85
84
  def normalize_filter(filter)
86
- if filter.is_a?(Hash) && filter[:controller]
85
+ if filter[:controller]
87
86
  { controller: /#{filter[:controller].underscore.sub(/_?controller\z/, "")}/ }
88
- elsif filter
89
- { controller: /#{filter}/, action: /#{filter}/, verb: /#{filter}/, name: /#{filter}/, path: /#{filter}/ }
87
+ elsif filter[:grep]
88
+ { controller: /#{filter[:grep]}/, action: /#{filter[:grep]}/,
89
+ verb: /#{filter[:grep]}/, name: /#{filter[:grep]}/, path: /#{filter[:grep]}/ }
90
90
  end
91
91
  end
92
92
 
@@ -126,62 +126,111 @@ module ActionDispatch
126
126
  end
127
127
  end
128
128
 
129
- class ConsoleFormatter
130
- def initialize
131
- @buffer = []
132
- end
129
+ module ConsoleFormatter
130
+ class Base
131
+ def initialize
132
+ @buffer = []
133
+ end
133
134
 
134
- def result
135
- @buffer.join("\n")
136
- end
135
+ def result
136
+ @buffer.join("\n")
137
+ end
137
138
 
138
- def section_title(title)
139
- @buffer << "\n#{title}:"
140
- end
139
+ def section_title(title)
140
+ end
141
141
 
142
- def section(routes)
143
- @buffer << draw_section(routes)
144
- end
142
+ def section(routes)
143
+ end
145
144
 
146
- def header(routes)
147
- @buffer << draw_header(routes)
148
- end
145
+ def header(routes)
146
+ end
149
147
 
150
- def no_routes(routes)
151
- @buffer <<
152
- if routes.none?
153
- <<-MESSAGE.strip_heredoc
154
- You don't have any routes defined!
148
+ def no_routes(routes, filter)
149
+ @buffer <<
150
+ if routes.none?
151
+ <<~MESSAGE
152
+ You don't have any routes defined!
153
+
154
+ Please add some routes in config/routes.rb.
155
+ MESSAGE
156
+ elsif filter.key?(:controller)
157
+ "No routes were found for this controller."
158
+ elsif filter.key?(:grep)
159
+ "No routes were found for this grep pattern."
160
+ end
155
161
 
156
- Please add some routes in config/routes.rb.
157
- MESSAGE
158
- else
159
- "No routes were found for this controller"
162
+ @buffer << "For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html."
160
163
  end
161
- @buffer << "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
162
164
  end
163
165
 
164
- private
165
- def draw_section(routes)
166
- header_lengths = ["Prefix", "Verb", "URI Pattern"].map(&:length)
167
- name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max)
166
+ class Sheet < Base
167
+ def section_title(title)
168
+ @buffer << "\n#{title}:"
169
+ end
168
170
 
169
- routes.map do |r|
170
- "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}"
171
- end
171
+ def section(routes)
172
+ @buffer << draw_section(routes)
173
+ end
174
+
175
+ def header(routes)
176
+ @buffer << draw_header(routes)
172
177
  end
173
178
 
174
- def draw_header(routes)
175
- name_width, verb_width, path_width = widths(routes)
179
+ private
180
+
181
+ def draw_section(routes)
182
+ header_lengths = ["Prefix", "Verb", "URI Pattern"].map(&:length)
183
+ name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max)
184
+
185
+ routes.map do |r|
186
+ "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}"
187
+ end
188
+ end
189
+
190
+ def draw_header(routes)
191
+ name_width, verb_width, path_width = widths(routes)
192
+
193
+ "#{"Prefix".rjust(name_width)} #{"Verb".ljust(verb_width)} #{"URI Pattern".ljust(path_width)} Controller#Action"
194
+ end
195
+
196
+ def widths(routes)
197
+ [routes.map { |r| r[:name].length }.max || 0,
198
+ routes.map { |r| r[:verb].length }.max || 0,
199
+ routes.map { |r| r[:path].length }.max || 0]
200
+ end
201
+ end
176
202
 
177
- "#{"Prefix".rjust(name_width)} #{"Verb".ljust(verb_width)} #{"URI Pattern".ljust(path_width)} Controller#Action"
203
+ class Expanded < Base
204
+ def section_title(title)
205
+ @buffer << "\n#{"[ #{title} ]"}"
178
206
  end
179
207
 
180
- def widths(routes)
181
- [routes.map { |r| r[:name].length }.max || 0,
182
- routes.map { |r| r[:verb].length }.max || 0,
183
- routes.map { |r| r[:path].length }.max || 0]
208
+ def section(routes)
209
+ @buffer << draw_expanded_section(routes)
184
210
  end
211
+
212
+ private
213
+
214
+ def draw_expanded_section(routes)
215
+ routes.map.each_with_index do |r, i|
216
+ <<~MESSAGE.chomp
217
+ #{route_header(index: i + 1)}
218
+ Prefix | #{r[:name]}
219
+ Verb | #{r[:verb]}
220
+ URI | #{r[:path]}
221
+ Controller#Action | #{r[:reqs]}
222
+ MESSAGE
223
+ end
224
+ end
225
+
226
+ def route_header(index:)
227
+ console_width = IO.console_size.second
228
+ header_prefix = "--[ Route #{index} ]"
229
+ dash_remainder = [console_width - header_prefix.size, 0].max
230
+
231
+ "#{header_prefix}#{'-' * dash_remainder}"
232
+ end
233
+ end
185
234
  end
186
235
 
187
236
  class HtmlTableFormatter
@@ -203,16 +252,16 @@ module ActionDispatch
203
252
  end
204
253
 
205
254
  def no_routes(*)
206
- @buffer << <<-MESSAGE.strip_heredoc
255
+ @buffer << <<~MESSAGE
207
256
  <p>You don't have any routes defined!</p>
208
257
  <ul>
209
258
  <li>Please add some routes in <tt>config/routes.rb</tt>.</li>
210
259
  <li>
211
260
  For more information about routes, please see the Rails guide
212
- <a href="http://guides.rubyonrails.org/routing.html">Rails Routing from the Outside In</a>.
261
+ <a href="https://guides.rubyonrails.org/routing.html">Rails Routing from the Outside In</a>.
213
262
  </li>
214
263
  </ul>
215
- MESSAGE
264
+ MESSAGE
216
265
  end
217
266
 
218
267
  def result