actionpack 7.0.8.6 → 7.1.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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +318 -423
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/lib/abstract_controller/base.rb +19 -10
  6. data/lib/abstract_controller/caching/fragments.rb +2 -0
  7. data/lib/abstract_controller/callbacks.rb +31 -6
  8. data/lib/abstract_controller/deprecator.rb +7 -0
  9. data/lib/abstract_controller/helpers.rb +61 -18
  10. data/lib/abstract_controller/railties/routes_helpers.rb +1 -16
  11. data/lib/abstract_controller/rendering.rb +3 -3
  12. data/lib/abstract_controller/translation.rb +1 -27
  13. data/lib/abstract_controller/url_for.rb +2 -0
  14. data/lib/abstract_controller.rb +6 -0
  15. data/lib/action_controller/api.rb +5 -3
  16. data/lib/action_controller/base.rb +3 -17
  17. data/lib/action_controller/caching.rb +2 -0
  18. data/lib/action_controller/deprecator.rb +7 -0
  19. data/lib/action_controller/form_builder.rb +2 -0
  20. data/lib/action_controller/log_subscriber.rb +16 -4
  21. data/lib/action_controller/metal/content_security_policy.rb +1 -1
  22. data/lib/action_controller/metal/data_streaming.rb +2 -0
  23. data/lib/action_controller/metal/default_headers.rb +2 -0
  24. data/lib/action_controller/metal/etag_with_flash.rb +2 -0
  25. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
  26. data/lib/action_controller/metal/exceptions.rb +8 -0
  27. data/lib/action_controller/metal/head.rb +8 -6
  28. data/lib/action_controller/metal/helpers.rb +3 -14
  29. data/lib/action_controller/metal/http_authentication.rb +10 -4
  30. data/lib/action_controller/metal/implicit_render.rb +5 -3
  31. data/lib/action_controller/metal/instrumentation.rb +8 -1
  32. data/lib/action_controller/metal/live.rb +24 -0
  33. data/lib/action_controller/metal/mime_responds.rb +2 -2
  34. data/lib/action_controller/metal/params_wrapper.rb +3 -1
  35. data/lib/action_controller/metal/permissions_policy.rb +1 -1
  36. data/lib/action_controller/metal/redirecting.rb +6 -6
  37. data/lib/action_controller/metal/renderers.rb +2 -2
  38. data/lib/action_controller/metal/rendering.rb +0 -7
  39. data/lib/action_controller/metal/request_forgery_protection.rb +138 -50
  40. data/lib/action_controller/metal/rescue.rb +2 -0
  41. data/lib/action_controller/metal/streaming.rb +70 -30
  42. data/lib/action_controller/metal/strong_parameters.rb +89 -50
  43. data/lib/action_controller/metal/url_for.rb +7 -0
  44. data/lib/action_controller/metal.rb +79 -21
  45. data/lib/action_controller/railtie.rb +22 -9
  46. data/lib/action_controller/renderer.rb +98 -65
  47. data/lib/action_controller/test_case.rb +15 -5
  48. data/lib/action_controller.rb +8 -1
  49. data/lib/action_dispatch/constants.rb +32 -0
  50. data/lib/action_dispatch/deprecator.rb +7 -0
  51. data/lib/action_dispatch/http/cache.rb +1 -3
  52. data/lib/action_dispatch/http/content_security_policy.rb +9 -8
  53. data/lib/action_dispatch/http/filter_parameters.rb +15 -14
  54. data/lib/action_dispatch/http/headers.rb +2 -0
  55. data/lib/action_dispatch/http/mime_negotiation.rb +21 -21
  56. data/lib/action_dispatch/http/mime_type.rb +35 -12
  57. data/lib/action_dispatch/http/mime_types.rb +3 -1
  58. data/lib/action_dispatch/http/parameters.rb +1 -1
  59. data/lib/action_dispatch/http/permissions_policy.rb +44 -15
  60. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  61. data/lib/action_dispatch/http/request.rb +48 -14
  62. data/lib/action_dispatch/http/response.rb +78 -59
  63. data/lib/action_dispatch/http/upload.rb +2 -0
  64. data/lib/action_dispatch/journey/formatter.rb +8 -2
  65. data/lib/action_dispatch/journey/path/pattern.rb +14 -14
  66. data/lib/action_dispatch/journey/route.rb +3 -2
  67. data/lib/action_dispatch/journey/router.rb +5 -4
  68. data/lib/action_dispatch/journey/routes.rb +2 -2
  69. data/lib/action_dispatch/log_subscriber.rb +23 -0
  70. data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -6
  71. data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
  72. data/lib/action_dispatch/middleware/callbacks.rb +2 -0
  73. data/lib/action_dispatch/middleware/cookies.rb +81 -98
  74. data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -25
  75. data/lib/action_dispatch/middleware/debug_locks.rb +4 -1
  76. data/lib/action_dispatch/middleware/debug_view.rb +7 -2
  77. data/lib/action_dispatch/middleware/exception_wrapper.rb +181 -27
  78. data/lib/action_dispatch/middleware/executor.rb +1 -1
  79. data/lib/action_dispatch/middleware/flash.rb +7 -0
  80. data/lib/action_dispatch/middleware/host_authorization.rb +6 -3
  81. data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
  82. data/lib/action_dispatch/middleware/reloader.rb +7 -5
  83. data/lib/action_dispatch/middleware/remote_ip.rb +17 -16
  84. data/lib/action_dispatch/middleware/request_id.rb +2 -0
  85. data/lib/action_dispatch/middleware/server_timing.rb +4 -4
  86. data/lib/action_dispatch/middleware/session/abstract_store.rb +5 -0
  87. data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
  88. data/lib/action_dispatch/middleware/session/cookie_store.rb +11 -5
  89. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
  90. data/lib/action_dispatch/middleware/show_exceptions.rb +19 -15
  91. data/lib/action_dispatch/middleware/ssl.rb +18 -6
  92. data/lib/action_dispatch/middleware/stack.rb +7 -2
  93. data/lib/action_dispatch/middleware/static.rb +12 -8
  94. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
  95. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +4 -4
  96. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
  97. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +7 -7
  98. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
  99. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +17 -0
  100. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +16 -12
  101. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -1
  102. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  103. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +4 -4
  104. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  105. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  106. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
  107. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +46 -37
  108. data/lib/action_dispatch/railtie.rb +14 -4
  109. data/lib/action_dispatch/request/session.rb +16 -6
  110. data/lib/action_dispatch/request/utils.rb +8 -3
  111. data/lib/action_dispatch/routing/inspector.rb +54 -6
  112. data/lib/action_dispatch/routing/mapper.rb +26 -14
  113. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
  114. data/lib/action_dispatch/routing/redirection.rb +15 -6
  115. data/lib/action_dispatch/routing/route_set.rb +52 -22
  116. data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
  117. data/lib/action_dispatch/routing/url_for.rb +5 -1
  118. data/lib/action_dispatch/routing.rb +4 -4
  119. data/lib/action_dispatch/system_test_case.rb +3 -3
  120. data/lib/action_dispatch/system_testing/browser.rb +5 -6
  121. data/lib/action_dispatch/system_testing/driver.rb +13 -21
  122. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +27 -16
  123. data/lib/action_dispatch/testing/assertions/response.rb +13 -6
  124. data/lib/action_dispatch/testing/assertions/routing.rb +67 -28
  125. data/lib/action_dispatch/testing/assertions.rb +3 -1
  126. data/lib/action_dispatch/testing/integration.rb +27 -17
  127. data/lib/action_dispatch/testing/request_encoder.rb +4 -1
  128. data/lib/action_dispatch/testing/test_process.rb +4 -3
  129. data/lib/action_dispatch/testing/test_request.rb +1 -1
  130. data/lib/action_dispatch/testing/test_response.rb +23 -9
  131. data/lib/action_dispatch.rb +37 -4
  132. data/lib/action_pack/gem_version.rb +4 -4
  133. data/lib/action_pack/version.rb +1 -1
  134. data/lib/action_pack.rb +1 -1
  135. metadata +55 -33
@@ -1,24 +1,45 @@
1
1
  <% content_for :style do %>
2
+ h2, p {
3
+ padding-left: 30px;
4
+ }
5
+
2
6
  #route_table {
3
7
  margin: 0;
4
8
  border-collapse: collapse;
9
+ word-wrap:break-word;
10
+ table-layout: fixed;
11
+ width:100%;
5
12
  }
6
13
 
7
14
  #route_table thead tr {
8
15
  border-bottom: 2px solid #ddd;
9
16
  }
10
17
 
18
+ #route_table th {
19
+ padding-left: 30px;
20
+ text-align: left;
21
+ }
22
+
11
23
  #route_table thead tr.bottom {
12
24
  border-bottom: none;
13
25
  }
14
26
 
15
27
  #route_table thead tr.bottom th {
16
- padding: 10px 0;
28
+ padding: 10px 30px;
17
29
  line-height: 15px;
18
30
  }
19
31
 
20
- #route_table thead tr.bottom th input#search {
32
+ #route_table #search_container {
33
+ padding: 7px 30px;
34
+ }
35
+
36
+ #route_table thead tr th input#search {
21
37
  -webkit-appearance: textfield;
38
+ width:100%;
39
+ }
40
+
41
+ #route_table thead th.http-verb {
42
+ width: 10%;
22
43
  }
23
44
 
24
45
  #route_table tbody tr {
@@ -45,11 +66,6 @@
45
66
  padding: 4px 30px;
46
67
  }
47
68
 
48
- #path_search {
49
- width: 80%;
50
- font-size: inherit;
51
- }
52
-
53
69
  @media (prefers-color-scheme: dark) {
54
70
  #route_table tbody tr:nth-child(odd) {
55
71
  background: #282828;
@@ -62,28 +78,22 @@
62
78
  }
63
79
  <% end %>
64
80
 
65
- <table id='route_table' class='route_table'>
81
+ <table id='route_table'>
66
82
  <thead>
67
83
  <tr>
68
- <th>Helper</th>
69
- <th>HTTP Verb</th>
70
- <th>Path</th>
71
- <th>Controller#Action</th>
72
- </tr>
73
- <tr class='bottom'>
74
- <th><%# Helper %>
75
- <%= link_to "Path", "#", 'data-route-helper' => '_path',
84
+ <th>Helper
85
+ (<%= link_to "Path", "#", 'data-route-helper' => '_path',
76
86
  title: "Returns a relative path (without the http or domain)" %> /
77
87
  <%= link_to "Url", "#", 'data-route-helper' => '_url',
78
- title: "Returns an absolute URL (with the http and domain)" %>
79
- </th>
80
- <th><%# HTTP Verb %>
81
- </th>
82
- <th><%# Path %>
83
- <%= search_field(:path, nil, id: 'search', placeholder: "Path Match") %>
84
- </th>
85
- <th><%# Controller#action %>
88
+ title: "Returns an absolute URL (with the http and domain)" %>)
86
89
  </th>
90
+ <th class="http-verb">HTTP Verb</th>
91
+ <th>Path</th>
92
+ <th>Controller#Action</th>
93
+ <th>Source Location</th>
94
+ </tr>
95
+ <tr>
96
+ <th colspan="5" id="search_container"><%= search_field(:query, nil, id: 'search', placeholder: "Search") %></th>
87
97
  </tr>
88
98
  </thead>
89
99
  <tbody class='exact_matches' id='exact_matches'>
@@ -99,8 +109,8 @@
99
109
  // support forEach iterator on NodeList
100
110
  NodeList.prototype.forEach = Array.prototype.forEach;
101
111
 
102
- // Enables path search functionality
103
- function setupMatchPaths() {
112
+ // Enables query search functionality
113
+ function setupMatchingRoutes() {
104
114
  // Check if there are any matched results in a section
105
115
  function checkNoMatch(section, trElement) {
106
116
  if (section.children.length <= 1) {
@@ -128,8 +138,8 @@
128
138
  }
129
139
 
130
140
  // remove params or fragments
131
- function sanitizePath(path) {
132
- return path.replace(/[#?].*/, '');
141
+ function sanitizeQuery(query) {
142
+ return query.replace(/[#?].*/, '');
133
143
  }
134
144
 
135
145
  var pathElements = document.querySelectorAll('#route_table [data-route-path]'),
@@ -156,16 +166,16 @@
156
166
 
157
167
  // On key press perform a search for matching paths
158
168
  delayedKeyup(searchElem, function() {
159
- var path = sanitizePath(searchElem.value),
160
- defaultExactMatch = buildTr('Paths Matching (' + path + '):'),
161
- defaultFuzzyMatch = buildTr('Paths Containing (' + path +'):'),
162
- noExactMatch = buildTr('No Exact Matches Found'),
163
- noFuzzyMatch = buildTr('No Fuzzy Matches Found');
169
+ var query = sanitizeQuery(searchElem.value),
170
+ defaultExactMatch = buildTr("Routes matching '" + query + "':"),
171
+ defaultFuzzyMatch = buildTr("Routes containing '" + query + "':"),
172
+ noExactMatch = buildTr('No exact matches found'),
173
+ noFuzzyMatch = buildTr('No fuzzy matches found');
164
174
 
165
- if (!path)
175
+ if (!query)
166
176
  return searchElem.onblur();
167
177
 
168
- getJSON('/rails/info/routes?path=' + path, function(matches){
178
+ getJSON('/rails/info/routes?query=' + query, function(matches){
169
179
  // Clear out results section
170
180
  exactSection.replaceChildren(defaultExactMatch);
171
181
  fuzzySection.replaceChildren(defaultFuzzyMatch);
@@ -173,7 +183,6 @@
173
183
  // Display exact matches and fuzzy matches
174
184
  pathElements.forEach(function(elem) {
175
185
  var elemPath = elem.getAttribute('data-route-path');
176
-
177
186
  if (matches['exact'].indexOf(elemPath) != -1)
178
187
  exactSection.appendChild(elem.parentNode.cloneNode(true));
179
188
 
@@ -215,7 +224,7 @@
215
224
  });
216
225
  }
217
226
 
218
- setupMatchPaths();
227
+ setupMatchingRoutes();
219
228
  setupRouteToggleHelperLinks();
220
229
 
221
230
  // Focus the search input after page has loaded
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "action_dispatch"
4
+ require "action_dispatch/log_subscriber"
4
5
  require "active_support/messages/rotation_configuration"
5
6
 
6
7
  module ActionDispatch
@@ -8,7 +9,7 @@ module ActionDispatch
8
9
  config.action_dispatch = ActiveSupport::OrderedOptions.new
9
10
  config.action_dispatch.x_sendfile_header = nil
10
11
  config.action_dispatch.ip_spoofing_check = true
11
- config.action_dispatch.show_exceptions = true
12
+ config.action_dispatch.show_exceptions = :all
12
13
  config.action_dispatch.tld_length = 1
13
14
  config.action_dispatch.ignore_accept_header = false
14
15
  config.action_dispatch.rescue_templates = {}
@@ -23,9 +24,9 @@ module ActionDispatch
23
24
  config.action_dispatch.use_authenticated_cookie_encryption = false
24
25
  config.action_dispatch.use_cookies_with_metadata = false
25
26
  config.action_dispatch.perform_deep_munge = true
26
- config.action_dispatch.request_id_header = "X-Request-Id"
27
- config.action_dispatch.return_only_request_media_type_on_content_type = true
27
+ config.action_dispatch.request_id_header = ActionDispatch::Constants::X_REQUEST_ID
28
28
  config.action_dispatch.log_rescued_responses = true
29
+ config.action_dispatch.debug_exception_log_level = :fatal
29
30
 
30
31
  config.action_dispatch.default_headers = {
31
32
  "X-Frame-Options" => "SAMEORIGIN",
@@ -40,13 +41,19 @@ module ActionDispatch
40
41
 
41
42
  config.eager_load_namespaces << ActionDispatch
42
43
 
44
+ initializer "action_dispatch.deprecator", before: :load_environment_config do |app|
45
+ app.deprecators[:action_dispatch] = ActionDispatch.deprecator
46
+ end
47
+
43
48
  initializer "action_dispatch.configure" do |app|
44
49
  ActionDispatch::Http::URL.secure_protocol = app.config.force_ssl
45
50
  ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
46
51
 
47
52
  ActiveSupport.on_load(:action_dispatch_request) do
48
53
  self.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
49
- self.return_only_media_type_on_content_type = app.config.action_dispatch.return_only_request_media_type_on_content_type
54
+ unless app.config.action_dispatch.respond_to?(:return_only_request_media_type_on_content_type)
55
+ self.return_only_media_type_on_content_type = app.config.action_dispatch.return_only_request_media_type_on_content_type
56
+ end
50
57
  ActionDispatch::Request::Utils.perform_deep_munge = app.config.action_dispatch.perform_deep_munge
51
58
  end
52
59
 
@@ -61,6 +68,9 @@ module ActionDispatch
61
68
  config.action_dispatch.always_write_cookie = Rails.env.development? if config.action_dispatch.always_write_cookie.nil?
62
69
  ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie
63
70
 
71
+ ActionDispatch::Routing::Mapper.route_source_locations = Rails.env.development?
72
+ ActionDispatch::Routing::Mapper.backtrace_cleaner = Rails.backtrace_cleaner
73
+
64
74
  ActionDispatch.test_app = app
65
75
  end
66
76
  end
@@ -78,6 +78,8 @@ module ActionDispatch
78
78
  @loaded = false
79
79
  @exists = nil # We haven't checked yet.
80
80
  @enabled = enabled
81
+ @id_was = nil
82
+ @id_was_initialized = false
81
83
  end
82
84
 
83
85
  def id
@@ -176,9 +178,14 @@ module ActionDispatch
176
178
  # session.to_hash
177
179
  # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2", "foo" => "bar"}
178
180
  def update(hash)
181
+ unless hash.respond_to?(:to_hash)
182
+ raise TypeError, "no implicit conversion of #{hash.class.name} into Hash"
183
+ end
184
+
179
185
  load_for_write!
180
- @delegate.update hash.stringify_keys
186
+ @delegate.update hash.to_hash.stringify_keys
181
187
  end
188
+ alias :merge! :update
182
189
 
183
190
  # Deletes given key from the session.
184
191
  def delete(key)
@@ -232,15 +239,15 @@ module ActionDispatch
232
239
  @delegate.empty?
233
240
  end
234
241
 
235
- def merge!(other)
236
- load_for_write!
237
- @delegate.merge!(other)
238
- end
239
-
240
242
  def each(&block)
241
243
  to_hash.each(&block)
242
244
  end
243
245
 
246
+ def id_was
247
+ load_for_read!
248
+ @id_was
249
+ end
250
+
244
251
  private
245
252
  def load_for_read!
246
253
  load! if !loaded? && exists?
@@ -260,10 +267,13 @@ module ActionDispatch
260
267
 
261
268
  def load!
262
269
  if enabled?
270
+ @id_was_initialized = true unless exists?
263
271
  id, session = @by.load_session @req
264
272
  options[:id] = id
265
273
  @delegate.replace(session.stringify_keys)
274
+ @id_was = id unless @id_was_initialized
266
275
  end
276
+ @id_was_initialized = true
267
277
  @loaded = true
268
278
  end
269
279
  end
@@ -55,9 +55,11 @@ module ActionDispatch
55
55
  if params.has_key?(:tempfile)
56
56
  ActionDispatch::Http::UploadedFile.new(params)
57
57
  else
58
- params.transform_values do |val|
59
- normalize_encode_params(val)
60
- end.with_indifferent_access
58
+ hwia = ActiveSupport::HashWithIndifferentAccess.new
59
+ params.each_pair do |key, val|
60
+ hwia[key] = normalize_encode_params(val)
61
+ end
62
+ hwia
61
63
  end
62
64
  else
63
65
  params
@@ -83,6 +85,9 @@ module ActionDispatch
83
85
  return params unless controller && controller.valid_encoding? && encoding_template = action_encoding_template(request, controller, action)
84
86
  params.except(:controller, :action).each do |key, value|
85
87
  ActionDispatch::Request::Utils.each_param_value(value) do |param|
88
+ # If `param` is frozen, it comes from the router defaults
89
+ next if param.frozen?
90
+
86
91
  if encoding_template[key.to_s]
87
92
  param.force_encoding(encoding_template[key.to_s])
88
93
  end
@@ -6,8 +6,21 @@ require "io/console/size"
6
6
  module ActionDispatch
7
7
  module Routing
8
8
  class RouteWrapper < SimpleDelegator # :nodoc:
9
+ def matches_filter?(filter, value)
10
+ return __getobj__.path.match(value) if filter == :exact_path_match
11
+
12
+ value.match?(public_send(filter))
13
+ end
14
+
9
15
  def endpoint
10
- app.dispatcher? ? "#{controller}##{action}" : rack_app.inspect
16
+ case
17
+ when app.dispatcher?
18
+ "#{controller}##{action}"
19
+ when rack_app.is_a?(Proc)
20
+ "Inline handler (Proc/Lambda)"
21
+ else
22
+ rack_app.inspect
23
+ end
11
24
  end
12
25
 
13
26
  def constraints
@@ -85,8 +98,18 @@ module ActionDispatch
85
98
  if filter[:controller]
86
99
  { controller: /#{filter[:controller].underscore.sub(/_?controller\z/, "")}/ }
87
100
  elsif filter[:grep]
88
- { controller: /#{filter[:grep]}/, action: /#{filter[:grep]}/,
89
- verb: /#{filter[:grep]}/, name: /#{filter[:grep]}/, path: /#{filter[:grep]}/ }
101
+ grep_pattern = Regexp.new(filter[:grep])
102
+ path = URI::DEFAULT_PARSER.escape(filter[:grep])
103
+ normalized_path = ("/" + path).squeeze("/")
104
+
105
+ {
106
+ controller: grep_pattern,
107
+ action: grep_pattern,
108
+ verb: grep_pattern,
109
+ name: grep_pattern,
110
+ path: grep_pattern,
111
+ exact_path_match: normalized_path,
112
+ }
90
113
  end
91
114
  end
92
115
 
@@ -94,7 +117,7 @@ module ActionDispatch
94
117
  if filter
95
118
  @routes.select do |route|
96
119
  route_wrapper = RouteWrapper.new(route)
97
- filter.any? { |default, value| value.match?(route_wrapper.send(default)) }
120
+ filter.any? { |filter_type, value| route_wrapper.matches_filter?(filter_type, value) }
98
121
  end
99
122
  else
100
123
  @routes
@@ -110,7 +133,8 @@ module ActionDispatch
110
133
  { name: route.name,
111
134
  verb: route.verb,
112
135
  path: route.path,
113
- reqs: route.reqs }
136
+ reqs: route.reqs,
137
+ source_location: route.source_location }
114
138
  end
115
139
  end
116
140
 
@@ -216,13 +240,16 @@ module ActionDispatch
216
240
  private
217
241
  def draw_expanded_section(routes)
218
242
  routes.map.each_with_index do |r, i|
219
- <<~MESSAGE.chomp
243
+ route_rows = <<~MESSAGE.chomp
220
244
  #{route_header(index: i + 1)}
221
245
  Prefix | #{r[:name]}
222
246
  Verb | #{r[:verb]}
223
247
  URI | #{r[:path]}
224
248
  Controller#Action | #{r[:reqs]}
225
249
  MESSAGE
250
+ source_location = "\nSource Location | #{r[:source_location]}"
251
+ route_rows += source_location if r[:source_location].present?
252
+ route_rows
226
253
  end
227
254
  end
228
255
 
@@ -230,6 +257,27 @@ module ActionDispatch
230
257
  "--[ Route #{index} ]".ljust(@width, "-")
231
258
  end
232
259
  end
260
+
261
+ class Unused < Sheet
262
+ def header(routes)
263
+ @buffer << <<~MSG
264
+ Found #{routes.count} unused #{"route".pluralize(routes.count)}:
265
+ MSG
266
+
267
+ super
268
+ end
269
+
270
+ def no_routes(routes, filter)
271
+ @buffer <<
272
+ if filter.none?
273
+ "No unused routes found."
274
+ elsif filter.key?(:controller)
275
+ "No unused routes found for this controller."
276
+ elsif filter.key?(:grep)
277
+ "No unused routes found for this grep pattern."
278
+ end
279
+ end
280
+ end
233
281
  end
234
282
 
235
283
  class HtmlTableFormatter
@@ -12,6 +12,9 @@ module ActionDispatch
12
12
  class Mapper
13
13
  URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
14
14
 
15
+ cattr_accessor :route_source_locations, instance_accessor: false, default: false
16
+ cattr_accessor :backtrace_cleaner, instance_accessor: false, default: ActiveSupport::BacktraceCleaner.new
17
+
15
18
  class Constraints < Routing::Endpoint # :nodoc:
16
19
  attr_reader :app, :constraints
17
20
 
@@ -43,7 +46,7 @@ module ActionDispatch
43
46
  end
44
47
 
45
48
  def serve(req)
46
- return [ 404, { "X-Cascade" => "pass" }, [] ] unless matches?(req)
49
+ return [ 404, { Constants::X_CASCADE => "pass" }, [] ] unless matches?(req)
47
50
 
48
51
  @strategy.call @app, req
49
52
  end
@@ -170,7 +173,7 @@ module ActionDispatch
170
173
  Journey::Route.new(name: name, app: application, path: path, constraints: conditions,
171
174
  required_defaults: required_defaults, defaults: defaults,
172
175
  request_method_match: request_method, precedence: precedence,
173
- scope_options: scope_options, internal: @internal)
176
+ scope_options: scope_options, internal: @internal, source_location: route_source_location)
174
177
  end
175
178
 
176
179
  def application
@@ -306,7 +309,7 @@ module ActionDispatch
306
309
  end
307
310
 
308
311
  def split_to(to)
309
- if /#/.match?(to)
312
+ if to&.include?("#")
310
313
  to.split("#").map!(&:-@)
311
314
  else
312
315
  []
@@ -356,6 +359,15 @@ module ActionDispatch
356
359
  def dispatcher(raise_on_name_error)
357
360
  Routing::RouteSet::Dispatcher.new raise_on_name_error
358
361
  end
362
+
363
+ def route_source_location
364
+ if Mapper.route_source_locations
365
+ action_dispatch_dir = File.expand_path("..", __dir__)
366
+ caller_location = caller_locations.find { |location| !location.path.include?(action_dispatch_dir) }
367
+ cleaned_path = Mapper.backtrace_cleaner.clean([caller_location.path]).first
368
+ "#{cleaned_path}:#{caller_location.lineno}" if cleaned_path
369
+ end
370
+ end
359
371
  end
360
372
 
361
373
  # Invokes Journey::Router::Utils.normalize_path, then ensures that
@@ -652,7 +664,7 @@ module ActionDispatch
652
664
 
653
665
  script_namer = ->(options) do
654
666
  prefix_options = options.slice(*_route.segment_keys)
655
- prefix_options[:relative_url_root] = ""
667
+ prefix_options[:script_name] = "" if options[:original_script_name]
656
668
 
657
669
  if options[:_recall]
658
670
  prefix_options.reverse_merge!(options[:_recall].slice(*_route.segment_keys))
@@ -748,7 +760,7 @@ module ActionDispatch
748
760
  # end
749
761
  #
750
762
  # This will create a number of routes for each of the posts and comments
751
- # controller. For <tt>Admin::PostsController</tt>, Rails will create:
763
+ # controller. For +Admin::PostsController+, \Rails will create:
752
764
  #
753
765
  # GET /admin/posts
754
766
  # GET /admin/posts/new
@@ -759,7 +771,7 @@ module ActionDispatch
759
771
  # DELETE /admin/posts/1
760
772
  #
761
773
  # If you want to route /posts (without the prefix /admin) to
762
- # <tt>Admin::PostsController</tt>, you could use
774
+ # +Admin::PostsController+, you could use
763
775
  #
764
776
  # scope module: "admin" do
765
777
  # resources :posts
@@ -808,7 +820,7 @@ module ActionDispatch
808
820
  #
809
821
  # Takes same options as <tt>Base#match</tt> and <tt>Resources#resources</tt>.
810
822
  #
811
- # # route /posts (without the prefix /admin) to <tt>Admin::PostsController</tt>
823
+ # # route /posts (without the prefix /admin) to +Admin::PostsController+
812
824
  # scope module: "admin" do
813
825
  # resources :posts
814
826
  # end
@@ -917,7 +929,7 @@ module ActionDispatch
917
929
  # resources :posts
918
930
  # end
919
931
  #
920
- # # maps to <tt>Sekret::PostsController</tt> rather than <tt>Admin::PostsController</tt>
932
+ # # maps to +Sekret::PostsController+ rather than +Admin::PostsController+
921
933
  # namespace :admin, module: "sekret" do
922
934
  # resources :posts
923
935
  # end
@@ -1318,7 +1330,7 @@ module ActionDispatch
1318
1330
  self
1319
1331
  end
1320
1332
 
1321
- # In Rails, a resourceful route provides a mapping between HTTP verbs
1333
+ # In \Rails, a resourceful route provides a mapping between HTTP verbs
1322
1334
  # and URLs and controller actions. By convention, each action also maps
1323
1335
  # to particular CRUD operations in a database. A single entry in the
1324
1336
  # routing file, such as
@@ -1450,7 +1462,7 @@ module ActionDispatch
1450
1462
  #
1451
1463
  # === Examples
1452
1464
  #
1453
- # # routes call <tt>Admin::PostsController</tt>
1465
+ # # routes call +Admin::PostsController+
1454
1466
  # resources :posts, module: "admin"
1455
1467
  #
1456
1468
  # # resource actions are at /admin/posts.
@@ -1493,7 +1505,7 @@ module ActionDispatch
1493
1505
  # end
1494
1506
  # end
1495
1507
  #
1496
- # This will enable Rails to recognize paths such as <tt>/photos/search</tt>
1508
+ # This will enable \Rails to recognize paths such as <tt>/photos/search</tt>
1497
1509
  # with GET, and route to the search action of +PhotosController+. It will also
1498
1510
  # create the <tt>search_photos_url</tt> and <tt>search_photos_path</tt>
1499
1511
  # route helpers.
@@ -1640,7 +1652,7 @@ module ActionDispatch
1640
1652
  when Symbol
1641
1653
  options[:action] = to
1642
1654
  when String
1643
- if /#/.match?(to)
1655
+ if to.include?("#")
1644
1656
  options[:to] = to
1645
1657
  else
1646
1658
  options[:controller] = to
@@ -1663,7 +1675,7 @@ module ActionDispatch
1663
1675
  end
1664
1676
  end
1665
1677
 
1666
- # You can specify what Rails should route "/" to with the root method:
1678
+ # You can specify what \Rails should route "/" to with the root method:
1667
1679
  #
1668
1680
  # root to: 'pages#main'
1669
1681
  #
@@ -1675,7 +1687,7 @@ module ActionDispatch
1675
1687
  #
1676
1688
  # You should put the root route at the top of <tt>config/routes.rb</tt>,
1677
1689
  # because this means it will be matched first. As this is the most popular route
1678
- # of most Rails applications, this is beneficial.
1690
+ # of most \Rails applications, this is beneficial.
1679
1691
  def root(path, options = {})
1680
1692
  if path.is_a?(String)
1681
1693
  options[:to] = path
@@ -2,6 +2,8 @@
2
2
 
3
3
  module ActionDispatch
4
4
  module Routing
5
+ # = Action Dispatch Routing \PolymorphicRoutes
6
+ #
5
7
  # Polymorphic URL helpers are methods for smart resolution to a named route call when
6
8
  # given an Active Record model instance. They are to be used in combination with
7
9
  # ActionController::Resources.
@@ -18,10 +18,19 @@ module ActionDispatch
18
18
  def redirect?; true; end
19
19
 
20
20
  def call(env)
21
- serve Request.new env
21
+ ActiveSupport::Notifications.instrument("redirect.action_dispatch") do |payload|
22
+ request = Request.new(env)
23
+ response = build_response(request)
24
+
25
+ payload[:status] = @status
26
+ payload[:location] = response.headers["Location"]
27
+ payload[:request] = request
28
+
29
+ response.to_a
30
+ end
22
31
  end
23
32
 
24
- def serve(req)
33
+ def build_response(req)
25
34
  uri = URI.parse(path(req.path_parameters, req))
26
35
 
27
36
  unless uri.host
@@ -38,15 +47,15 @@ module ActionDispatch
38
47
 
39
48
  req.commit_flash
40
49
 
41
- body = %(<html><body>You are being <a href="#{ERB::Util.unwrapped_html_escape(uri.to_s)}">redirected</a>.</body></html>)
50
+ body = ""
42
51
 
43
52
  headers = {
44
53
  "Location" => uri.to_s,
45
- "Content-Type" => "text/html",
54
+ "Content-Type" => "text/html; charset=#{ActionDispatch::Response.default_charset}",
46
55
  "Content-Length" => body.length.to_s
47
56
  }
48
57
 
49
- [ status, headers, [body] ]
58
+ ActionDispatch::Response.new(status, headers, body)
50
59
  end
51
60
 
52
61
  def path(params, request)
@@ -59,7 +68,7 @@ module ActionDispatch
59
68
 
60
69
  private
61
70
  def relative_path?(path)
62
- path && !path.empty? && path[0] != "/"
71
+ path && !path.empty? && !path.start_with?("/")
63
72
  end
64
73
 
65
74
  def escape(params)