actionpack 7.0.8.7 → 7.1.5.1

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.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +423 -342
  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 -24
  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 +13 -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 +174 -54
  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 +18 -8
  48. data/lib/action_controller.rb +13 -3
  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 +37 -11
  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 +38 -16
  60. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  61. data/lib/action_dispatch/http/request.rb +70 -16
  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 +7 -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 +33 -19
  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 +13 -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 +74 -26
  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 +53 -23
  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 +25 -19
  121. data/lib/action_dispatch/system_testing/driver.rb +14 -22
  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 +41 -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 +62 -26
@@ -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,8 @@ 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
+
64
73
  ActionDispatch.test_app = app
65
74
  end
66
75
  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 = RFC2396_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
@@ -10,8 +10,20 @@ require "action_dispatch/routing/endpoint"
10
10
  module ActionDispatch
11
11
  module Routing
12
12
  class Mapper
13
+ class BacktraceCleaner < ActiveSupport::BacktraceCleaner # :nodoc:
14
+ def initialize
15
+ super
16
+ remove_silencers!
17
+ add_core_silencer
18
+ add_stdlib_silencer
19
+ end
20
+ end
21
+
13
22
  URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
14
23
 
24
+ cattr_accessor :route_source_locations, instance_accessor: false, default: false
25
+ cattr_accessor :backtrace_cleaner, instance_accessor: false, default: BacktraceCleaner.new
26
+
15
27
  class Constraints < Routing::Endpoint # :nodoc:
16
28
  attr_reader :app, :constraints
17
29
 
@@ -43,7 +55,7 @@ module ActionDispatch
43
55
  end
44
56
 
45
57
  def serve(req)
46
- return [ 404, { "X-Cascade" => "pass" }, [] ] unless matches?(req)
58
+ return [ 404, { Constants::X_CASCADE => "pass" }, [] ] unless matches?(req)
47
59
 
48
60
  @strategy.call @app, req
49
61
  end
@@ -170,7 +182,7 @@ module ActionDispatch
170
182
  Journey::Route.new(name: name, app: application, path: path, constraints: conditions,
171
183
  required_defaults: required_defaults, defaults: defaults,
172
184
  request_method_match: request_method, precedence: precedence,
173
- scope_options: scope_options, internal: @internal)
185
+ scope_options: scope_options, internal: @internal, source_location: route_source_location)
174
186
  end
175
187
 
176
188
  def application
@@ -214,9 +226,21 @@ module ActionDispatch
214
226
  if to.respond_to?(:action) || to.respond_to?(:call)
215
227
  options
216
228
  else
217
- to_endpoint = split_to to
218
- controller = to_endpoint[0] || default_controller
219
- action = to_endpoint[1] || default_action
229
+ if to.nil?
230
+ controller = default_controller
231
+ action = default_action
232
+ elsif to.is_a?(String)
233
+ if to.include?("#")
234
+ to_endpoint = to.split("#").map!(&:-@)
235
+ controller = to_endpoint[0]
236
+ action = to_endpoint[1]
237
+ else
238
+ controller = default_controller
239
+ action = to
240
+ end
241
+ else
242
+ raise ArgumentError, ":to must respond to `action` or `call`, or it must be a String that includes '#', or the controller should be implicit"
243
+ end
220
244
 
221
245
  controller = add_controller_module(controller, modyoule)
222
246
 
@@ -305,14 +329,6 @@ module ActionDispatch
305
329
  hash
306
330
  end
307
331
 
308
- def split_to(to)
309
- if /#/.match?(to)
310
- to.split("#").map!(&:-@)
311
- else
312
- []
313
- end
314
- end
315
-
316
332
  def add_controller_module(controller, modyoule)
317
333
  if modyoule && !controller.is_a?(Regexp)
318
334
  if controller&.start_with?("/")
@@ -356,6 +372,38 @@ module ActionDispatch
356
372
  def dispatcher(raise_on_name_error)
357
373
  Routing::RouteSet::Dispatcher.new raise_on_name_error
358
374
  end
375
+
376
+ if Thread.respond_to?(:each_caller_location)
377
+ def route_source_location
378
+ if Mapper.route_source_locations
379
+ action_dispatch_dir = File.expand_path("..", __dir__)
380
+ Thread.each_caller_location do |location|
381
+ next if location.path.start_with?(action_dispatch_dir)
382
+
383
+ cleaned_path = Mapper.backtrace_cleaner.clean_frame(location.path)
384
+ next if cleaned_path.nil?
385
+
386
+ return "#{cleaned_path}:#{location.lineno}"
387
+ end
388
+ nil
389
+ end
390
+ end
391
+ else
392
+ def route_source_location
393
+ if Mapper.route_source_locations
394
+ action_dispatch_dir = File.expand_path("..", __dir__)
395
+ caller_locations.each do |location|
396
+ next if location.path.start_with?(action_dispatch_dir)
397
+
398
+ cleaned_path = Mapper.backtrace_cleaner.clean_frame(location.path)
399
+ next if cleaned_path.nil?
400
+
401
+ return "#{cleaned_path}:#{location.lineno}"
402
+ end
403
+ nil
404
+ end
405
+ end
406
+ end
359
407
  end
360
408
 
361
409
  # Invokes Journey::Router::Utils.normalize_path, then ensures that
@@ -652,7 +700,7 @@ module ActionDispatch
652
700
 
653
701
  script_namer = ->(options) do
654
702
  prefix_options = options.slice(*_route.segment_keys)
655
- prefix_options[:relative_url_root] = ""
703
+ prefix_options[:script_name] = "" if options[:original_script_name]
656
704
 
657
705
  if options[:_recall]
658
706
  prefix_options.reverse_merge!(options[:_recall].slice(*_route.segment_keys))
@@ -669,7 +717,7 @@ module ActionDispatch
669
717
  def optimize_routes_generation?; false; end
670
718
 
671
719
  define_method :find_script_name do |options|
672
- if options.key? :script_name
720
+ if options.key?(:script_name) && options[:script_name].present?
673
721
  super(options)
674
722
  else
675
723
  script_namer.call(options)
@@ -748,7 +796,7 @@ module ActionDispatch
748
796
  # end
749
797
  #
750
798
  # This will create a number of routes for each of the posts and comments
751
- # controller. For <tt>Admin::PostsController</tt>, Rails will create:
799
+ # controller. For +Admin::PostsController+, \Rails will create:
752
800
  #
753
801
  # GET /admin/posts
754
802
  # GET /admin/posts/new
@@ -759,7 +807,7 @@ module ActionDispatch
759
807
  # DELETE /admin/posts/1
760
808
  #
761
809
  # If you want to route /posts (without the prefix /admin) to
762
- # <tt>Admin::PostsController</tt>, you could use
810
+ # +Admin::PostsController+, you could use
763
811
  #
764
812
  # scope module: "admin" do
765
813
  # resources :posts
@@ -808,7 +856,7 @@ module ActionDispatch
808
856
  #
809
857
  # Takes same options as <tt>Base#match</tt> and <tt>Resources#resources</tt>.
810
858
  #
811
- # # route /posts (without the prefix /admin) to <tt>Admin::PostsController</tt>
859
+ # # route /posts (without the prefix /admin) to +Admin::PostsController+
812
860
  # scope module: "admin" do
813
861
  # resources :posts
814
862
  # end
@@ -917,7 +965,7 @@ module ActionDispatch
917
965
  # resources :posts
918
966
  # end
919
967
  #
920
- # # maps to <tt>Sekret::PostsController</tt> rather than <tt>Admin::PostsController</tt>
968
+ # # maps to +Sekret::PostsController+ rather than +Admin::PostsController+
921
969
  # namespace :admin, module: "sekret" do
922
970
  # resources :posts
923
971
  # end
@@ -1318,7 +1366,7 @@ module ActionDispatch
1318
1366
  self
1319
1367
  end
1320
1368
 
1321
- # In Rails, a resourceful route provides a mapping between HTTP verbs
1369
+ # In \Rails, a resourceful route provides a mapping between HTTP verbs
1322
1370
  # and URLs and controller actions. By convention, each action also maps
1323
1371
  # to particular CRUD operations in a database. A single entry in the
1324
1372
  # routing file, such as
@@ -1450,7 +1498,7 @@ module ActionDispatch
1450
1498
  #
1451
1499
  # === Examples
1452
1500
  #
1453
- # # routes call <tt>Admin::PostsController</tt>
1501
+ # # routes call +Admin::PostsController+
1454
1502
  # resources :posts, module: "admin"
1455
1503
  #
1456
1504
  # # resource actions are at /admin/posts.
@@ -1493,7 +1541,7 @@ module ActionDispatch
1493
1541
  # end
1494
1542
  # end
1495
1543
  #
1496
- # This will enable Rails to recognize paths such as <tt>/photos/search</tt>
1544
+ # This will enable \Rails to recognize paths such as <tt>/photos/search</tt>
1497
1545
  # with GET, and route to the search action of +PhotosController+. It will also
1498
1546
  # create the <tt>search_photos_url</tt> and <tt>search_photos_path</tt>
1499
1547
  # route helpers.
@@ -1640,7 +1688,7 @@ module ActionDispatch
1640
1688
  when Symbol
1641
1689
  options[:action] = to
1642
1690
  when String
1643
- if /#/.match?(to)
1691
+ if to.include?("#")
1644
1692
  options[:to] = to
1645
1693
  else
1646
1694
  options[:controller] = to
@@ -1663,7 +1711,7 @@ module ActionDispatch
1663
1711
  end
1664
1712
  end
1665
1713
 
1666
- # You can specify what Rails should route "/" to with the root method:
1714
+ # You can specify what \Rails should route "/" to with the root method:
1667
1715
  #
1668
1716
  # root to: 'pages#main'
1669
1717
  #
@@ -1675,7 +1723,7 @@ module ActionDispatch
1675
1723
  #
1676
1724
  # You should put the root route at the top of <tt>config/routes.rb</tt>,
1677
1725
  # because this means it will be matched first. As this is the most popular route
1678
- # of most Rails applications, this is beneficial.
1726
+ # of most \Rails applications, this is beneficial.
1679
1727
  def root(path, options = {})
1680
1728
  if path.is_a?(String)
1681
1729
  options[:to] = path
@@ -1978,7 +2026,7 @@ module ActionDispatch
1978
2026
  name_for_action(options.delete(:as), action)
1979
2027
  end
1980
2028
 
1981
- path = Mapping.normalize_path URI::DEFAULT_PARSER.escape(path), formatted
2029
+ path = Mapping.normalize_path RFC2396_PARSER.escape(path), formatted
1982
2030
  ast = Journey::Parser.parse path
1983
2031
 
1984
2032
  mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
@@ -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.