actionpack 7.2.2.1 → 8.0.5

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +228 -101
  3. data/README.rdoc +1 -1
  4. data/lib/abstract_controller/base.rb +1 -12
  5. data/lib/abstract_controller/collector.rb +1 -1
  6. data/lib/abstract_controller/helpers.rb +1 -1
  7. data/lib/abstract_controller/rendering.rb +0 -1
  8. data/lib/action_controller/base.rb +1 -1
  9. data/lib/action_controller/form_builder.rb +3 -3
  10. data/lib/action_controller/metal/allow_browser.rb +11 -1
  11. data/lib/action_controller/metal/conditional_get.rb +5 -1
  12. data/lib/action_controller/metal/data_streaming.rb +4 -2
  13. data/lib/action_controller/metal/instrumentation.rb +1 -2
  14. data/lib/action_controller/metal/live.rb +59 -11
  15. data/lib/action_controller/metal/params_wrapper.rb +3 -3
  16. data/lib/action_controller/metal/rate_limiting.rb +13 -4
  17. data/lib/action_controller/metal/redirecting.rb +4 -3
  18. data/lib/action_controller/metal/renderers.rb +2 -3
  19. data/lib/action_controller/metal/rendering.rb +1 -1
  20. data/lib/action_controller/metal/request_forgery_protection.rb +3 -1
  21. data/lib/action_controller/metal/streaming.rb +5 -84
  22. data/lib/action_controller/metal/strong_parameters.rb +277 -92
  23. data/lib/action_controller/railtie.rb +6 -7
  24. data/lib/action_controller/renderer.rb +0 -1
  25. data/lib/action_controller/test_case.rb +12 -2
  26. data/lib/action_dispatch/constants.rb +6 -0
  27. data/lib/action_dispatch/http/cache.rb +27 -10
  28. data/lib/action_dispatch/http/content_security_policy.rb +14 -1
  29. data/lib/action_dispatch/http/mime_negotiation.rb +8 -3
  30. data/lib/action_dispatch/http/param_builder.rb +186 -0
  31. data/lib/action_dispatch/http/param_error.rb +26 -0
  32. data/lib/action_dispatch/http/permissions_policy.rb +2 -0
  33. data/lib/action_dispatch/http/query_parser.rb +53 -0
  34. data/lib/action_dispatch/http/request.rb +64 -19
  35. data/lib/action_dispatch/http/response.rb +49 -14
  36. data/lib/action_dispatch/http/url.rb +2 -2
  37. data/lib/action_dispatch/journey/formatter.rb +8 -3
  38. data/lib/action_dispatch/journey/gtg/transition_table.rb +4 -4
  39. data/lib/action_dispatch/journey/parser.rb +99 -196
  40. data/lib/action_dispatch/journey/scanner.rb +44 -42
  41. data/lib/action_dispatch/middleware/cookies.rb +4 -2
  42. data/lib/action_dispatch/middleware/debug_exceptions.rb +19 -4
  43. data/lib/action_dispatch/middleware/debug_view.rb +0 -5
  44. data/lib/action_dispatch/middleware/exception_wrapper.rb +3 -9
  45. data/lib/action_dispatch/middleware/executor.rb +5 -2
  46. data/lib/action_dispatch/middleware/public_exceptions.rb +5 -1
  47. data/lib/action_dispatch/middleware/request_id.rb +2 -1
  48. data/lib/action_dispatch/middleware/ssl.rb +13 -3
  49. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +0 -3
  50. data/lib/action_dispatch/railtie.rb +8 -0
  51. data/lib/action_dispatch/request/session.rb +1 -0
  52. data/lib/action_dispatch/request/utils.rb +9 -3
  53. data/lib/action_dispatch/routing/inspector.rb +1 -1
  54. data/lib/action_dispatch/routing/mapper.rb +96 -67
  55. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -2
  56. data/lib/action_dispatch/routing/route_set.rb +21 -10
  57. data/lib/action_dispatch/routing/routes_proxy.rb +1 -0
  58. data/lib/action_dispatch/system_testing/browser.rb +12 -21
  59. data/lib/action_dispatch/testing/assertion_response.rb +1 -1
  60. data/lib/action_dispatch/testing/assertions/response.rb +12 -2
  61. data/lib/action_dispatch/testing/assertions/routing.rb +16 -12
  62. data/lib/action_dispatch/testing/integration.rb +20 -10
  63. data/lib/action_dispatch/testing/request_encoder.rb +9 -9
  64. data/lib/action_dispatch/testing/test_process.rb +1 -2
  65. data/lib/action_dispatch.rb +6 -4
  66. data/lib/action_pack/gem_version.rb +4 -4
  67. metadata +16 -38
  68. data/lib/action_dispatch/journey/parser.y +0 -50
  69. data/lib/action_dispatch/journey/parser_extras.rb +0 -33
@@ -29,6 +29,10 @@ module ActionDispatch
29
29
  config.action_dispatch.request_id_header = ActionDispatch::Constants::X_REQUEST_ID
30
30
  config.action_dispatch.log_rescued_responses = true
31
31
  config.action_dispatch.debug_exception_log_level = :fatal
32
+ config.action_dispatch.strict_freshness = false
33
+
34
+ config.action_dispatch.ignore_leading_brackets = nil
35
+ config.action_dispatch.strict_query_string_separator = nil
32
36
 
33
37
  config.action_dispatch.default_headers = {
34
38
  "X-Frame-Options" => "SAMEORIGIN",
@@ -51,6 +55,9 @@ module ActionDispatch
51
55
  ActionDispatch::Http::URL.secure_protocol = app.config.force_ssl
52
56
  ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
53
57
 
58
+ ActionDispatch::ParamBuilder.ignore_leading_brackets = app.config.action_dispatch.ignore_leading_brackets
59
+ ActionDispatch::QueryParser.strict_query_string_separator = app.config.action_dispatch.strict_query_string_separator
60
+
54
61
  ActiveSupport.on_load(:action_dispatch_request) do
55
62
  self.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
56
63
  ActionDispatch::Request::Utils.perform_deep_munge = app.config.action_dispatch.perform_deep_munge
@@ -69,6 +76,7 @@ module ActionDispatch
69
76
 
70
77
  ActionDispatch::Routing::Mapper.route_source_locations = Rails.env.development?
71
78
 
79
+ ActionDispatch::Http::Cache::Request.strict_freshness = app.config.action_dispatch.strict_freshness
72
80
  ActionDispatch.test_app = app
73
81
  end
74
82
  end
@@ -155,6 +155,7 @@ module ActionDispatch
155
155
  load_for_write!
156
156
  @delegate[key.to_s] = value
157
157
  end
158
+ alias store []=
158
159
 
159
160
  # Clears the session.
160
161
  def clear
@@ -83,8 +83,8 @@ module ActionDispatch
83
83
  end
84
84
 
85
85
  class CustomParamEncoder # :nodoc:
86
- def self.encode(request, params, controller, action)
87
- return params unless controller && controller.valid_encoding? && encoding_template = action_encoding_template(request, controller, action)
86
+ def self.encode_for_template(params, encoding_template)
87
+ return params unless encoding_template
88
88
  params.except(:controller, :action).each do |key, value|
89
89
  ActionDispatch::Request::Utils.each_param_value(value) do |param|
90
90
  # If `param` is frozen, it comes from the router defaults
@@ -98,8 +98,14 @@ module ActionDispatch
98
98
  params
99
99
  end
100
100
 
101
+ def self.encode(request, params, controller, action)
102
+ encoding_template = action_encoding_template(request, controller, action)
103
+ encode_for_template(params, encoding_template)
104
+ end
105
+
101
106
  def self.action_encoding_template(request, controller, action) # :nodoc:
102
- request.controller_class_for(controller).action_encoding_template(action)
107
+ controller && controller.valid_encoding? &&
108
+ request.controller_class_for(controller).action_encoding_template(action)
103
109
  rescue MissingController
104
110
  nil
105
111
  end
@@ -101,7 +101,7 @@ module ActionDispatch
101
101
  { controller: /#{filter[:controller].underscore.sub(/_?controller\z/, "")}/ }
102
102
  elsif filter[:grep]
103
103
  grep_pattern = Regexp.new(filter[:grep])
104
- path = RFC2396_PARSER.escape(filter[:grep])
104
+ path = URI::RFC2396_PARSER.escape(filter[:grep])
105
105
  normalized_path = ("/" + path).squeeze("/")
106
106
 
107
107
  {
@@ -375,35 +375,18 @@ module ActionDispatch
375
375
  Routing::RouteSet::Dispatcher.new raise_on_name_error
376
376
  end
377
377
 
378
- if Thread.respond_to?(:each_caller_location)
379
- def route_source_location
380
- if Mapper.route_source_locations
381
- action_dispatch_dir = File.expand_path("..", __dir__)
382
- Thread.each_caller_location do |location|
383
- next if location.path.start_with?(action_dispatch_dir)
378
+ def route_source_location
379
+ if Mapper.route_source_locations
380
+ action_dispatch_dir = File.expand_path("..", __dir__)
381
+ Thread.each_caller_location do |location|
382
+ next if location.path.start_with?(action_dispatch_dir)
384
383
 
385
- cleaned_path = Mapper.backtrace_cleaner.clean_frame(location.path)
386
- next if cleaned_path.nil?
384
+ cleaned_path = Mapper.backtrace_cleaner.clean_frame(location.path)
385
+ next if cleaned_path.nil?
387
386
 
388
- return "#{cleaned_path}:#{location.lineno}"
389
- end
390
- nil
391
- end
392
- end
393
- else
394
- def route_source_location
395
- if Mapper.route_source_locations
396
- action_dispatch_dir = File.expand_path("..", __dir__)
397
- caller_locations.each do |location|
398
- next if location.path.start_with?(action_dispatch_dir)
399
-
400
- cleaned_path = Mapper.backtrace_cleaner.clean_frame(location.path)
401
- next if cleaned_path.nil?
402
-
403
- return "#{cleaned_path}:#{location.lineno}"
404
- end
405
- nil
387
+ return "#{cleaned_path}:#{location.lineno}"
406
388
  end
389
+ nil
407
390
  end
408
391
  end
409
392
  end
@@ -470,7 +453,6 @@ module ActionDispatch
470
453
  # When a pattern points to an internal route, the route's `:action` and
471
454
  # `:controller` should be set in options or hash shorthand. Examples:
472
455
  #
473
- # match 'photos/:id' => 'photos#show', via: :get
474
456
  # match 'photos/:id', to: 'photos#show', via: :get
475
457
  # match 'photos/:id', controller: 'photos', action: 'show', via: :get
476
458
  #
@@ -614,10 +596,6 @@ module ActionDispatch
614
596
  #
615
597
  # mount SomeRackApp, at: "some_route"
616
598
  #
617
- # Alternatively:
618
- #
619
- # mount(SomeRackApp => "some_route")
620
- #
621
599
  # For options, see `match`, as `mount` uses it internally.
622
600
  #
623
601
  # All mounted applications come with routing helpers to access them. These are
@@ -625,7 +603,7 @@ module ActionDispatch
625
603
  # `some_rack_app_path` or `some_rack_app_url`. To customize this helper's name,
626
604
  # use the `:as` option:
627
605
  #
628
- # mount(SomeRackApp => "some_route", as: "exciting")
606
+ # mount(SomeRackApp, at: "some_route", as: "exciting")
629
607
  #
630
608
  # This will generate the `exciting_path` and `exciting_url` helpers which can be
631
609
  # used to navigate to this mounted app.
@@ -773,6 +751,16 @@ module ActionDispatch
773
751
  map_method(:options, args, &block)
774
752
  end
775
753
 
754
+ # Define a route that recognizes HTTP CONNECT (and GET) requests. More
755
+ # specifically this recognizes HTTP/1 protocol upgrade requests and HTTP/2
756
+ # CONNECT requests with the protocol pseudo header. For supported arguments,
757
+ # see [match](rdoc-ref:Base#match)
758
+ #
759
+ # connect 'live', to: 'live#index'
760
+ def connect(*args, &block)
761
+ map_method([:get, :connect], args, &block)
762
+ end
763
+
776
764
  private
777
765
  def map_method(method, args, &block)
778
766
  options = args.extract_options!
@@ -852,7 +840,7 @@ module ActionDispatch
852
840
  #
853
841
  # Takes same options as `Base#match` and `Resources#resources`.
854
842
  #
855
- # # route /posts (without the prefix /admin) to +Admin::PostsController+
843
+ # # route /posts (without the prefix /admin) to Admin::PostsController
856
844
  # scope module: "admin" do
857
845
  # resources :posts
858
846
  # end
@@ -862,7 +850,7 @@ module ActionDispatch
862
850
  # resources :posts
863
851
  # end
864
852
  #
865
- # # prefix the routing helper name: +sekret_posts_path+ instead of +posts_path+
853
+ # # prefix the routing helper name: sekret_posts_path instead of posts_path
866
854
  # scope as: "sekret" do
867
855
  # resources :posts
868
856
  # end
@@ -961,12 +949,12 @@ module ActionDispatch
961
949
  # resources :posts
962
950
  # end
963
951
  #
964
- # # maps to +Sekret::PostsController+ rather than +Admin::PostsController+
952
+ # # maps to Sekret::PostsController rather than Admin::PostsController
965
953
  # namespace :admin, module: "sekret" do
966
954
  # resources :posts
967
955
  # end
968
956
  #
969
- # # generates +sekret_posts_path+ rather than +admin_posts_path+
957
+ # # generates sekret_posts_path rather than admin_posts_path
970
958
  # namespace :admin, as: "sekret" do
971
959
  # resources :posts
972
960
  # end
@@ -1174,6 +1162,16 @@ module ActionDispatch
1174
1162
  CANONICAL_ACTIONS = %w(index create new show update destroy)
1175
1163
 
1176
1164
  class Resource # :nodoc:
1165
+ class << self
1166
+ def default_actions(api_only)
1167
+ if api_only
1168
+ [:index, :create, :show, :update, :destroy]
1169
+ else
1170
+ [:index, :create, :new, :show, :update, :destroy, :edit]
1171
+ end
1172
+ end
1173
+ end
1174
+
1177
1175
  attr_reader :controller, :path, :param
1178
1176
 
1179
1177
  def initialize(entities, api_only, shallow, options = {})
@@ -1181,6 +1179,12 @@ module ActionDispatch
1181
1179
  raise ArgumentError, ":param option can't contain colons"
1182
1180
  end
1183
1181
 
1182
+ valid_actions = self.class.default_actions(false) # ignore api_only for this validation
1183
+ if invalid_actions = invalid_only_except_options(options, valid_actions).presence
1184
+ error_prefix = "Route `resource#{"s" unless singleton?} :#{entities}`"
1185
+ raise ArgumentError, "#{error_prefix} - :only and :except must include only #{valid_actions}, but also included #{invalid_actions}"
1186
+ end
1187
+
1184
1188
  @name = entities.to_s
1185
1189
  @path = (options[:path] || @name).to_s
1186
1190
  @controller = (options[:controller] || @name).to_s
@@ -1194,11 +1198,7 @@ module ActionDispatch
1194
1198
  end
1195
1199
 
1196
1200
  def default_actions
1197
- if @api_only
1198
- [:index, :create, :show, :update, :destroy]
1199
- else
1200
- [:index, :create, :new, :show, :update, :destroy, :edit]
1201
- end
1201
+ self.class.default_actions(@api_only)
1202
1202
  end
1203
1203
 
1204
1204
  def actions
@@ -1266,9 +1266,24 @@ module ActionDispatch
1266
1266
  end
1267
1267
 
1268
1268
  def singleton?; false; end
1269
+
1270
+ private
1271
+ def invalid_only_except_options(options, valid_actions)
1272
+ options.values_at(:only, :except).flatten.compact.uniq.map(&:to_sym) - valid_actions
1273
+ end
1269
1274
  end
1270
1275
 
1271
1276
  class SingletonResource < Resource # :nodoc:
1277
+ class << self
1278
+ def default_actions(api_only)
1279
+ if api_only
1280
+ [:show, :create, :update, :destroy]
1281
+ else
1282
+ [:show, :create, :update, :destroy, :new, :edit]
1283
+ end
1284
+ end
1285
+ end
1286
+
1272
1287
  def initialize(entities, api_only, shallow, options)
1273
1288
  super
1274
1289
  @as = nil
@@ -1277,11 +1292,7 @@ module ActionDispatch
1277
1292
  end
1278
1293
 
1279
1294
  def default_actions
1280
- if @api_only
1281
- [:show, :create, :update, :destroy]
1282
- else
1283
- [:show, :create, :update, :destroy, :new, :edit]
1284
- end
1295
+ self.class.default_actions(@api_only)
1285
1296
  end
1286
1297
 
1287
1298
  def plural
@@ -1342,7 +1353,7 @@ module ActionDispatch
1342
1353
  end
1343
1354
 
1344
1355
  with_scope_level(:resource) do
1345
- options = apply_action_options options
1356
+ options = apply_action_options :resource, options
1346
1357
  resource_scope(SingletonResource.new(resources.pop, api_only?, @scope[:shallow], options)) do
1347
1358
  yield if block_given?
1348
1359
 
@@ -1499,7 +1510,7 @@ module ActionDispatch
1499
1510
  #
1500
1511
  # ### Examples
1501
1512
  #
1502
- # # routes call +Admin::PostsController+
1513
+ # # routes call Admin::PostsController
1503
1514
  # resources :posts, module: "admin"
1504
1515
  #
1505
1516
  # # resource actions are at /admin/posts.
@@ -1512,7 +1523,7 @@ module ActionDispatch
1512
1523
  end
1513
1524
 
1514
1525
  with_scope_level(:resources) do
1515
- options = apply_action_options options
1526
+ options = apply_action_options :resources, options
1516
1527
  resource_scope(Resource.new(resources.pop, api_only?, @scope[:shallow], options)) do
1517
1528
  yield if block_given?
1518
1529
 
@@ -1673,7 +1684,6 @@ module ActionDispatch
1673
1684
  # Matches a URL pattern to one or more routes. For more information, see
1674
1685
  # [match](rdoc-ref:Base#match).
1675
1686
  #
1676
- # match 'path' => 'controller#action', via: :patch
1677
1687
  # match 'path', to: 'controller#action', via: :post
1678
1688
  # match 'path', 'otherpath', on: :member, via: :get
1679
1689
  def match(path, *rest, &block)
@@ -1782,17 +1792,32 @@ module ActionDispatch
1782
1792
  false
1783
1793
  end
1784
1794
 
1785
- def apply_action_options(options)
1795
+ def apply_action_options(method, options)
1786
1796
  return options if action_options? options
1787
- options.merge scope_action_options
1797
+ options.merge scope_action_options(method)
1788
1798
  end
1789
1799
 
1790
1800
  def action_options?(options)
1791
1801
  options[:only] || options[:except]
1792
1802
  end
1793
1803
 
1794
- def scope_action_options
1795
- @scope[:action_options] || {}
1804
+ def scope_action_options(method)
1805
+ return {} unless @scope[:action_options]
1806
+
1807
+ actions = applicable_actions_for(method)
1808
+ @scope[:action_options].dup.tap do |options|
1809
+ (options[:only] = Array(options[:only]) & actions) if options[:only]
1810
+ (options[:except] = Array(options[:except]) & actions) if options[:except]
1811
+ end
1812
+ end
1813
+
1814
+ def applicable_actions_for(method)
1815
+ case method
1816
+ when :resource
1817
+ SingletonResource.default_actions(api_only?)
1818
+ when :resources
1819
+ Resource.default_actions(api_only?)
1820
+ end
1796
1821
  end
1797
1822
 
1798
1823
  def resource_scope?
@@ -1935,6 +1960,11 @@ module ActionDispatch
1935
1960
  end
1936
1961
 
1937
1962
  def map_match(paths, options)
1963
+ ActionDispatch.deprecator.warn(<<-MSG.squish) if paths.count > 1
1964
+ Mapping a route with multiple paths is deprecated and
1965
+ will be removed in Rails 8.1. Please use multiple method calls instead.
1966
+ MSG
1967
+
1938
1968
  if (on = options[:on]) && !VALID_ON_OPTIONS.include?(on)
1939
1969
  raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
1940
1970
  end
@@ -2025,7 +2055,7 @@ module ActionDispatch
2025
2055
  name_for_action(options.delete(:as), action)
2026
2056
  end
2027
2057
 
2028
- path = Mapping.normalize_path RFC2396_PARSER.escape(path), formatted
2058
+ path = Mapping.normalize_path URI::RFC2396_PARSER.escape(path), formatted
2029
2059
  ast = Journey::Parser.parse path
2030
2060
 
2031
2061
  mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
@@ -2200,8 +2230,8 @@ module ActionDispatch
2200
2230
  end
2201
2231
 
2202
2232
  # Define custom polymorphic mappings of models to URLs. This alters the behavior
2203
- # of `polymorphic_url` and consequently the behavior of `link_to` and `form_for`
2204
- # when passed a model instance, e.g:
2233
+ # of `polymorphic_url` and consequently the behavior of `link_to`, `form_with`
2234
+ # and `form_for` when passed a model instance, e.g:
2205
2235
  #
2206
2236
  # resource :basket
2207
2237
  #
@@ -2210,7 +2240,7 @@ module ActionDispatch
2210
2240
  # end
2211
2241
  #
2212
2242
  # This will now generate "/basket" when a `Basket` instance is passed to
2213
- # `link_to` or `form_for` instead of the standard "/baskets/:id".
2243
+ # `link_to`, `form_with` or `form_for` instead of the standard "/baskets/:id".
2214
2244
  #
2215
2245
  # NOTE: This custom behavior only applies to simple polymorphic URLs where a
2216
2246
  # single model instance is passed and not more complicated forms, e.g:
@@ -2267,9 +2297,9 @@ module ActionDispatch
2267
2297
 
2268
2298
  attr_reader :parent, :scope_level
2269
2299
 
2270
- def initialize(hash, parent = NULL, scope_level = nil)
2271
- @hash = hash
2300
+ def initialize(hash, parent = ROOT, scope_level = nil)
2272
2301
  @parent = parent
2302
+ @hash = parent ? parent.frame.merge(hash) : hash
2273
2303
  @scope_level = scope_level
2274
2304
  end
2275
2305
 
@@ -2282,7 +2312,7 @@ module ActionDispatch
2282
2312
  end
2283
2313
 
2284
2314
  def root?
2285
- @parent.null?
2315
+ @parent == ROOT
2286
2316
  end
2287
2317
 
2288
2318
  def resources?
@@ -2327,23 +2357,22 @@ module ActionDispatch
2327
2357
  end
2328
2358
 
2329
2359
  def [](key)
2330
- scope = find { |node| node.frame.key? key }
2331
- scope && scope.frame[key]
2360
+ frame[key]
2332
2361
  end
2333
2362
 
2363
+ def frame; @hash; end
2364
+
2334
2365
  include Enumerable
2335
2366
 
2336
2367
  def each
2337
2368
  node = self
2338
- until node.equal? NULL
2369
+ until node.equal? ROOT
2339
2370
  yield node
2340
2371
  node = node.parent
2341
2372
  end
2342
2373
  end
2343
2374
 
2344
- def frame; @hash; end
2345
-
2346
- NULL = Scope.new(nil, nil)
2375
+ ROOT = Scope.new({}, nil)
2347
2376
  end
2348
2377
 
2349
2378
  def initialize(set) # :nodoc:
@@ -31,7 +31,7 @@ module ActionDispatch
31
31
  # * `url_for`, so you can use it with a record as the argument, e.g.
32
32
  # `url_for(@article)`;
33
33
  # * ActionView::Helpers::FormHelper uses `polymorphic_path`, so you can write
34
- # `form_for(@article)` without having to specify `:url` parameter for the
34
+ # `form_with(model: @article)` without having to specify `:url` parameter for the
35
35
  # form action;
36
36
  # * `redirect_to` (which, in fact, uses `url_for`) so you can write
37
37
  # `redirect_to(post)` in your controllers;
@@ -61,7 +61,7 @@ module ActionDispatch
61
61
  # argument to the method. For example:
62
62
  #
63
63
  # polymorphic_url([blog, @post]) # calls blog.post_path(@post)
64
- # form_for([blog, @post]) # => "/blog/posts/1"
64
+ # form_with(model: [blog, @post]) # => "/blog/posts/1"
65
65
  #
66
66
  module PolymorphicRoutes
67
67
  # Constructs a call to a named RESTful route for the given record and returns
@@ -29,7 +29,7 @@ module ActionDispatch
29
29
  def from_requirements(requirements)
30
30
  routes.find { |route| route.requirements == requirements }
31
31
  end
32
- # :stopdoc:
32
+ # :enddoc:
33
33
 
34
34
  # Since the router holds references to many parts of the system like engines,
35
35
  # controllers and the application itself, inspecting the route set can actually
@@ -351,7 +351,7 @@ module ActionDispatch
351
351
  PATH = ->(options) { ActionDispatch::Http::URL.path_for(options) }
352
352
  UNKNOWN = ->(options) { ActionDispatch::Http::URL.url_for(options) }
353
353
 
354
- attr_accessor :formatter, :set, :named_routes, :default_scope, :router
354
+ attr_accessor :formatter, :set, :named_routes, :router
355
355
  attr_accessor :disable_clear_and_finalize, :resources_path_names
356
356
  attr_accessor :default_url_options, :draw_paths
357
357
  attr_reader :env_key, :polymorphic_mappings
@@ -363,7 +363,7 @@ module ActionDispatch
363
363
  end
364
364
 
365
365
  def self.new_with_config(config)
366
- route_set_config = DEFAULT_CONFIG
366
+ route_set_config = DEFAULT_CONFIG.dup
367
367
 
368
368
  # engines apparently don't have this set
369
369
  if config.respond_to? :relative_url_root
@@ -374,14 +374,18 @@ module ActionDispatch
374
374
  route_set_config.api_only = config.api_only
375
375
  end
376
376
 
377
+ if config.respond_to? :default_scope
378
+ route_set_config.default_scope = config.default_scope
379
+ end
380
+
377
381
  new route_set_config
378
382
  end
379
383
 
380
- Config = Struct.new :relative_url_root, :api_only
384
+ Config = Struct.new :relative_url_root, :api_only, :default_scope
381
385
 
382
- DEFAULT_CONFIG = Config.new(nil, false)
386
+ DEFAULT_CONFIG = Config.new(nil, false, nil)
383
387
 
384
- def initialize(config = DEFAULT_CONFIG)
388
+ def initialize(config = DEFAULT_CONFIG.dup)
385
389
  self.named_routes = NamedRouteCollection.new
386
390
  self.resources_path_names = self.class.default_resources_path_names
387
391
  self.default_url_options = {}
@@ -416,6 +420,14 @@ module ActionDispatch
416
420
  @config.api_only
417
421
  end
418
422
 
423
+ def default_scope
424
+ @config.default_scope
425
+ end
426
+
427
+ def default_scope=(new_default_scope)
428
+ @config.default_scope = new_default_scope
429
+ end
430
+
419
431
  def request_class
420
432
  ActionDispatch::Request
421
433
  end
@@ -647,14 +659,14 @@ module ActionDispatch
647
659
  if route.segment_keys.include?(:controller)
648
660
  ActionDispatch.deprecator.warn(<<-MSG.squish)
649
661
  Using a dynamic :controller segment in a route is deprecated and
650
- will be removed in Rails 7.2.
662
+ will be removed in Rails 8.1.
651
663
  MSG
652
664
  end
653
665
 
654
666
  if route.segment_keys.include?(:action)
655
667
  ActionDispatch.deprecator.warn(<<-MSG.squish)
656
668
  Using a dynamic :action segment in a route is deprecated and
657
- will be removed in Rails 7.2.
669
+ will be removed in Rails 8.1.
658
670
  MSG
659
671
  end
660
672
 
@@ -917,7 +929,7 @@ module ActionDispatch
917
929
  params.each do |key, value|
918
930
  if value.is_a?(String)
919
931
  value = value.dup.force_encoding(Encoding::BINARY)
920
- params[key] = RFC2396_PARSER.unescape(value)
932
+ params[key] = URI::RFC2396_PARSER.unescape(value)
921
933
  end
922
934
  end
923
935
  req.path_parameters = params
@@ -941,6 +953,5 @@ module ActionDispatch
941
953
  end
942
954
  end
943
955
  end
944
- # :startdoc:
945
956
  end
946
957
  end
@@ -54,6 +54,7 @@ module ActionDispatch
54
54
  # dependent part.
55
55
  def merge_script_names(previous_script_name, new_script_name)
56
56
  return new_script_name unless previous_script_name
57
+ new_script_name = new_script_name.chomp("/")
57
58
 
58
59
  resolved_parts = new_script_name.count("/")
59
60
  previous_parts = previous_script_name.count("/")
@@ -9,7 +9,6 @@ module ActionDispatch
9
9
 
10
10
  def initialize(name)
11
11
  @name = name
12
- set_default_options
13
12
  end
14
13
 
15
14
  def type
@@ -27,9 +26,9 @@ module ActionDispatch
27
26
  @options ||=
28
27
  case type
29
28
  when :chrome
30
- ::Selenium::WebDriver::Chrome::Options.new
29
+ default_chrome_options
31
30
  when :firefox
32
- ::Selenium::WebDriver::Firefox::Options.new
31
+ default_firefox_options
33
32
  end
34
33
  end
35
34
 
@@ -49,26 +48,18 @@ module ActionDispatch
49
48
  end
50
49
 
51
50
  private
52
- def set_default_options
53
- case name
54
- when :headless_chrome
55
- set_headless_chrome_browser_options
56
- when :headless_firefox
57
- set_headless_firefox_browser_options
58
- end
51
+ def default_chrome_options
52
+ options = ::Selenium::WebDriver::Chrome::Options.new
53
+ options.add_argument("--disable-search-engine-choice-screen")
54
+ options.add_argument("--headless") if name == :headless_chrome
55
+ options.add_argument("--disable-gpu") if Gem.win_platform?
56
+ options
59
57
  end
60
58
 
61
- def set_headless_chrome_browser_options
62
- configure do |capabilities|
63
- capabilities.add_argument("--headless")
64
- capabilities.add_argument("--disable-gpu") if Gem.win_platform?
65
- end
66
- end
67
-
68
- def set_headless_firefox_browser_options
69
- configure do |capabilities|
70
- capabilities.add_argument("-headless")
71
- end
59
+ def default_firefox_options
60
+ options = ::Selenium::WebDriver::Firefox::Options.new
61
+ options.add_argument("-headless") if name == :headless_firefox
62
+ options
72
63
  end
73
64
 
74
65
  def resolve_driver_path(namespace)
@@ -38,7 +38,7 @@ module ActionDispatch
38
38
 
39
39
  private
40
40
  def code_from_name(name)
41
- GENERIC_RESPONSE_CODES[name] || Rack::Utils.status_code(name)
41
+ GENERIC_RESPONSE_CODES[name] || ActionDispatch::Response.rack_status_code(name)
42
42
  end
43
43
 
44
44
  def name_from_code(code)
@@ -87,8 +87,13 @@ module ActionDispatch
87
87
  end
88
88
 
89
89
  def generate_response_message(expected, actual = @response.response_code)
90
- (+"Expected response to be a <#{code_with_name(expected)}>,"\
91
- " but was a <#{code_with_name(actual)}>").concat(location_if_redirected).concat(response_body_if_short)
90
+ lambda do
91
+ (+"Expected response to be a <#{code_with_name(expected)}>,"\
92
+ " but was a <#{code_with_name(actual)}>").
93
+ concat(location_if_redirected).
94
+ concat(exception_if_present).
95
+ concat(response_body_if_short)
96
+ end
92
97
  end
93
98
 
94
99
  def response_body_if_short
@@ -96,6 +101,11 @@ module ActionDispatch
96
101
  "\nResponse body: #{@response.body}"
97
102
  end
98
103
 
104
+ def exception_if_present
105
+ return "" unless ex = @request&.env&.[]("action_dispatch.exception")
106
+ "\n\nException while processing request: #{Minitest::UnexpectedError.new(ex).message}\n"
107
+ end
108
+
99
109
  def location_if_redirected
100
110
  return "" unless @response.redirection? && @response.location.present?
101
111
  location = normalize_argument_to_redirection(@response.location)