actionpack 7.2.3 → 8.1.3

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 (102) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +394 -119
  3. data/lib/abstract_controller/asset_paths.rb +4 -2
  4. data/lib/abstract_controller/base.rb +11 -5
  5. data/lib/abstract_controller/caching.rb +6 -3
  6. data/lib/abstract_controller/callbacks.rb +6 -0
  7. data/lib/abstract_controller/logger.rb +2 -1
  8. data/lib/abstract_controller/rendering.rb +0 -1
  9. data/lib/action_controller/api.rb +1 -0
  10. data/lib/action_controller/base.rb +3 -2
  11. data/lib/action_controller/caching.rb +1 -2
  12. data/lib/action_controller/form_builder.rb +4 -4
  13. data/lib/action_controller/log_subscriber.rb +22 -3
  14. data/lib/action_controller/metal/allow_browser.rb +12 -2
  15. data/lib/action_controller/metal/conditional_get.rb +30 -1
  16. data/lib/action_controller/metal/data_streaming.rb +5 -5
  17. data/lib/action_controller/metal/exceptions.rb +5 -0
  18. data/lib/action_controller/metal/flash.rb +1 -4
  19. data/lib/action_controller/metal/head.rb +3 -1
  20. data/lib/action_controller/metal/instrumentation.rb +1 -2
  21. data/lib/action_controller/metal/live.rb +65 -25
  22. data/lib/action_controller/metal/permissions_policy.rb +9 -0
  23. data/lib/action_controller/metal/rate_limiting.rb +39 -9
  24. data/lib/action_controller/metal/redirecting.rb +105 -13
  25. data/lib/action_controller/metal/renderers.rb +29 -9
  26. data/lib/action_controller/metal/rendering.rb +7 -1
  27. data/lib/action_controller/metal/request_forgery_protection.rb +18 -10
  28. data/lib/action_controller/metal/rescue.rb +9 -0
  29. data/lib/action_controller/metal/streaming.rb +5 -84
  30. data/lib/action_controller/metal/strong_parameters.rb +277 -89
  31. data/lib/action_controller/railtie.rb +33 -15
  32. data/lib/action_controller/structured_event_subscriber.rb +116 -0
  33. data/lib/action_controller/test_case.rb +12 -2
  34. data/lib/action_dispatch/http/cache.rb +138 -11
  35. data/lib/action_dispatch/http/content_security_policy.rb +14 -1
  36. data/lib/action_dispatch/http/filter_parameters.rb +5 -3
  37. data/lib/action_dispatch/http/mime_negotiation.rb +55 -1
  38. data/lib/action_dispatch/http/mime_types.rb +1 -0
  39. data/lib/action_dispatch/http/param_builder.rb +187 -0
  40. data/lib/action_dispatch/http/param_error.rb +26 -0
  41. data/lib/action_dispatch/http/parameters.rb +3 -3
  42. data/lib/action_dispatch/http/permissions_policy.rb +6 -0
  43. data/lib/action_dispatch/http/query_parser.rb +55 -0
  44. data/lib/action_dispatch/http/request.rb +70 -21
  45. data/lib/action_dispatch/http/response.rb +50 -16
  46. data/lib/action_dispatch/http/url.rb +110 -14
  47. data/lib/action_dispatch/journey/gtg/simulator.rb +33 -12
  48. data/lib/action_dispatch/journey/gtg/transition_table.rb +33 -41
  49. data/lib/action_dispatch/journey/nodes/node.rb +2 -1
  50. data/lib/action_dispatch/journey/parser.rb +99 -196
  51. data/lib/action_dispatch/journey/route.rb +45 -31
  52. data/lib/action_dispatch/journey/router/utils.rb +8 -14
  53. data/lib/action_dispatch/journey/router.rb +59 -81
  54. data/lib/action_dispatch/journey/routes.rb +7 -0
  55. data/lib/action_dispatch/journey/scanner.rb +44 -42
  56. data/lib/action_dispatch/journey/visitors.rb +55 -23
  57. data/lib/action_dispatch/journey/visualizer/fsm.js +4 -6
  58. data/lib/action_dispatch/log_subscriber.rb +7 -3
  59. data/lib/action_dispatch/middleware/cookies.rb +8 -4
  60. data/lib/action_dispatch/middleware/debug_exceptions.rb +24 -5
  61. data/lib/action_dispatch/middleware/debug_view.rb +11 -5
  62. data/lib/action_dispatch/middleware/exception_wrapper.rb +11 -11
  63. data/lib/action_dispatch/middleware/executor.rb +12 -2
  64. data/lib/action_dispatch/middleware/public_exceptions.rb +1 -5
  65. data/lib/action_dispatch/middleware/remote_ip.rb +11 -5
  66. data/lib/action_dispatch/middleware/request_id.rb +2 -1
  67. data/lib/action_dispatch/middleware/session/cache_store.rb +17 -0
  68. data/lib/action_dispatch/middleware/ssl.rb +13 -3
  69. data/lib/action_dispatch/middleware/templates/rescues/_copy_button.html.erb +1 -0
  70. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +3 -5
  71. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +9 -5
  72. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +1 -0
  73. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +1 -0
  74. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +4 -0
  75. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -0
  76. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +50 -0
  77. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +1 -0
  78. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -0
  79. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -0
  80. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -0
  81. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -0
  82. data/lib/action_dispatch/railtie.rb +21 -0
  83. data/lib/action_dispatch/request/session.rb +1 -0
  84. data/lib/action_dispatch/request/utils.rb +9 -3
  85. data/lib/action_dispatch/routing/inspector.rb +80 -57
  86. data/lib/action_dispatch/routing/mapper.rb +404 -223
  87. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -2
  88. data/lib/action_dispatch/routing/redirection.rb +10 -7
  89. data/lib/action_dispatch/routing/route_set.rb +21 -12
  90. data/lib/action_dispatch/routing/routes_proxy.rb +1 -0
  91. data/lib/action_dispatch/structured_event_subscriber.rb +20 -0
  92. data/lib/action_dispatch/system_test_case.rb +3 -3
  93. data/lib/action_dispatch/system_testing/browser.rb +12 -21
  94. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +2 -2
  95. data/lib/action_dispatch/testing/assertions/response.rb +26 -2
  96. data/lib/action_dispatch/testing/assertions/routing.rb +27 -15
  97. data/lib/action_dispatch/testing/integration.rb +18 -7
  98. data/lib/action_dispatch.rb +14 -4
  99. data/lib/action_pack/gem_version.rb +2 -2
  100. metadata +18 -48
  101. data/lib/action_dispatch/journey/parser.y +0 -50
  102. data/lib/action_dispatch/journey/parser_extras.rb +0 -33
@@ -87,7 +87,7 @@ module ActionDispatch
87
87
  attr_reader :path, :requirements, :defaults, :to, :default_controller,
88
88
  :default_action, :required_defaults, :ast, :scope_options
89
89
 
90
- def self.build(scope, set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
90
+ def self.build(scope, set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, internal, options)
91
91
  scope_params = {
92
92
  blocks: scope[:blocks] || [],
93
93
  constraints: scope[:constraints] || {},
@@ -98,7 +98,7 @@ module ActionDispatch
98
98
 
99
99
  new set: set, ast: ast, controller: controller, default_action: default_action,
100
100
  to: to, formatted: formatted, via: via, options_constraints: options_constraints,
101
- anchor: anchor, scope_params: scope_params, options: scope_params[:options].merge(options)
101
+ anchor: anchor, scope_params: scope_params, internal: internal, options: scope_params[:options].merge(options)
102
102
  end
103
103
 
104
104
  def self.check_via(via)
@@ -129,7 +129,7 @@ module ActionDispatch
129
129
  format != false && !path.match?(OPTIONAL_FORMAT_REGEX)
130
130
  end
131
131
 
132
- def initialize(set:, ast:, controller:, default_action:, to:, formatted:, via:, options_constraints:, anchor:, scope_params:, options:)
132
+ def initialize(set:, ast:, controller:, default_action:, to:, formatted:, via:, options_constraints:, anchor:, scope_params:, internal:, options:)
133
133
  @defaults = scope_params[:defaults]
134
134
  @set = set
135
135
  @to = intern(to)
@@ -137,7 +137,7 @@ module ActionDispatch
137
137
  @default_action = intern(default_action)
138
138
  @anchor = anchor
139
139
  @via = via
140
- @internal = options.delete(:internal)
140
+ @internal = internal
141
141
  @scope_options = scope_params[:options]
142
142
  ast = Journey::Ast.new(ast, formatted)
143
143
 
@@ -183,7 +183,7 @@ module ActionDispatch
183
183
  def make_route(name, precedence)
184
184
  Journey::Route.new(name: name, app: application, path: path, constraints: conditions,
185
185
  required_defaults: required_defaults, defaults: defaults,
186
- request_method_match: request_method, precedence: precedence,
186
+ via: @via, precedence: precedence,
187
187
  scope_options: scope_options, internal: @internal, source_location: route_source_location)
188
188
  end
189
189
 
@@ -204,11 +204,6 @@ module ActionDispatch
204
204
  end
205
205
  private :build_conditions
206
206
 
207
- def request_method
208
- @via.map { |x| Journey::Route.verb_matcher(x) }
209
- end
210
- private :request_method
211
-
212
207
  private
213
208
  def intern(object)
214
209
  object.is_a?(String) ? -object : object
@@ -375,35 +370,18 @@ module ActionDispatch
375
370
  Routing::RouteSet::Dispatcher.new raise_on_name_error
376
371
  end
377
372
 
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)
384
-
385
- cleaned_path = Mapper.backtrace_cleaner.clean_frame(location.path)
386
- next if cleaned_path.nil?
387
-
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)
373
+ def route_source_location
374
+ if Mapper.route_source_locations
375
+ action_dispatch_dir = File.expand_path("..", __dir__)
376
+ Thread.each_caller_location do |location|
377
+ next if location.path.start_with?(action_dispatch_dir)
399
378
 
400
- cleaned_path = Mapper.backtrace_cleaner.clean_frame(location.path)
401
- next if cleaned_path.nil?
379
+ cleaned_path = Mapper.backtrace_cleaner.clean_frame(location.path)
380
+ next if cleaned_path.nil?
402
381
 
403
- return "#{cleaned_path}:#{location.lineno}"
404
- end
405
- nil
382
+ return "#{cleaned_path}:#{location.lineno}"
406
383
  end
384
+ nil
407
385
  end
408
386
  end
409
387
  end
@@ -470,7 +448,6 @@ module ActionDispatch
470
448
  # When a pattern points to an internal route, the route's `:action` and
471
449
  # `:controller` should be set in options or hash shorthand. Examples:
472
450
  #
473
- # match 'photos/:id' => 'photos#show', via: :get
474
451
  # match 'photos/:id', to: 'photos#show', via: :get
475
452
  # match 'photos/:id', controller: 'photos', action: 'show', via: :get
476
453
  #
@@ -614,10 +591,6 @@ module ActionDispatch
614
591
  #
615
592
  # mount SomeRackApp, at: "some_route"
616
593
  #
617
- # Alternatively:
618
- #
619
- # mount(SomeRackApp => "some_route")
620
- #
621
594
  # For options, see `match`, as `mount` uses it internally.
622
595
  #
623
596
  # All mounted applications come with routing helpers to access them. These are
@@ -625,21 +598,36 @@ module ActionDispatch
625
598
  # `some_rack_app_path` or `some_rack_app_url`. To customize this helper's name,
626
599
  # use the `:as` option:
627
600
  #
628
- # mount(SomeRackApp => "some_route", as: "exciting")
601
+ # mount(SomeRackApp, at: "some_route", as: "exciting")
629
602
  #
630
603
  # This will generate the `exciting_path` and `exciting_url` helpers which can be
631
604
  # used to navigate to this mounted app.
632
- def mount(app, options = nil)
633
- if options
634
- path = options.delete(:at)
635
- elsif Hash === app
636
- options = app
637
- app, path = options.find { |k, _| k.respond_to?(:call) }
638
- options.delete(app) if app
605
+ def mount(app = nil, deprecated_options = nil, as: DEFAULT, via: nil, at: nil, defaults: nil, constraints: nil, anchor: false, format: false, path: nil, internal: nil, **mapping, &block)
606
+ if deprecated_options.is_a?(Hash)
607
+ as = assign_deprecated_option(deprecated_options, :as, :mount) if deprecated_options.key?(:as)
608
+ via ||= assign_deprecated_option(deprecated_options, :via, :mount)
609
+ at ||= assign_deprecated_option(deprecated_options, :at, :mount)
610
+ defaults ||= assign_deprecated_option(deprecated_options, :defaults, :mount)
611
+ constraints ||= assign_deprecated_option(deprecated_options, :constraints, :mount)
612
+ anchor = assign_deprecated_option(deprecated_options, :anchor, :mount) if deprecated_options.key?(:anchor)
613
+ format = assign_deprecated_option(deprecated_options, :format, :mount) if deprecated_options.key?(:format)
614
+ path ||= assign_deprecated_option(deprecated_options, :path, :mount)
615
+ internal ||= assign_deprecated_option(deprecated_options, :internal, :mount)
616
+ assign_deprecated_options(deprecated_options, mapping, :mount)
617
+ end
618
+
619
+ path_or_action = at
620
+
621
+ if app.nil?
622
+ hash_app, hash_path = mapping.find { |key, _| key.respond_to?(:call) }
623
+ mapping.delete(hash_app) if hash_app
624
+
625
+ app ||= hash_app
626
+ path_or_action ||= hash_path
639
627
  end
640
628
 
641
629
  raise ArgumentError, "A rack application must be specified" unless app.respond_to?(:call)
642
- raise ArgumentError, <<~MSG unless path
630
+ raise ArgumentError, <<~MSG unless path_or_action
643
631
  Must be called with mount point
644
632
 
645
633
  mount SomeRackApp, at: "some_route"
@@ -648,12 +636,12 @@ module ActionDispatch
648
636
  MSG
649
637
 
650
638
  rails_app = rails_app? app
651
- options[:as] ||= app_name(app, rails_app)
639
+ as = app_name(app, rails_app) if as == DEFAULT
652
640
 
653
- target_as = name_for_action(options[:as], path)
654
- options[:via] ||= :all
641
+ target_as = name_for_action(as, path_or_action)
642
+ via ||= :all
655
643
 
656
- match(path, { to: app, anchor: false, format: false }.merge(options))
644
+ match(path_or_action, to: app, as:, via:, defaults:, constraints:, anchor:, format:, path:, internal:, **mapping, &block)
657
645
 
658
646
  define_generate_prefix(app, target_as) if rails_app
659
647
  self
@@ -665,7 +653,7 @@ module ActionDispatch
665
653
  alias_method :default_url_options, :default_url_options=
666
654
 
667
655
  def with_default_scope(scope, &block)
668
- scope(scope) do
656
+ scope(**scope) do
669
657
  instance_exec(&block)
670
658
  end
671
659
  end
@@ -676,6 +664,24 @@ module ActionDispatch
676
664
  end
677
665
 
678
666
  private
667
+ def assign_deprecated_option(deprecated_options, key, method_name)
668
+ if (deprecated_value = deprecated_options.delete(key))
669
+ ActionDispatch.deprecator.warn(<<~MSG.squish)
670
+ #{method_name} received a hash argument #{key}. Please use a keyword instead. Support to hash argument will be removed in Rails 8.2.
671
+ MSG
672
+ deprecated_value
673
+ end
674
+ end
675
+
676
+ def assign_deprecated_options(deprecated_options, options, method_name)
677
+ deprecated_options.each do |key, value|
678
+ ActionDispatch.deprecator.warn(<<~MSG.squish)
679
+ #{method_name} received a hash argument #{key}. Please use a keyword instead. Support to hash argument will be removed in Rails 8.2.
680
+ MSG
681
+ options[key] = value
682
+ end
683
+ end
684
+
679
685
  def rails_app?(app)
680
686
  app.is_a?(Class) && app < Rails::Railtie
681
687
  end
@@ -729,57 +735,171 @@ module ActionDispatch
729
735
  # [match](rdoc-ref:Base#match)
730
736
  #
731
737
  # get 'bacon', to: 'food#bacon'
732
- def get(*args, &block)
733
- map_method(:get, args, &block)
738
+ def get(*path_or_actions, as: DEFAULT, to: nil, controller: nil, action: nil, on: nil, defaults: nil, constraints: nil, anchor: nil, format: nil, path: nil, internal: nil, **mapping, &block)
739
+ if path_or_actions.grep(Hash).any? && (deprecated_options = path_or_actions.extract_options!)
740
+ as = assign_deprecated_option(deprecated_options, :as, :get) if deprecated_options.key?(:as)
741
+ to ||= assign_deprecated_option(deprecated_options, :to, :get)
742
+ controller ||= assign_deprecated_option(deprecated_options, :controller, :get)
743
+ action ||= assign_deprecated_option(deprecated_options, :action, :get)
744
+ on ||= assign_deprecated_option(deprecated_options, :on, :get)
745
+ defaults ||= assign_deprecated_option(deprecated_options, :defaults, :get)
746
+ constraints ||= assign_deprecated_option(deprecated_options, :constraints, :get)
747
+ anchor = assign_deprecated_option(deprecated_options, :anchor, :get) if deprecated_options.key?(:anchor)
748
+ format = assign_deprecated_option(deprecated_options, :format, :get) if deprecated_options.key?(:format)
749
+ path ||= assign_deprecated_option(deprecated_options, :path, :get)
750
+ internal ||= assign_deprecated_option(deprecated_options, :internal, :get)
751
+ assign_deprecated_options(deprecated_options, mapping, :get)
752
+ end
753
+
754
+ match(*path_or_actions, as:, to:, controller:, action:, on:, defaults:, constraints:, anchor:, format:, path:, internal:, **mapping, via: :get, &block)
755
+ self
734
756
  end
735
757
 
736
758
  # Define a route that only recognizes HTTP POST. For supported arguments, see
737
759
  # [match](rdoc-ref:Base#match)
738
760
  #
739
761
  # post 'bacon', to: 'food#bacon'
740
- def post(*args, &block)
741
- map_method(:post, args, &block)
762
+ def post(*path_or_actions, as: DEFAULT, to: nil, controller: nil, action: nil, on: nil, defaults: nil, constraints: nil, anchor: nil, format: nil, path: nil, internal: nil, **mapping, &block)
763
+ if path_or_actions.grep(Hash).any? && (deprecated_options = path_or_actions.extract_options!)
764
+ as = assign_deprecated_option(deprecated_options, :as, :post) if deprecated_options.key?(:as)
765
+ to ||= assign_deprecated_option(deprecated_options, :to, :post)
766
+ controller ||= assign_deprecated_option(deprecated_options, :controller, :post)
767
+ action ||= assign_deprecated_option(deprecated_options, :action, :post)
768
+ on ||= assign_deprecated_option(deprecated_options, :on, :post)
769
+ defaults ||= assign_deprecated_option(deprecated_options, :defaults, :post)
770
+ constraints ||= assign_deprecated_option(deprecated_options, :constraints, :post)
771
+ anchor = assign_deprecated_option(deprecated_options, :anchor, :post) if deprecated_options.key?(:anchor)
772
+ format = assign_deprecated_option(deprecated_options, :format, :post) if deprecated_options.key?(:format)
773
+ path ||= assign_deprecated_option(deprecated_options, :path, :post)
774
+ internal ||= assign_deprecated_option(deprecated_options, :internal, :post)
775
+ assign_deprecated_options(deprecated_options, mapping, :post)
776
+ end
777
+
778
+ match(*path_or_actions, as:, to:, controller:, action:, on:, defaults:, constraints:, anchor:, format:, path:, internal:, **mapping, via: :post, &block)
779
+ self
742
780
  end
743
781
 
744
782
  # Define a route that only recognizes HTTP PATCH. For supported arguments, see
745
783
  # [match](rdoc-ref:Base#match)
746
784
  #
747
785
  # patch 'bacon', to: 'food#bacon'
748
- def patch(*args, &block)
749
- map_method(:patch, args, &block)
786
+ def patch(*path_or_actions, as: DEFAULT, to: nil, controller: nil, action: nil, on: nil, defaults: nil, constraints: nil, anchor: nil, format: nil, path: nil, internal: nil, **mapping, &block)
787
+ if path_or_actions.grep(Hash).any? && (deprecated_options = path_or_actions.extract_options!)
788
+ as = assign_deprecated_option(deprecated_options, :as, :patch) if deprecated_options.key?(:as)
789
+ to ||= assign_deprecated_option(deprecated_options, :to, :patch)
790
+ controller ||= assign_deprecated_option(deprecated_options, :controller, :patch)
791
+ action ||= assign_deprecated_option(deprecated_options, :action, :patch)
792
+ on ||= assign_deprecated_option(deprecated_options, :on, :patch)
793
+ defaults ||= assign_deprecated_option(deprecated_options, :defaults, :patch)
794
+ constraints ||= assign_deprecated_option(deprecated_options, :constraints, :patch)
795
+ anchor = assign_deprecated_option(deprecated_options, :anchor, :patch) if deprecated_options.key?(:anchor)
796
+ format = assign_deprecated_option(deprecated_options, :format, :patch) if deprecated_options.key?(:format)
797
+ path ||= assign_deprecated_option(deprecated_options, :path, :patch)
798
+ internal ||= assign_deprecated_option(deprecated_options, :internal, :patch)
799
+ assign_deprecated_options(deprecated_options, mapping, :patch)
800
+ end
801
+
802
+ match(*path_or_actions, as:, to:, controller:, action:, on:, defaults:, constraints:, anchor:, format:, path:, internal:, **mapping, via: :patch, &block)
803
+ self
750
804
  end
751
805
 
752
806
  # Define a route that only recognizes HTTP PUT. For supported arguments, see
753
807
  # [match](rdoc-ref:Base#match)
754
808
  #
755
809
  # put 'bacon', to: 'food#bacon'
756
- def put(*args, &block)
757
- map_method(:put, args, &block)
810
+ def put(*path_or_actions, as: DEFAULT, to: nil, controller: nil, action: nil, on: nil, defaults: nil, constraints: nil, anchor: nil, format: nil, path: nil, internal: nil, **mapping, &block)
811
+ if path_or_actions.grep(Hash).any? && (deprecated_options = path_or_actions.extract_options!)
812
+ as = assign_deprecated_option(deprecated_options, :as, :put) if deprecated_options.key?(:as)
813
+ to ||= assign_deprecated_option(deprecated_options, :to, :put)
814
+ controller ||= assign_deprecated_option(deprecated_options, :controller, :put)
815
+ action ||= assign_deprecated_option(deprecated_options, :action, :put)
816
+ on ||= assign_deprecated_option(deprecated_options, :on, :put)
817
+ defaults ||= assign_deprecated_option(deprecated_options, :defaults, :put)
818
+ constraints ||= assign_deprecated_option(deprecated_options, :constraints, :put)
819
+ anchor = assign_deprecated_option(deprecated_options, :anchor, :put) if deprecated_options.key?(:anchor)
820
+ format = assign_deprecated_option(deprecated_options, :format, :put) if deprecated_options.key?(:format)
821
+ path ||= assign_deprecated_option(deprecated_options, :path, :put)
822
+ internal ||= assign_deprecated_option(deprecated_options, :internal, :put)
823
+ assign_deprecated_options(deprecated_options, mapping, :put)
824
+ end
825
+
826
+ match(*path_or_actions, as:, to:, controller:, action:, on:, defaults:, constraints:, anchor:, format:, path:, internal:, **mapping, via: :put, &block)
827
+ self
758
828
  end
759
829
 
760
830
  # Define a route that only recognizes HTTP DELETE. For supported arguments, see
761
831
  # [match](rdoc-ref:Base#match)
762
832
  #
763
833
  # delete 'broccoli', to: 'food#broccoli'
764
- def delete(*args, &block)
765
- map_method(:delete, args, &block)
834
+ def delete(*path_or_actions, as: DEFAULT, to: nil, controller: nil, action: nil, on: nil, defaults: nil, constraints: nil, anchor: nil, format: nil, path: nil, internal: nil, **mapping, &block)
835
+ if path_or_actions.grep(Hash).any? && (deprecated_options = path_or_actions.extract_options!)
836
+ as = assign_deprecated_option(deprecated_options, :as, :delete) if deprecated_options.key?(:as)
837
+ to ||= assign_deprecated_option(deprecated_options, :to, :delete)
838
+ controller ||= assign_deprecated_option(deprecated_options, :controller, :delete)
839
+ action ||= assign_deprecated_option(deprecated_options, :action, :delete)
840
+ on ||= assign_deprecated_option(deprecated_options, :on, :delete)
841
+ defaults ||= assign_deprecated_option(deprecated_options, :defaults, :delete)
842
+ constraints ||= assign_deprecated_option(deprecated_options, :constraints, :delete)
843
+ anchor = assign_deprecated_option(deprecated_options, :anchor, :delete) if deprecated_options.key?(:anchor)
844
+ format = assign_deprecated_option(deprecated_options, :format, :delete) if deprecated_options.key?(:format)
845
+ path ||= assign_deprecated_option(deprecated_options, :path, :delete)
846
+ internal ||= assign_deprecated_option(deprecated_options, :internal, :delete)
847
+ assign_deprecated_options(deprecated_options, mapping, :delete)
848
+ end
849
+
850
+ match(*path_or_actions, as:, to:, controller:, action:, on:, defaults:, constraints:, anchor:, format:, path:, internal:, **mapping, via: :delete, &block)
851
+ self
766
852
  end
767
853
 
768
854
  # Define a route that only recognizes HTTP OPTIONS. For supported arguments, see
769
855
  # [match](rdoc-ref:Base#match)
770
856
  #
771
857
  # options 'carrots', to: 'food#carrots'
772
- def options(*args, &block)
773
- map_method(:options, args, &block)
858
+ def options(*path_or_actions, as: DEFAULT, to: nil, controller: nil, action: nil, on: nil, defaults: nil, constraints: nil, anchor: false, format: false, path: nil, internal: nil, **mapping, &block)
859
+ if path_or_actions.grep(Hash).any? && (deprecated_options = path_or_actions.extract_options!)
860
+ as = assign_deprecated_option(deprecated_options, :as, :options) if deprecated_options.key?(:as)
861
+ to ||= assign_deprecated_option(deprecated_options, :to, :options)
862
+ controller ||= assign_deprecated_option(deprecated_options, :controller, :options)
863
+ action ||= assign_deprecated_option(deprecated_options, :action, :options)
864
+ on ||= assign_deprecated_option(deprecated_options, :on, :options)
865
+ defaults ||= assign_deprecated_option(deprecated_options, :defaults, :options)
866
+ constraints ||= assign_deprecated_option(deprecated_options, :constraints, :options)
867
+ anchor = assign_deprecated_option(deprecated_options, :anchor, :options) if deprecated_options.key?(:anchor)
868
+ format = assign_deprecated_option(deprecated_options, :format, :options) if deprecated_options.key?(:format)
869
+ path ||= assign_deprecated_option(deprecated_options, :path, :options)
870
+ internal ||= assign_deprecated_option(deprecated_options, :internal, :options)
871
+ assign_deprecated_options(deprecated_options, mapping, :options)
872
+ end
873
+
874
+ match(*path_or_actions, as:, to:, controller:, action:, on:, defaults:, constraints:, anchor:, format:, path:, internal:, **mapping, via: :options, &block)
875
+ self
774
876
  end
775
877
 
776
- private
777
- def map_method(method, args, &block)
778
- options = args.extract_options!
779
- options[:via] = method
780
- match(*args, options, &block)
781
- self
782
- end
878
+ # Define a route that recognizes HTTP CONNECT (and GET) requests. More
879
+ # specifically this recognizes HTTP/1 protocol upgrade requests and HTTP/2
880
+ # CONNECT requests with the protocol pseudo header. For supported arguments,
881
+ # see [match](rdoc-ref:Base#match)
882
+ #
883
+ # connect 'live', to: 'live#index'
884
+ def connect(*path_or_actions, as: DEFAULT, to: nil, controller: nil, action: nil, on: nil, defaults: nil, constraints: nil, anchor: false, format: false, path: nil, internal: nil, **mapping, &block)
885
+ if path_or_actions.grep(Hash).any? && (deprecated_options = path_or_actions.extract_options!)
886
+ as = assign_deprecated_option(deprecated_options, :as, :connect) if deprecated_options.key?(:as)
887
+ to ||= assign_deprecated_option(deprecated_options, :to, :connect)
888
+ controller ||= assign_deprecated_option(deprecated_options, :controller, :connect)
889
+ action ||= assign_deprecated_option(deprecated_options, :action, :connect)
890
+ on ||= assign_deprecated_option(deprecated_options, :on, :connect)
891
+ defaults ||= assign_deprecated_option(deprecated_options, :defaults, :connect)
892
+ constraints ||= assign_deprecated_option(deprecated_options, :constraints, :connect)
893
+ anchor = assign_deprecated_option(deprecated_options, :anchor, :connect) if deprecated_options.key?(:anchor)
894
+ format = assign_deprecated_option(deprecated_options, :format, :connect) if deprecated_options.key?(:format)
895
+ path ||= assign_deprecated_option(deprecated_options, :path, :connect)
896
+ internal ||= assign_deprecated_option(deprecated_options, :internal, :connect)
897
+ assign_deprecated_options(deprecated_options, mapping, :connect)
898
+ end
899
+
900
+ match(*path_or_actions, as:, to:, controller:, action:, on:, defaults:, constraints:, anchor:, format:, path:, internal:, **mapping, via: [:get, :connect], &block)
901
+ self
902
+ end
783
903
  end
784
904
 
785
905
  # You may wish to organize groups of controllers under a namespace. Most
@@ -866,8 +986,13 @@ module ActionDispatch
866
986
  # scope as: "sekret" do
867
987
  # resources :posts
868
988
  # end
869
- def scope(*args)
870
- options = args.extract_options!.dup
989
+ def scope(*args, only: nil, except: nil, **options)
990
+ if args.grep(Hash).any? && (deprecated_options = args.extract_options!)
991
+ only ||= assign_deprecated_option(deprecated_options, :only, :scope)
992
+ only ||= assign_deprecated_option(deprecated_options, :except, :scope)
993
+ assign_deprecated_options(deprecated_options, options, :scope)
994
+ end
995
+
871
996
  scope = {}
872
997
 
873
998
  options[:path] = args.flatten.join("/") if args.any?
@@ -888,9 +1013,8 @@ module ActionDispatch
888
1013
  block, options[:constraints] = options[:constraints], {}
889
1014
  end
890
1015
 
891
- if options.key?(:only) || options.key?(:except)
892
- scope[:action_options] = { only: options.delete(:only),
893
- except: options.delete(:except) }
1016
+ if only || except
1017
+ scope[:action_options] = { only:, except: }
894
1018
  end
895
1019
 
896
1020
  if options.key? :anchor
@@ -970,18 +1094,24 @@ module ActionDispatch
970
1094
  # namespace :admin, as: "sekret" do
971
1095
  # resources :posts
972
1096
  # end
973
- def namespace(path, options = {}, &block)
974
- path = path.to_s
975
-
976
- defaults = {
977
- module: path,
978
- as: options.fetch(:as, path),
979
- shallow_path: options.fetch(:path, path),
980
- shallow_prefix: options.fetch(:as, path)
981
- }
1097
+ def namespace(name, deprecated_options = nil, as: DEFAULT, path: DEFAULT, shallow_path: DEFAULT, shallow_prefix: DEFAULT, **options, &block)
1098
+ if deprecated_options.is_a?(Hash)
1099
+ as = assign_deprecated_option(deprecated_options, :as, :namespace) if deprecated_options.key?(:as)
1100
+ path ||= assign_deprecated_option(deprecated_options, :path, :namespace) if deprecated_options.key?(:path)
1101
+ shallow_path ||= assign_deprecated_option(deprecated_options, :shallow_path, :namespace) if deprecated_options.key?(:shallow_path)
1102
+ shallow_prefix ||= assign_deprecated_option(deprecated_options, :shallow_prefix, :namespace) if deprecated_options.key?(:shallow_prefix)
1103
+ assign_deprecated_options(deprecated_options, options, :namespace)
1104
+ end
1105
+
1106
+ name = name.to_s
1107
+ options[:module] ||= name
1108
+ as = name if as == DEFAULT
1109
+ path = name if path == DEFAULT
1110
+ shallow_path = path if shallow_path == DEFAULT
1111
+ shallow_prefix = as if shallow_prefix == DEFAULT
982
1112
 
983
- path_scope(options.delete(:path) { path }) do
984
- scope(defaults.merge!(options), &block)
1113
+ path_scope(path) do
1114
+ scope(**options, as:, shallow_path:, shallow_prefix:, &block)
985
1115
  end
986
1116
  end
987
1117
 
@@ -1174,13 +1304,29 @@ module ActionDispatch
1174
1304
  CANONICAL_ACTIONS = %w(index create new show update destroy)
1175
1305
 
1176
1306
  class Resource # :nodoc:
1307
+ class << self
1308
+ def default_actions(api_only)
1309
+ if api_only
1310
+ [:index, :create, :show, :update, :destroy]
1311
+ else
1312
+ [:index, :create, :new, :show, :update, :destroy, :edit]
1313
+ end
1314
+ end
1315
+ end
1316
+
1177
1317
  attr_reader :controller, :path, :param
1178
1318
 
1179
- def initialize(entities, api_only, shallow, options = {})
1319
+ def initialize(entities, api_only, shallow, only: nil, except: nil, **options)
1180
1320
  if options[:param].to_s.include?(":")
1181
1321
  raise ArgumentError, ":param option can't contain colons"
1182
1322
  end
1183
1323
 
1324
+ valid_actions = self.class.default_actions(false) # ignore api_only for this validation
1325
+ if (invalid_actions = invalid_only_except_options(valid_actions, only:, except:).presence)
1326
+ error_prefix = "Route `resource#{"s" unless singleton?} :#{entities}`"
1327
+ raise ArgumentError, "#{error_prefix} - :only and :except must include only #{valid_actions}, but also included #{invalid_actions}"
1328
+ end
1329
+
1184
1330
  @name = entities.to_s
1185
1331
  @path = (options[:path] || @name).to_s
1186
1332
  @controller = (options[:controller] || @name).to_s
@@ -1189,16 +1335,12 @@ module ActionDispatch
1189
1335
  @options = options
1190
1336
  @shallow = shallow
1191
1337
  @api_only = api_only
1192
- @only = options.delete :only
1193
- @except = options.delete :except
1338
+ @only = only
1339
+ @except = except
1194
1340
  end
1195
1341
 
1196
1342
  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
1343
+ self.class.default_actions(@api_only)
1202
1344
  end
1203
1345
 
1204
1346
  def actions
@@ -1266,10 +1408,25 @@ module ActionDispatch
1266
1408
  end
1267
1409
 
1268
1410
  def singleton?; false; end
1411
+
1412
+ private
1413
+ def invalid_only_except_options(valid_actions, only:, except:)
1414
+ [only, except].flatten.compact.uniq.map(&:to_sym) - valid_actions
1415
+ end
1269
1416
  end
1270
1417
 
1271
1418
  class SingletonResource < Resource # :nodoc:
1272
- def initialize(entities, api_only, shallow, options)
1419
+ class << self
1420
+ def default_actions(api_only)
1421
+ if api_only
1422
+ [:show, :create, :update, :destroy]
1423
+ else
1424
+ [:show, :create, :update, :destroy, :new, :edit]
1425
+ end
1426
+ end
1427
+ end
1428
+
1429
+ def initialize(entities, api_only, shallow, **options)
1273
1430
  super
1274
1431
  @as = nil
1275
1432
  @controller = (options[:controller] || plural).to_s
@@ -1277,11 +1434,7 @@ module ActionDispatch
1277
1434
  end
1278
1435
 
1279
1436
  def default_actions
1280
- if @api_only
1281
- [:show, :create, :update, :destroy]
1282
- else
1283
- [:show, :create, :update, :destroy, :new, :edit]
1284
- end
1437
+ self.class.default_actions(@api_only)
1285
1438
  end
1286
1439
 
1287
1440
  def plural
@@ -1334,19 +1487,22 @@ module ActionDispatch
1334
1487
  #
1335
1488
  # ### Options
1336
1489
  # Takes same options as [resources](rdoc-ref:#resources)
1337
- def resource(*resources, &block)
1338
- options = resources.extract_options!.dup
1490
+ def resource(*resources, concerns: nil, **options, &block)
1491
+ if resources.grep(Hash).any? && (deprecated_options = resources.extract_options!)
1492
+ concerns = assign_deprecated_option(deprecated_options, :concerns, :resource) if deprecated_options.key?(:concerns)
1493
+ assign_deprecated_options(deprecated_options, options, :resource)
1494
+ end
1339
1495
 
1340
- if apply_common_behavior_for(:resource, resources, options, &block)
1496
+ if apply_common_behavior_for(:resource, resources, concerns:, **options, &block)
1341
1497
  return self
1342
1498
  end
1343
1499
 
1344
1500
  with_scope_level(:resource) do
1345
- options = apply_action_options options
1346
- resource_scope(SingletonResource.new(resources.pop, api_only?, @scope[:shallow], options)) do
1501
+ options = apply_action_options :resource, options
1502
+ resource_scope(SingletonResource.new(resources.pop, api_only?, @scope[:shallow], **options)) do
1347
1503
  yield if block_given?
1348
1504
 
1349
- concerns(options[:concerns]) if options[:concerns]
1505
+ concerns(*concerns) if concerns
1350
1506
 
1351
1507
  new do
1352
1508
  get :new
@@ -1504,19 +1660,22 @@ module ActionDispatch
1504
1660
  #
1505
1661
  # # resource actions are at /admin/posts.
1506
1662
  # resources :posts, path: "admin/posts"
1507
- def resources(*resources, &block)
1508
- options = resources.extract_options!.dup
1663
+ def resources(*resources, concerns: nil, **options, &block)
1664
+ if resources.grep(Hash).any? && (deprecated_options = resources.extract_options!)
1665
+ concerns = assign_deprecated_option(deprecated_options, :concerns, :resources) if deprecated_options.key?(:concerns)
1666
+ assign_deprecated_options(deprecated_options, options, :resources)
1667
+ end
1509
1668
 
1510
- if apply_common_behavior_for(:resources, resources, options, &block)
1669
+ if apply_common_behavior_for(:resources, resources, concerns:, **options, &block)
1511
1670
  return self
1512
1671
  end
1513
1672
 
1514
1673
  with_scope_level(:resources) do
1515
- options = apply_action_options options
1516
- resource_scope(Resource.new(resources.pop, api_only?, @scope[:shallow], options)) do
1674
+ options = apply_action_options :resources, options
1675
+ resource_scope(Resource.new(resources.pop, api_only?, @scope[:shallow], **options)) do
1517
1676
  yield if block_given?
1518
1677
 
1519
- concerns(options[:concerns]) if options[:concerns]
1678
+ concerns(*concerns) if concerns
1520
1679
 
1521
1680
  collection do
1522
1681
  get :index if parent_resource.actions.include?(:index)
@@ -1601,19 +1760,19 @@ module ActionDispatch
1601
1760
  if shallow? && shallow_nesting_depth >= 1
1602
1761
  shallow_scope do
1603
1762
  path_scope(parent_resource.nested_scope) do
1604
- scope(nested_options, &block)
1763
+ scope(**nested_options, &block)
1605
1764
  end
1606
1765
  end
1607
1766
  else
1608
1767
  path_scope(parent_resource.nested_scope) do
1609
- scope(nested_options, &block)
1768
+ scope(**nested_options, &block)
1610
1769
  end
1611
1770
  end
1612
1771
  end
1613
1772
  end
1614
1773
 
1615
1774
  # See ActionDispatch::Routing::Mapper::Scoping#namespace.
1616
- def namespace(path, options = {})
1775
+ def namespace(name, deprecated_options = nil, as: DEFAULT, path: DEFAULT, shallow_path: DEFAULT, shallow_prefix: DEFAULT, **options, &block)
1617
1776
  if resource_scope?
1618
1777
  nested { super }
1619
1778
  else
@@ -1673,40 +1832,58 @@ module ActionDispatch
1673
1832
  # Matches a URL pattern to one or more routes. For more information, see
1674
1833
  # [match](rdoc-ref:Base#match).
1675
1834
  #
1676
- # match 'path' => 'controller#action', via: :patch
1677
1835
  # match 'path', to: 'controller#action', via: :post
1678
- # match 'path', 'otherpath', on: :member, via: :get
1679
- def match(path, *rest, &block)
1680
- if rest.empty? && Hash === path
1681
- options = path
1682
- path, to = options.find { |name, _value| name.is_a?(String) }
1683
-
1684
- raise ArgumentError, "Route path not specified" if path.nil?
1836
+ # match 'otherpath', on: :member, via: :get
1837
+ def match(*path_or_actions, as: DEFAULT, via: nil, to: nil, controller: nil, action: nil, on: nil, defaults: nil, constraints: nil, anchor: nil, format: nil, path: nil, internal: nil, **mapping, &block)
1838
+ if path_or_actions.grep(Hash).any? && (deprecated_options = path_or_actions.extract_options!)
1839
+ as = assign_deprecated_option(deprecated_options, :as, :match) if deprecated_options.key?(:as)
1840
+ via ||= assign_deprecated_option(deprecated_options, :via, :match)
1841
+ to ||= assign_deprecated_option(deprecated_options, :to, :match)
1842
+ controller ||= assign_deprecated_option(deprecated_options, :controller, :match)
1843
+ action ||= assign_deprecated_option(deprecated_options, :action, :match)
1844
+ on ||= assign_deprecated_option(deprecated_options, :on, :match)
1845
+ defaults ||= assign_deprecated_option(deprecated_options, :defaults, :match)
1846
+ constraints ||= assign_deprecated_option(deprecated_options, :constraints, :match)
1847
+ anchor = assign_deprecated_option(deprecated_options, :anchor, :match) if deprecated_options.key?(:anchor)
1848
+ format = assign_deprecated_option(deprecated_options, :format, :match) if deprecated_options.key?(:format)
1849
+ path ||= assign_deprecated_option(deprecated_options, :path, :match)
1850
+ internal ||= assign_deprecated_option(deprecated_options, :internal, :match)
1851
+ assign_deprecated_options(deprecated_options, mapping, :match)
1852
+ end
1853
+
1854
+ raise ArgumentError, "Wrong number of arguments (expect 1, got #{path_or_actions.count})" if path_or_actions.count > 1
1855
+
1856
+ if path_or_actions.none? && mapping.any?
1857
+ hash_path, hash_to = mapping.find { |key, _| key.is_a?(String) }
1858
+ if hash_path.nil?
1859
+ raise ArgumentError, "Route path not specified"
1860
+ else
1861
+ mapping.delete(hash_path)
1862
+ end
1685
1863
 
1686
- case to
1687
- when Symbol
1688
- options[:action] = to
1689
- when String
1690
- if to.include?("#")
1691
- options[:to] = to
1864
+ if hash_path
1865
+ path_or_actions.push hash_path
1866
+ case hash_to
1867
+ when Symbol
1868
+ action ||= hash_to
1869
+ when String
1870
+ if hash_to.include?("#")
1871
+ to ||= hash_to
1872
+ else
1873
+ controller ||= hash_to
1874
+ end
1692
1875
  else
1693
- options[:controller] = to
1876
+ to ||= hash_to
1694
1877
  end
1695
- else
1696
- options[:to] = to
1697
1878
  end
1698
-
1699
- options.delete(path)
1700
- paths = [path]
1701
- else
1702
- options = rest.pop || {}
1703
- paths = [path] + rest
1704
1879
  end
1705
1880
 
1706
- if options.key?(:defaults)
1707
- defaults(options.delete(:defaults)) { map_match(paths, options, &block) }
1708
- else
1709
- map_match(paths, options, &block)
1881
+ path_or_actions.each do |path_or_action|
1882
+ if defaults
1883
+ defaults(defaults) { map_match(path_or_action, as:, via:, to:, controller:, action:, on:, constraints:, anchor:, format:, path:, internal:, mapping:, &block) }
1884
+ else
1885
+ map_match(path_or_action, as:, via:, to:, controller:, action:, on:, constraints:, anchor:, format:, path:, internal:, mapping:, &block)
1886
+ end
1710
1887
  end
1711
1888
  end
1712
1889
 
@@ -1748,22 +1925,21 @@ module ActionDispatch
1748
1925
  @scope[:scope_level_resource]
1749
1926
  end
1750
1927
 
1751
- def apply_common_behavior_for(method, resources, options, &block)
1928
+ def apply_common_behavior_for(method, resources, shallow: nil, **options, &block)
1752
1929
  if resources.length > 1
1753
- resources.each { |r| public_send(method, r, options, &block) }
1930
+ resources.each { |r| public_send(method, r, shallow:, **options, &block) }
1754
1931
  return true
1755
1932
  end
1756
1933
 
1757
- if options[:shallow]
1758
- options.delete(:shallow)
1759
- shallow do
1760
- public_send(method, resources.pop, options, &block)
1934
+ if shallow
1935
+ self.shallow do
1936
+ public_send(method, resources.pop, **options, &block)
1761
1937
  end
1762
1938
  return true
1763
1939
  end
1764
1940
 
1765
1941
  if resource_scope?
1766
- nested { public_send(method, resources.pop, options, &block) }
1942
+ nested { public_send(method, resources.pop, shallow:, **options, &block) }
1767
1943
  return true
1768
1944
  end
1769
1945
 
@@ -1772,9 +1948,11 @@ module ActionDispatch
1772
1948
  end
1773
1949
 
1774
1950
  scope_options = options.slice!(*RESOURCE_OPTIONS)
1951
+ scope_options[:shallow] = shallow unless shallow.nil?
1952
+
1775
1953
  unless scope_options.empty?
1776
- scope(scope_options) do
1777
- public_send(method, resources.pop, options, &block)
1954
+ scope(**scope_options) do
1955
+ public_send(method, resources.pop, **options, &block)
1778
1956
  end
1779
1957
  return true
1780
1958
  end
@@ -1782,17 +1960,32 @@ module ActionDispatch
1782
1960
  false
1783
1961
  end
1784
1962
 
1785
- def apply_action_options(options)
1963
+ def apply_action_options(method, options)
1786
1964
  return options if action_options? options
1787
- options.merge scope_action_options
1965
+ options.merge scope_action_options(method)
1788
1966
  end
1789
1967
 
1790
1968
  def action_options?(options)
1791
1969
  options[:only] || options[:except]
1792
1970
  end
1793
1971
 
1794
- def scope_action_options
1795
- @scope[:action_options] || {}
1972
+ def scope_action_options(method)
1973
+ return {} unless @scope[:action_options]
1974
+
1975
+ actions = applicable_actions_for(method)
1976
+ @scope[:action_options].dup.tap do |options|
1977
+ (options[:only] = Array(options[:only]) & actions) if options[:only]
1978
+ (options[:except] = Array(options[:except]) & actions) if options[:except]
1979
+ end
1980
+ end
1981
+
1982
+ def applicable_actions_for(method)
1983
+ case method
1984
+ when :resource
1985
+ SingletonResource.default_actions(api_only?)
1986
+ when :resources
1987
+ Resource.default_actions(api_only?)
1988
+ end
1796
1989
  end
1797
1990
 
1798
1991
  def resource_scope?
@@ -1850,9 +2043,10 @@ module ActionDispatch
1850
2043
  end
1851
2044
 
1852
2045
  def shallow_scope
1853
- scope = { as: @scope[:shallow_prefix],
1854
- path: @scope[:shallow_path] }
1855
- @scope = @scope.new scope
2046
+ @scope = @scope.new(
2047
+ as: @scope[:shallow_prefix],
2048
+ path: @scope[:shallow_path],
2049
+ )
1856
2050
 
1857
2051
  yield
1858
2052
  ensure
@@ -1874,7 +2068,7 @@ module ActionDispatch
1874
2068
  end
1875
2069
 
1876
2070
  def prefix_name_for_action(as, action)
1877
- if as
2071
+ if as && as != DEFAULT
1878
2072
  prefix = as
1879
2073
  elsif !canonical_action?(action)
1880
2074
  prefix = action
@@ -1890,7 +2084,7 @@ module ActionDispatch
1890
2084
  name_prefix = @scope[:as]
1891
2085
 
1892
2086
  if parent_resource
1893
- return nil unless as || action
2087
+ return nil unless as != DEFAULT || action
1894
2088
 
1895
2089
  collection_name = parent_resource.collection_name
1896
2090
  member_name = parent_resource.member_name
@@ -1903,7 +2097,7 @@ module ActionDispatch
1903
2097
  # If a name was not explicitly given, we check if it is valid and return nil in
1904
2098
  # case it isn't. Otherwise, we pass the invalid name forward so the underlying
1905
2099
  # router engine treats it and raises an exception.
1906
- if as.nil?
2100
+ if as == DEFAULT
1907
2101
  candidate unless !candidate.match?(/\A[_a-z]/i) || has_named_route?(candidate)
1908
2102
  else
1909
2103
  candidate
@@ -1934,42 +2128,35 @@ module ActionDispatch
1934
2128
  @scope = @scope.parent
1935
2129
  end
1936
2130
 
1937
- def map_match(paths, options)
1938
- if (on = options[:on]) && !VALID_ON_OPTIONS.include?(on)
2131
+ def map_match(path_or_action, constraints: nil, anchor: nil, format: nil, path: nil, as: DEFAULT, via: nil, to: nil, controller: nil, action: nil, on: nil, internal: nil, mapping: nil)
2132
+ if on && !VALID_ON_OPTIONS.include?(on)
1939
2133
  raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
1940
2134
  end
1941
2135
 
1942
2136
  if @scope[:to]
1943
- options[:to] ||= @scope[:to]
2137
+ to ||= @scope[:to]
1944
2138
  end
1945
2139
 
1946
2140
  if @scope[:controller] && @scope[:action]
1947
- options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
2141
+ to ||= "#{@scope[:controller]}##{@scope[:action]}"
1948
2142
  end
1949
2143
 
1950
- controller = options.delete(:controller) || @scope[:controller]
1951
- option_path = options.delete :path
1952
- to = options.delete :to
1953
- via = Mapping.check_via Array(options.delete(:via) {
1954
- @scope[:via]
1955
- })
1956
- formatted = options.delete(:format) { @scope[:format] }
1957
- anchor = options.delete(:anchor) { true }
1958
- options_constraints = options.delete(:constraints) || {}
1959
-
1960
- path_types = paths.group_by(&:class)
1961
- (path_types[String] || []).each do |_path|
1962
- route_options = options.dup
1963
- if _path && option_path
2144
+ controller ||= @scope[:controller]
2145
+ via = Mapping.check_via Array(via || @scope[:via])
2146
+ format ||= @scope[:format] if format.nil?
2147
+ anchor ||= true if anchor.nil?
2148
+ constraints ||= {}
2149
+
2150
+ case path_or_action
2151
+ when String
2152
+ if path_or_action && path
1964
2153
  raise ArgumentError, "Ambiguous route definition. Both :path and the route path were specified as strings."
1965
2154
  end
1966
- to = get_to_from_path(_path, to, route_options[:action])
1967
- decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints)
1968
- end
1969
-
1970
- (path_types[Symbol] || []).each do |action|
1971
- route_options = options.dup
1972
- decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints)
2155
+ path = path_or_action
2156
+ to = get_to_from_path(path_or_action, to, action)
2157
+ decomposed_match(path, controller, as, action, path, to, via, format, anchor, constraints, internal, mapping, on)
2158
+ when Symbol
2159
+ decomposed_match(path_or_action, controller, as, action, path, to, via, format, anchor, constraints, internal, mapping, on)
1973
2160
  end
1974
2161
 
1975
2162
  self
@@ -1990,28 +2177,28 @@ module ActionDispatch
1990
2177
  %r{^/?[-\w]+/[-\w/]+$}.match?(path)
1991
2178
  end
1992
2179
 
1993
- def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
1994
- if on = options.delete(:on)
1995
- send(on) { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
2180
+ def decomposed_match(path, controller, as, action, _path, to, via, formatted, anchor, options_constraints, internal, options_mapping, on = nil)
2181
+ if on
2182
+ send(on) { decomposed_match(path, controller, as, action, _path, to, via, formatted, anchor, options_constraints, internal, options_mapping) }
1996
2183
  else
1997
2184
  case @scope.scope_level
1998
2185
  when :resources
1999
- nested { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
2186
+ nested { decomposed_match(path, controller, as, action, _path, to, via, formatted, anchor, options_constraints, internal, options_mapping) }
2000
2187
  when :resource
2001
- member { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
2188
+ member { decomposed_match(path, controller, as, action, _path, to, via, formatted, anchor, options_constraints, internal, options_mapping) }
2002
2189
  else
2003
- add_route(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
2190
+ add_route(path, controller, as, action, _path, to, via, formatted, anchor, options_constraints, internal, options_mapping)
2004
2191
  end
2005
2192
  end
2006
2193
  end
2007
2194
 
2008
- def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints)
2195
+ def add_route(action, controller, as, options_action, _path, to, via, formatted, anchor, options_constraints, internal, options_mapping)
2009
2196
  path = path_for_action(action, _path)
2010
2197
  raise ArgumentError, "path is required" if path.blank?
2011
2198
 
2012
2199
  action = action.to_s
2013
2200
 
2014
- default_action = options.delete(:action) || @scope[:action]
2201
+ default_action = options_action || @scope[:action]
2015
2202
 
2016
2203
  if /^[\w\-\/]+$/.match?(action)
2017
2204
  default_action ||= action.tr("-", "_") unless action.include?("/")
@@ -2019,22 +2206,16 @@ module ActionDispatch
2019
2206
  action = nil
2020
2207
  end
2021
2208
 
2022
- as = if !options.fetch(:as, true) # if it's set to nil or false
2023
- options.delete(:as)
2024
- else
2025
- name_for_action(options.delete(:as), action)
2026
- end
2027
-
2028
- path = Mapping.normalize_path RFC2396_PARSER.escape(path), formatted
2029
- ast = Journey::Parser.parse path
2209
+ as = name_for_action(as, action) if as
2210
+ path = Mapping.normalize_path URI::RFC2396_PARSER.escape(path), formatted
2211
+ ast = Journey::Parser.parse path
2030
2212
 
2031
- mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
2213
+ mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, internal, options_mapping)
2032
2214
  @set.add_route(mapping, as)
2033
2215
  end
2034
2216
 
2035
2217
  def match_root_route(options)
2036
- args = ["/", { as: :root, via: :get }.merge(options)]
2037
- match(*args)
2218
+ match("/", as: :root, via: :get, **options)
2038
2219
  end
2039
2220
  end
2040
2221
 
@@ -2129,8 +2310,7 @@ module ActionDispatch
2129
2310
  # namespace :posts do
2130
2311
  # concerns :commentable
2131
2312
  # end
2132
- def concerns(*args)
2133
- options = args.extract_options!
2313
+ def concerns(*args, **options)
2134
2314
  args.flatten.each do |name|
2135
2315
  if concern = @concerns[name]
2136
2316
  concern.call(self, options)
@@ -2200,8 +2380,8 @@ module ActionDispatch
2200
2380
  end
2201
2381
 
2202
2382
  # 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:
2383
+ # of `polymorphic_url` and consequently the behavior of `link_to`, `form_with`
2384
+ # and `form_for` when passed a model instance, e.g:
2205
2385
  #
2206
2386
  # resource :basket
2207
2387
  #
@@ -2210,7 +2390,7 @@ module ActionDispatch
2210
2390
  # end
2211
2391
  #
2212
2392
  # This will now generate "/basket" when a `Basket` instance is passed to
2213
- # `link_to` or `form_for` instead of the standard "/baskets/:id".
2393
+ # `link_to`, `form_with` or `form_for` instead of the standard "/baskets/:id".
2214
2394
  #
2215
2395
  # NOTE: This custom behavior only applies to simple polymorphic URLs where a
2216
2396
  # single model instance is passed and not more complicated forms, e.g:
@@ -2267,9 +2447,9 @@ module ActionDispatch
2267
2447
 
2268
2448
  attr_reader :parent, :scope_level
2269
2449
 
2270
- def initialize(hash, parent = NULL, scope_level = nil)
2271
- @hash = hash
2450
+ def initialize(hash, parent = ROOT, scope_level = nil)
2272
2451
  @parent = parent
2452
+ @hash = parent ? parent.frame.merge(hash) : hash
2273
2453
  @scope_level = scope_level
2274
2454
  end
2275
2455
 
@@ -2282,7 +2462,7 @@ module ActionDispatch
2282
2462
  end
2283
2463
 
2284
2464
  def root?
2285
- @parent.null?
2465
+ @parent == ROOT
2286
2466
  end
2287
2467
 
2288
2468
  def resources?
@@ -2327,25 +2507,26 @@ module ActionDispatch
2327
2507
  end
2328
2508
 
2329
2509
  def [](key)
2330
- scope = find { |node| node.frame.key? key }
2331
- scope && scope.frame[key]
2510
+ frame[key]
2332
2511
  end
2333
2512
 
2513
+ def frame; @hash; end
2514
+
2334
2515
  include Enumerable
2335
2516
 
2336
2517
  def each
2337
2518
  node = self
2338
- until node.equal? NULL
2519
+ until node.equal? ROOT
2339
2520
  yield node
2340
2521
  node = node.parent
2341
2522
  end
2342
2523
  end
2343
2524
 
2344
- def frame; @hash; end
2345
-
2346
- NULL = Scope.new(nil, nil)
2525
+ ROOT = Scope.new({}, nil)
2347
2526
  end
2348
2527
 
2528
+ DEFAULT = Object.new # :nodoc:
2529
+
2349
2530
  def initialize(set) # :nodoc:
2350
2531
  @set = set
2351
2532
  @draw_paths = set.draw_paths