actionpack 7.0.8 → 7.1.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +360 -353
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/lib/abstract_controller/base.rb +20 -11
  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 +7 -4
  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 +17 -8
  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 +4 -2
  35. data/lib/action_controller/metal/permissions_policy.rb +1 -1
  36. data/lib/action_controller/metal/redirecting.rb +7 -7
  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 +139 -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 +132 -52
  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 +11 -5
  54. data/lib/action_dispatch/http/headers.rb +2 -0
  55. data/lib/action_dispatch/http/mime_negotiation.rb +22 -22
  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 +40 -18
  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 +80 -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 +9 -8
  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 +186 -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 +35 -24
  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 +10 -15
  117. data/lib/action_dispatch/routing/url_for.rb +5 -1
  118. data/lib/action_dispatch/routing.rb +7 -7
  119. data/lib/action_dispatch/system_test_case.rb +3 -3
  120. data/lib/action_dispatch/system_testing/browser.rb +20 -19
  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/assertion_response.rb +1 -1
  124. data/lib/action_dispatch/testing/assertions/response.rb +13 -6
  125. data/lib/action_dispatch/testing/assertions/routing.rb +67 -28
  126. data/lib/action_dispatch/testing/assertions.rb +3 -1
  127. data/lib/action_dispatch/testing/integration.rb +27 -17
  128. data/lib/action_dispatch/testing/request_encoder.rb +4 -1
  129. data/lib/action_dispatch/testing/test_process.rb +4 -3
  130. data/lib/action_dispatch/testing/test_request.rb +1 -1
  131. data/lib/action_dispatch/testing/test_response.rb +23 -9
  132. data/lib/action_dispatch.rb +37 -4
  133. data/lib/action_pack/gem_version.rb +4 -4
  134. data/lib/action_pack/version.rb +1 -1
  135. data/lib/action_pack.rb +1 -1
  136. metadata +64 -28
@@ -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
@@ -214,9 +217,16 @@ module ActionDispatch
214
217
  if to.respond_to?(:action) || to.respond_to?(:call)
215
218
  options
216
219
  else
217
- to_endpoint = split_to to
218
- controller = to_endpoint[0] || default_controller
219
- action = to_endpoint[1] || default_action
220
+ if to.nil?
221
+ controller = default_controller
222
+ action = default_action
223
+ elsif to.is_a?(String) && to.include?("#")
224
+ to_endpoint = to.split("#").map!(&:-@)
225
+ controller = to_endpoint[0]
226
+ action = to_endpoint[1]
227
+ else
228
+ raise ArgumentError, ":to must respond to `action` or `call`, or it must be a String that includes '#'"
229
+ end
220
230
 
221
231
  controller = add_controller_module(controller, modyoule)
222
232
 
@@ -305,14 +315,6 @@ module ActionDispatch
305
315
  hash
306
316
  end
307
317
 
308
- def split_to(to)
309
- if /#/.match?(to)
310
- to.split("#").map!(&:-@)
311
- else
312
- []
313
- end
314
- end
315
-
316
318
  def add_controller_module(controller, modyoule)
317
319
  if modyoule && !controller.is_a?(Regexp)
318
320
  if controller&.start_with?("/")
@@ -356,6 +358,15 @@ module ActionDispatch
356
358
  def dispatcher(raise_on_name_error)
357
359
  Routing::RouteSet::Dispatcher.new raise_on_name_error
358
360
  end
361
+
362
+ def route_source_location
363
+ if Mapper.route_source_locations
364
+ action_dispatch_dir = File.expand_path("..", __dir__)
365
+ caller_location = caller_locations.find { |location| !location.path.include?(action_dispatch_dir) }
366
+ cleaned_path = Mapper.backtrace_cleaner.clean([caller_location.path]).first
367
+ "#{cleaned_path}:#{caller_location.lineno}" if cleaned_path
368
+ end
369
+ end
359
370
  end
360
371
 
361
372
  # Invokes Journey::Router::Utils.normalize_path, then ensures that
@@ -652,7 +663,7 @@ module ActionDispatch
652
663
 
653
664
  script_namer = ->(options) do
654
665
  prefix_options = options.slice(*_route.segment_keys)
655
- prefix_options[:relative_url_root] = ""
666
+ prefix_options[:script_name] = "" if options[:original_script_name]
656
667
 
657
668
  if options[:_recall]
658
669
  prefix_options.reverse_merge!(options[:_recall].slice(*_route.segment_keys))
@@ -748,7 +759,7 @@ module ActionDispatch
748
759
  # end
749
760
  #
750
761
  # This will create a number of routes for each of the posts and comments
751
- # controller. For <tt>Admin::PostsController</tt>, Rails will create:
762
+ # controller. For +Admin::PostsController+, \Rails will create:
752
763
  #
753
764
  # GET /admin/posts
754
765
  # GET /admin/posts/new
@@ -759,7 +770,7 @@ module ActionDispatch
759
770
  # DELETE /admin/posts/1
760
771
  #
761
772
  # If you want to route /posts (without the prefix /admin) to
762
- # <tt>Admin::PostsController</tt>, you could use
773
+ # +Admin::PostsController+, you could use
763
774
  #
764
775
  # scope module: "admin" do
765
776
  # resources :posts
@@ -808,7 +819,7 @@ module ActionDispatch
808
819
  #
809
820
  # Takes same options as <tt>Base#match</tt> and <tt>Resources#resources</tt>.
810
821
  #
811
- # # route /posts (without the prefix /admin) to <tt>Admin::PostsController</tt>
822
+ # # route /posts (without the prefix /admin) to +Admin::PostsController+
812
823
  # scope module: "admin" do
813
824
  # resources :posts
814
825
  # end
@@ -917,7 +928,7 @@ module ActionDispatch
917
928
  # resources :posts
918
929
  # end
919
930
  #
920
- # # maps to <tt>Sekret::PostsController</tt> rather than <tt>Admin::PostsController</tt>
931
+ # # maps to +Sekret::PostsController+ rather than +Admin::PostsController+
921
932
  # namespace :admin, module: "sekret" do
922
933
  # resources :posts
923
934
  # end
@@ -1318,7 +1329,7 @@ module ActionDispatch
1318
1329
  self
1319
1330
  end
1320
1331
 
1321
- # In Rails, a resourceful route provides a mapping between HTTP verbs
1332
+ # In \Rails, a resourceful route provides a mapping between HTTP verbs
1322
1333
  # and URLs and controller actions. By convention, each action also maps
1323
1334
  # to particular CRUD operations in a database. A single entry in the
1324
1335
  # routing file, such as
@@ -1450,7 +1461,7 @@ module ActionDispatch
1450
1461
  #
1451
1462
  # === Examples
1452
1463
  #
1453
- # # routes call <tt>Admin::PostsController</tt>
1464
+ # # routes call +Admin::PostsController+
1454
1465
  # resources :posts, module: "admin"
1455
1466
  #
1456
1467
  # # resource actions are at /admin/posts.
@@ -1493,7 +1504,7 @@ module ActionDispatch
1493
1504
  # end
1494
1505
  # end
1495
1506
  #
1496
- # This will enable Rails to recognize paths such as <tt>/photos/search</tt>
1507
+ # This will enable \Rails to recognize paths such as <tt>/photos/search</tt>
1497
1508
  # with GET, and route to the search action of +PhotosController+. It will also
1498
1509
  # create the <tt>search_photos_url</tt> and <tt>search_photos_path</tt>
1499
1510
  # route helpers.
@@ -1640,7 +1651,7 @@ module ActionDispatch
1640
1651
  when Symbol
1641
1652
  options[:action] = to
1642
1653
  when String
1643
- if /#/.match?(to)
1654
+ if to.include?("#")
1644
1655
  options[:to] = to
1645
1656
  else
1646
1657
  options[:controller] = to
@@ -1663,7 +1674,7 @@ module ActionDispatch
1663
1674
  end
1664
1675
  end
1665
1676
 
1666
- # You can specify what Rails should route "/" to with the root method:
1677
+ # You can specify what \Rails should route "/" to with the root method:
1667
1678
  #
1668
1679
  # root to: 'pages#main'
1669
1680
  #
@@ -1675,7 +1686,7 @@ module ActionDispatch
1675
1686
  #
1676
1687
  # You should put the root route at the top of <tt>config/routes.rb</tt>,
1677
1688
  # because this means it will be matched first. As this is the most popular route
1678
- # of most Rails applications, this is beneficial.
1689
+ # of most \Rails applications, this is beneficial.
1679
1690
  def root(path, options = {})
1680
1691
  if path.is_a?(String)
1681
1692
  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)