actionpack 7.1.5.1 → 8.1.2

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 (177) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +308 -523
  3. data/README.rdoc +1 -1
  4. data/lib/abstract_controller/asset_paths.rb +6 -2
  5. data/lib/abstract_controller/base.rb +104 -105
  6. data/lib/abstract_controller/caching/fragments.rb +50 -53
  7. data/lib/abstract_controller/caching.rb +8 -3
  8. data/lib/abstract_controller/callbacks.rb +70 -62
  9. data/lib/abstract_controller/collector.rb +7 -7
  10. data/lib/abstract_controller/deprecator.rb +2 -0
  11. data/lib/abstract_controller/error.rb +2 -0
  12. data/lib/abstract_controller/helpers.rb +71 -84
  13. data/lib/abstract_controller/logger.rb +4 -1
  14. data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
  15. data/lib/abstract_controller/rendering.rb +13 -13
  16. data/lib/abstract_controller/translation.rb +12 -13
  17. data/lib/abstract_controller/url_for.rb +8 -6
  18. data/lib/abstract_controller.rb +2 -0
  19. data/lib/action_controller/api/api_rendering.rb +2 -0
  20. data/lib/action_controller/api.rb +76 -72
  21. data/lib/action_controller/base.rb +199 -126
  22. data/lib/action_controller/caching.rb +16 -14
  23. data/lib/action_controller/deprecator.rb +2 -0
  24. data/lib/action_controller/form_builder.rb +21 -18
  25. data/lib/action_controller/log_subscriber.rb +23 -2
  26. data/lib/action_controller/metal/allow_browser.rb +133 -0
  27. data/lib/action_controller/metal/basic_implicit_render.rb +2 -0
  28. data/lib/action_controller/metal/conditional_get.rb +217 -175
  29. data/lib/action_controller/metal/content_security_policy.rb +25 -24
  30. data/lib/action_controller/metal/cookies.rb +4 -2
  31. data/lib/action_controller/metal/data_streaming.rb +72 -63
  32. data/lib/action_controller/metal/default_headers.rb +5 -3
  33. data/lib/action_controller/metal/etag_with_flash.rb +3 -1
  34. data/lib/action_controller/metal/etag_with_template_digest.rb +17 -15
  35. data/lib/action_controller/metal/exceptions.rb +16 -9
  36. data/lib/action_controller/metal/flash.rb +13 -14
  37. data/lib/action_controller/metal/head.rb +15 -11
  38. data/lib/action_controller/metal/helpers.rb +63 -55
  39. data/lib/action_controller/metal/http_authentication.rb +209 -201
  40. data/lib/action_controller/metal/implicit_render.rb +17 -15
  41. data/lib/action_controller/metal/instrumentation.rb +16 -14
  42. data/lib/action_controller/metal/live.rb +177 -128
  43. data/lib/action_controller/metal/logging.rb +6 -4
  44. data/lib/action_controller/metal/mime_responds.rb +151 -142
  45. data/lib/action_controller/metal/parameter_encoding.rb +34 -32
  46. data/lib/action_controller/metal/params_wrapper.rb +57 -59
  47. data/lib/action_controller/metal/permissions_policy.rb +22 -12
  48. data/lib/action_controller/metal/rate_limiting.rb +92 -0
  49. data/lib/action_controller/metal/redirecting.rb +213 -94
  50. data/lib/action_controller/metal/renderers.rb +78 -57
  51. data/lib/action_controller/metal/rendering.rb +111 -77
  52. data/lib/action_controller/metal/request_forgery_protection.rb +182 -143
  53. data/lib/action_controller/metal/rescue.rb +20 -9
  54. data/lib/action_controller/metal/streaming.rb +118 -195
  55. data/lib/action_controller/metal/strong_parameters.rb +720 -530
  56. data/lib/action_controller/metal/testing.rb +2 -0
  57. data/lib/action_controller/metal/url_for.rb +17 -15
  58. data/lib/action_controller/metal.rb +86 -60
  59. data/lib/action_controller/railtie.rb +36 -15
  60. data/lib/action_controller/railties/helpers.rb +2 -0
  61. data/lib/action_controller/renderer.rb +41 -36
  62. data/lib/action_controller/structured_event_subscriber.rb +116 -0
  63. data/lib/action_controller/template_assertions.rb +4 -2
  64. data/lib/action_controller/test_case.rb +160 -131
  65. data/lib/action_controller.rb +5 -1
  66. data/lib/action_dispatch/constants.rb +8 -0
  67. data/lib/action_dispatch/deprecator.rb +2 -0
  68. data/lib/action_dispatch/http/cache.rb +163 -35
  69. data/lib/action_dispatch/http/content_disposition.rb +2 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +54 -39
  71. data/lib/action_dispatch/http/filter_parameters.rb +14 -8
  72. data/lib/action_dispatch/http/filter_redirect.rb +22 -1
  73. data/lib/action_dispatch/http/headers.rb +22 -22
  74. data/lib/action_dispatch/http/mime_negotiation.rb +89 -41
  75. data/lib/action_dispatch/http/mime_type.rb +25 -21
  76. data/lib/action_dispatch/http/mime_types.rb +3 -0
  77. data/lib/action_dispatch/http/param_builder.rb +187 -0
  78. data/lib/action_dispatch/http/param_error.rb +26 -0
  79. data/lib/action_dispatch/http/parameters.rb +14 -12
  80. data/lib/action_dispatch/http/permissions_policy.rb +25 -36
  81. data/lib/action_dispatch/http/query_parser.rb +55 -0
  82. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  83. data/lib/action_dispatch/http/request.rb +141 -92
  84. data/lib/action_dispatch/http/response.rb +137 -77
  85. data/lib/action_dispatch/http/upload.rb +18 -16
  86. data/lib/action_dispatch/http/url.rb +187 -89
  87. data/lib/action_dispatch/journey/formatter.rb +21 -9
  88. data/lib/action_dispatch/journey/gtg/builder.rb +4 -3
  89. data/lib/action_dispatch/journey/gtg/simulator.rb +34 -11
  90. data/lib/action_dispatch/journey/gtg/transition_table.rb +47 -53
  91. data/lib/action_dispatch/journey/nfa/dot.rb +2 -0
  92. data/lib/action_dispatch/journey/nodes/node.rb +8 -6
  93. data/lib/action_dispatch/journey/parser.rb +99 -195
  94. data/lib/action_dispatch/journey/path/pattern.rb +4 -1
  95. data/lib/action_dispatch/journey/route.rb +54 -38
  96. data/lib/action_dispatch/journey/router/utils.rb +22 -27
  97. data/lib/action_dispatch/journey/router.rb +63 -83
  98. data/lib/action_dispatch/journey/routes.rb +11 -2
  99. data/lib/action_dispatch/journey/scanner.rb +46 -42
  100. data/lib/action_dispatch/journey/visitors.rb +57 -23
  101. data/lib/action_dispatch/journey/visualizer/fsm.js +4 -6
  102. data/lib/action_dispatch/journey.rb +2 -0
  103. data/lib/action_dispatch/log_subscriber.rb +7 -1
  104. data/lib/action_dispatch/middleware/actionable_exceptions.rb +2 -0
  105. data/lib/action_dispatch/middleware/assume_ssl.rb +8 -5
  106. data/lib/action_dispatch/middleware/callbacks.rb +3 -1
  107. data/lib/action_dispatch/middleware/cookies.rb +125 -106
  108. data/lib/action_dispatch/middleware/debug_exceptions.rb +37 -8
  109. data/lib/action_dispatch/middleware/debug_locks.rb +15 -13
  110. data/lib/action_dispatch/middleware/debug_view.rb +13 -5
  111. data/lib/action_dispatch/middleware/exception_wrapper.rb +18 -23
  112. data/lib/action_dispatch/middleware/executor.rb +19 -4
  113. data/lib/action_dispatch/middleware/flash.rb +63 -51
  114. data/lib/action_dispatch/middleware/host_authorization.rb +17 -15
  115. data/lib/action_dispatch/middleware/public_exceptions.rb +14 -12
  116. data/lib/action_dispatch/middleware/reloader.rb +5 -3
  117. data/lib/action_dispatch/middleware/remote_ip.rb +87 -77
  118. data/lib/action_dispatch/middleware/request_id.rb +16 -10
  119. data/lib/action_dispatch/middleware/server_timing.rb +4 -2
  120. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -0
  121. data/lib/action_dispatch/middleware/session/cache_store.rb +30 -8
  122. data/lib/action_dispatch/middleware/session/cookie_store.rb +27 -26
  123. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +7 -3
  124. data/lib/action_dispatch/middleware/show_exceptions.rb +16 -16
  125. data/lib/action_dispatch/middleware/ssl.rb +53 -40
  126. data/lib/action_dispatch/middleware/stack.rb +11 -10
  127. data/lib/action_dispatch/middleware/static.rb +33 -31
  128. data/lib/action_dispatch/middleware/templates/rescues/_copy_button.html.erb +1 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +3 -5
  130. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +9 -5
  131. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +1 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +1 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +4 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +50 -0
  136. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +1 -0
  137. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -0
  138. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -0
  139. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -0
  140. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -0
  141. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +1 -1
  142. data/lib/action_dispatch/railtie.rb +23 -3
  143. data/lib/action_dispatch/request/session.rb +24 -21
  144. data/lib/action_dispatch/request/utils.rb +11 -3
  145. data/lib/action_dispatch/routing/endpoint.rb +2 -0
  146. data/lib/action_dispatch/routing/inspector.rb +85 -60
  147. data/lib/action_dispatch/routing/mapper.rb +1031 -851
  148. data/lib/action_dispatch/routing/polymorphic_routes.rb +69 -62
  149. data/lib/action_dispatch/routing/redirection.rb +47 -39
  150. data/lib/action_dispatch/routing/route_set.rb +79 -56
  151. data/lib/action_dispatch/routing/routes_proxy.rb +7 -4
  152. data/lib/action_dispatch/routing/url_for.rb +130 -125
  153. data/lib/action_dispatch/routing.rb +150 -148
  154. data/lib/action_dispatch/structured_event_subscriber.rb +20 -0
  155. data/lib/action_dispatch/system_test_case.rb +91 -81
  156. data/lib/action_dispatch/system_testing/browser.rb +16 -23
  157. data/lib/action_dispatch/system_testing/driver.rb +2 -0
  158. data/lib/action_dispatch/system_testing/server.rb +2 -0
  159. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +34 -23
  160. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +2 -0
  161. data/lib/action_dispatch/testing/assertion_response.rb +9 -7
  162. data/lib/action_dispatch/testing/assertions/response.rb +52 -25
  163. data/lib/action_dispatch/testing/assertions/routing.rb +168 -87
  164. data/lib/action_dispatch/testing/assertions.rb +2 -0
  165. data/lib/action_dispatch/testing/integration.rb +233 -223
  166. data/lib/action_dispatch/testing/request_encoder.rb +11 -9
  167. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  168. data/lib/action_dispatch/testing/test_process.rb +11 -8
  169. data/lib/action_dispatch/testing/test_request.rb +3 -1
  170. data/lib/action_dispatch/testing/test_response.rb +27 -26
  171. data/lib/action_dispatch.rb +36 -32
  172. data/lib/action_pack/gem_version.rb +6 -4
  173. data/lib/action_pack/version.rb +3 -1
  174. data/lib/action_pack.rb +17 -16
  175. metadata +36 -32
  176. data/lib/action_dispatch/journey/parser.y +0 -50
  177. data/lib/action_dispatch/journey/parser_extras.rb +0 -31
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "active_support/core_ext/hash/slice"
4
6
  require "active_support/core_ext/enumerable"
5
7
  require "active_support/core_ext/array/extract_options"
@@ -31,10 +33,10 @@ module ActionDispatch
31
33
  CALL = ->(app, req) { app.call req.env }
32
34
 
33
35
  def initialize(app, constraints, strategy)
34
- # Unwrap Constraints objects. I don't actually think it's possible
35
- # to pass a Constraints object to this constructor, but there were
36
- # multiple places that kept testing children of this object. I
37
- # *think* they were just being defensive, but I have no idea.
36
+ # Unwrap Constraints objects. I don't actually think it's possible to pass a
37
+ # Constraints object to this constructor, but there were multiple places that
38
+ # kept testing children of this object. I **think** they were just being
39
+ # defensive, but I have no idea.
38
40
  if app.is_a?(self.class)
39
41
  constraints += app.constraints
40
42
  app = app.app
@@ -85,7 +87,7 @@ module ActionDispatch
85
87
  attr_reader :path, :requirements, :defaults, :to, :default_controller,
86
88
  :default_action, :required_defaults, :ast, :scope_options
87
89
 
88
- 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)
89
91
  scope_params = {
90
92
  blocks: scope[:blocks] || [],
91
93
  constraints: scope[:constraints] || {},
@@ -96,7 +98,7 @@ module ActionDispatch
96
98
 
97
99
  new set: set, ast: ast, controller: controller, default_action: default_action,
98
100
  to: to, formatted: formatted, via: via, options_constraints: options_constraints,
99
- 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)
100
102
  end
101
103
 
102
104
  def self.check_via(via)
@@ -127,7 +129,7 @@ module ActionDispatch
127
129
  format != false && !path.match?(OPTIONAL_FORMAT_REGEX)
128
130
  end
129
131
 
130
- 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:)
131
133
  @defaults = scope_params[:defaults]
132
134
  @set = set
133
135
  @to = intern(to)
@@ -135,7 +137,7 @@ module ActionDispatch
135
137
  @default_action = intern(default_action)
136
138
  @anchor = anchor
137
139
  @via = via
138
- @internal = options.delete(:internal)
140
+ @internal = internal
139
141
  @scope_options = scope_params[:options]
140
142
  ast = Journey::Ast.new(ast, formatted)
141
143
 
@@ -181,7 +183,7 @@ module ActionDispatch
181
183
  def make_route(name, precedence)
182
184
  Journey::Route.new(name: name, app: application, path: path, constraints: conditions,
183
185
  required_defaults: required_defaults, defaults: defaults,
184
- request_method_match: request_method, precedence: precedence,
186
+ via: @via, precedence: precedence,
185
187
  scope_options: scope_options, internal: @internal, source_location: route_source_location)
186
188
  end
187
189
 
@@ -202,11 +204,6 @@ module ActionDispatch
202
204
  end
203
205
  private :build_conditions
204
206
 
205
- def request_method
206
- @via.map { |x| Journey::Route.verb_matcher(x) }
207
- end
208
- private :request_method
209
-
210
207
  private
211
208
  def intern(object)
212
209
  object.is_a?(String) ? -object : object
@@ -219,7 +216,7 @@ module ActionDispatch
219
216
  # Add a default constraint for :controller path segments that matches namespaced
220
217
  # controllers with default routes like :controller/:action/:id(.:format), e.g:
221
218
  # GET /admin/products/show/1
222
- # => { controller: 'admin/products', action: 'show', id: '1' }
219
+ # # > { controller: 'admin/products', action: 'show', id: '1' }
223
220
  options[:controller] ||= /.+?/
224
221
  end
225
222
 
@@ -373,55 +370,36 @@ module ActionDispatch
373
370
  Routing::RouteSet::Dispatcher.new raise_on_name_error
374
371
  end
375
372
 
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)
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)
382
378
 
383
- cleaned_path = Mapper.backtrace_cleaner.clean_frame(location.path)
384
- next if cleaned_path.nil?
379
+ cleaned_path = Mapper.backtrace_cleaner.clean_frame(location.path)
380
+ next if cleaned_path.nil?
385
381
 
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
382
+ return "#{cleaned_path}:#{location.lineno}"
404
383
  end
384
+ nil
405
385
  end
406
386
  end
407
387
  end
408
388
 
409
- # Invokes Journey::Router::Utils.normalize_path, then ensures that
410
- # /(:locale) becomes (/:locale). Except for root cases, where the
411
- # former is the correct one.
389
+ # Invokes Journey::Router::Utils.normalize_path, then ensures that /(:locale)
390
+ # becomes (/:locale). Except for root cases, where the former is the correct
391
+ # one.
412
392
  def self.normalize_path(path)
413
393
  path = Journey::Router::Utils.normalize_path(path)
414
394
 
415
395
  # the path for a root URL at this point can be something like
416
396
  # "/(/:locale)(/:platform)/(:browser)", and we would want
417
- # "/(:locale)(/:platform)(/:browser)"
418
-
419
- # reverse "/(", "/((" etc to "(/", "((/" etc
397
+ # "/(:locale)(/:platform)(/:browser)" reverse "/(", "/((" etc to "(/", "((/" etc
420
398
  path.gsub!(%r{/(\(+)/?}, '\1/')
421
- # if a path is all optional segments, change the leading "(/" back to
422
- # "/(" so it evaluates to "/" when interpreted with no options.
423
- # Unless, however, at least one secondary segment consists of a static
424
- # part, ex. "(/:locale)(/pages/:page)"
399
+ # if a path is all optional segments, change the leading "(/" back to "/(" so it
400
+ # evaluates to "/" when interpreted with no options. Unless, however, at least
401
+ # one secondary segment consists of a static part, ex.
402
+ # "(/:locale)(/pages/:page)"
425
403
  path.sub!(%r{^(\(+)/}, '/\1') if %r{^(\(+[^)]+\))(\(+/:[^)]+\))*$}.match?(path)
426
404
  path
427
405
  end
@@ -433,217 +411,223 @@ module ActionDispatch
433
411
  module Base
434
412
  # Matches a URL pattern to one or more routes.
435
413
  #
436
- # You should not use the +match+ method in your router
437
- # without specifying an HTTP method.
414
+ # You should not use the `match` method in your router without specifying an
415
+ # HTTP method.
438
416
  #
439
417
  # If you want to expose your action to both GET and POST, use:
440
418
  #
441
- # # sets :controller, :action, and :id in params
442
- # match ':controller/:action/:id', via: [:get, :post]
419
+ # # sets :controller, :action, and :id in params
420
+ # match ':controller/:action/:id', via: [:get, :post]
443
421
  #
444
- # Note that +:controller+, +:action+, and +:id+ are interpreted as URL
445
- # query parameters and thus available through +params+ in an action.
422
+ # Note that `:controller`, `:action`, and `:id` are interpreted as URL query
423
+ # parameters and thus available through `params` in an action.
446
424
  #
447
- # If you want to expose your action to GET, use +get+ in the router:
425
+ # If you want to expose your action to GET, use `get` in the router:
448
426
  #
449
427
  # Instead of:
450
428
  #
451
- # match ":controller/:action/:id"
429
+ # match ":controller/:action/:id"
452
430
  #
453
431
  # Do:
454
432
  #
455
- # get ":controller/:action/:id"
433
+ # get ":controller/:action/:id"
456
434
  #
457
- # Two of these symbols are special, +:controller+ maps to the controller
458
- # and +:action+ to the controller's action. A pattern can also map
459
- # wildcard segments (globs) to params:
435
+ # Two of these symbols are special, `:controller` maps to the controller and
436
+ # `:action` to the controller's action. A pattern can also map wildcard segments
437
+ # (globs) to params:
460
438
  #
461
- # get 'songs/*category/:title', to: 'songs#show'
439
+ # get 'songs/*category/:title', to: 'songs#show'
462
440
  #
463
- # # 'songs/rock/classic/stairway-to-heaven' sets
464
- # # params[:category] = 'rock/classic'
465
- # # params[:title] = 'stairway-to-heaven'
441
+ # # 'songs/rock/classic/stairway-to-heaven' sets
442
+ # # params[:category] = 'rock/classic'
443
+ # # params[:title] = 'stairway-to-heaven'
466
444
  #
467
- # To match a wildcard parameter, it must have a name assigned to it.
468
- # Without a variable name to attach the glob parameter to, the route
469
- # can't be parsed.
445
+ # To match a wildcard parameter, it must have a name assigned to it. Without a
446
+ # variable name to attach the glob parameter to, the route can't be parsed.
470
447
  #
471
- # When a pattern points to an internal route, the route's +:action+ and
472
- # +:controller+ should be set in options or hash shorthand. Examples:
448
+ # When a pattern points to an internal route, the route's `:action` and
449
+ # `:controller` should be set in options or hash shorthand. Examples:
473
450
  #
474
- # match 'photos/:id' => 'photos#show', via: :get
475
- # match 'photos/:id', to: 'photos#show', via: :get
476
- # match 'photos/:id', controller: 'photos', action: 'show', via: :get
451
+ # match 'photos/:id', to: 'photos#show', via: :get
452
+ # match 'photos/:id', controller: 'photos', action: 'show', via: :get
477
453
  #
478
- # A pattern can also point to a +Rack+ endpoint i.e. anything that
479
- # responds to +call+:
454
+ # A pattern can also point to a `Rack` endpoint i.e. anything that responds to
455
+ # `call`:
480
456
  #
481
- # match 'photos/:id', to: -> (hash) { [200, {}, ["Coming soon"]] }, via: :get
482
- # match 'photos/:id', to: PhotoRackApp, via: :get
483
- # # Yes, controller actions are just rack endpoints
484
- # match 'photos/:id', to: PhotosController.action(:show), via: :get
457
+ # match 'photos/:id', to: -> (hash) { [200, {}, ["Coming soon"]] }, via: :get
458
+ # match 'photos/:id', to: PhotoRackApp, via: :get
459
+ # # Yes, controller actions are just rack endpoints
460
+ # match 'photos/:id', to: PhotosController.action(:show), via: :get
485
461
  #
486
462
  # Because requesting various HTTP verbs with a single action has security
487
- # implications, you must either specify the actions in
488
- # the via options or use one of the HttpHelpers[rdoc-ref:HttpHelpers]
489
- # instead +match+
463
+ # implications, you must either specify the actions in the via options or use
464
+ # one of the [HttpHelpers](rdoc-ref:HttpHelpers) instead `match`
490
465
  #
491
- # === Options
466
+ # ### Options
492
467
  #
493
468
  # Any options not seen here are passed on as params with the URL.
494
469
  #
495
- # [:controller]
496
- # The route's controller.
470
+ # :controller
471
+ # : The route's controller.
497
472
  #
498
- # [:action]
499
- # The route's action.
473
+ # :action
474
+ # : The route's action.
500
475
  #
501
- # [:param]
502
- # Overrides the default resource identifier +:id+ (name of the
503
- # dynamic segment used to generate the routes).
504
- # You can access that segment from your controller using
505
- # <tt>params[<:param>]</tt>.
506
- # In your router:
476
+ # :param
477
+ # : Overrides the default resource identifier `:id` (name of the dynamic
478
+ # segment used to generate the routes). You can access that segment from
479
+ # your controller using `params[<:param>]`. In your router:
507
480
  #
508
- # resources :users, param: :name
481
+ # resources :users, param: :name
509
482
  #
510
- # The +users+ resource here will have the following routes generated for it:
483
+ # The `users` resource here will have the following routes generated for it:
511
484
  #
512
- # GET /users(.:format)
513
- # POST /users(.:format)
514
- # GET /users/new(.:format)
515
- # GET /users/:name/edit(.:format)
516
- # GET /users/:name(.:format)
517
- # PATCH/PUT /users/:name(.:format)
518
- # DELETE /users/:name(.:format)
485
+ # GET /users(.:format)
486
+ # POST /users(.:format)
487
+ # GET /users/new(.:format)
488
+ # GET /users/:name/edit(.:format)
489
+ # GET /users/:name(.:format)
490
+ # PATCH/PUT /users/:name(.:format)
491
+ # DELETE /users/:name(.:format)
519
492
  #
520
- # You can override <tt>ActiveRecord::Base#to_param</tt> of a related
521
- # model to construct a URL:
493
+ # You can override `ActiveRecord::Base#to_param` of a related model to
494
+ # construct a URL:
522
495
  #
523
- # class User < ActiveRecord::Base
524
- # def to_param
525
- # name
526
- # end
527
- # end
496
+ # class User < ActiveRecord::Base
497
+ # def to_param
498
+ # name
499
+ # end
500
+ # end
528
501
  #
529
- # user = User.find_by(name: 'Phusion')
530
- # user_path(user) # => "/users/Phusion"
502
+ # user = User.find_by(name: 'Phusion')
503
+ # user_path(user) # => "/users/Phusion"
531
504
  #
532
- # [:path]
533
- # The path prefix for the routes.
505
+ # :path
506
+ # : The path prefix for the routes.
534
507
  #
535
- # [:module]
536
- # The namespace for :controller.
508
+ # :module
509
+ # : The namespace for :controller.
537
510
  #
538
- # match 'path', to: 'c#a', module: 'sekret', controller: 'posts', via: :get
539
- # # => Sekret::PostsController
511
+ # match 'path', to: 'c#a', module: 'sekret', controller: 'posts', via: :get
512
+ # # => Sekret::PostsController
540
513
  #
541
- # See <tt>Scoping#namespace</tt> for its scope equivalent.
514
+ # See `Scoping#namespace` for its scope equivalent.
542
515
  #
543
- # [:as]
544
- # The name used to generate routing helpers.
516
+ # :as
517
+ # : The name used to generate routing helpers.
545
518
  #
546
- # [:via]
547
- # Allowed HTTP verb(s) for route.
519
+ # :via
520
+ # : Allowed HTTP verb(s) for route.
548
521
  #
549
- # match 'path', to: 'c#a', via: :get
550
- # match 'path', to: 'c#a', via: [:get, :post]
551
- # match 'path', to: 'c#a', via: :all
522
+ # match 'path', to: 'c#a', via: :get
523
+ # match 'path', to: 'c#a', via: [:get, :post]
524
+ # match 'path', to: 'c#a', via: :all
552
525
  #
553
- # [:to]
554
- # Points to a +Rack+ endpoint. Can be an object that responds to
555
- # +call+ or a string representing a controller's action.
526
+ # :to
527
+ # : Points to a `Rack` endpoint. Can be an object that responds to `call` or a
528
+ # string representing a controller's action.
556
529
  #
557
- # match 'path', to: 'controller#action', via: :get
558
- # match 'path', to: -> (env) { [200, {}, ["Success!"]] }, via: :get
559
- # match 'path', to: RackApp, via: :get
530
+ # match 'path', to: 'controller#action', via: :get
531
+ # match 'path', to: -> (env) { [200, {}, ["Success!"]] }, via: :get
532
+ # match 'path', to: RackApp, via: :get
560
533
  #
561
- # [:on]
562
- # Shorthand for wrapping routes in a specific RESTful context. Valid
563
- # values are +:member+, +:collection+, and +:new+. Only use within
564
- # <tt>resource(s)</tt> block. For example:
534
+ # :on
535
+ # : Shorthand for wrapping routes in a specific RESTful context. Valid values
536
+ # are `:member`, `:collection`, and `:new`. Only use within `resource(s)`
537
+ # block. For example:
565
538
  #
566
- # resource :bar do
567
- # match 'foo', to: 'c#a', on: :member, via: [:get, :post]
568
- # end
539
+ # resource :bar do
540
+ # match 'foo', to: 'c#a', on: :member, via: [:get, :post]
541
+ # end
569
542
  #
570
- # Is equivalent to:
543
+ # Is equivalent to:
571
544
  #
572
- # resource :bar do
573
- # member do
574
- # match 'foo', to: 'c#a', via: [:get, :post]
575
- # end
576
- # end
545
+ # resource :bar do
546
+ # member do
547
+ # match 'foo', to: 'c#a', via: [:get, :post]
548
+ # end
549
+ # end
577
550
  #
578
- # [:constraints]
579
- # Constrains parameters with a hash of regular expressions
580
- # or an object that responds to <tt>matches?</tt>. In addition, constraints
581
- # other than path can also be specified with any object
582
- # that responds to <tt>===</tt> (e.g. String, Array, Range, etc.).
551
+ # :constraints
552
+ # : Constrains parameters with a hash of regular expressions or an object that
553
+ # responds to `matches?`. In addition, constraints other than path can also
554
+ # be specified with any object that responds to `===` (e.g. String, Array,
555
+ # Range, etc.).
583
556
  #
584
- # match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via: :get
557
+ # match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via: :get
585
558
  #
586
- # match 'json_only', constraints: { format: 'json' }, via: :get
559
+ # match 'json_only', constraints: { format: 'json' }, via: :get
587
560
  #
588
- # class PermitList
589
- # def matches?(request) request.remote_ip == '1.2.3.4' end
590
- # end
591
- # match 'path', to: 'c#a', constraints: PermitList.new, via: :get
561
+ # class PermitList
562
+ # def matches?(request) request.remote_ip == '1.2.3.4' end
563
+ # end
564
+ # match 'path', to: 'c#a', constraints: PermitList.new, via: :get
565
+ #
566
+ # See `Scoping#constraints` for more examples with its scope equivalent.
592
567
  #
593
- # See <tt>Scoping#constraints</tt> for more examples with its scope
594
- # equivalent.
568
+ # :defaults
569
+ # : Sets defaults for parameters
595
570
  #
596
- # [:defaults]
597
- # Sets defaults for parameters
571
+ # # Sets params[:format] to 'jpg' by default
572
+ # match 'path', to: 'c#a', defaults: { format: 'jpg' }, via: :get
598
573
  #
599
- # # Sets params[:format] to 'jpg' by default
600
- # match 'path', to: 'c#a', defaults: { format: 'jpg' }, via: :get
574
+ # See `Scoping#defaults` for its scope equivalent.
601
575
  #
602
- # See <tt>Scoping#defaults</tt> for its scope equivalent.
576
+ # :anchor
577
+ # : Boolean to anchor a `match` pattern. Default is true. When set to false,
578
+ # the pattern matches any request prefixed with the given path.
603
579
  #
604
- # [:anchor]
605
- # Boolean to anchor a <tt>match</tt> pattern. Default is true. When set to
606
- # false, the pattern matches any request prefixed with the given path.
580
+ # # Matches any request starting with 'path'
581
+ # match 'path', to: 'c#a', anchor: false, via: :get
607
582
  #
608
- # # Matches any request starting with 'path'
609
- # match 'path', to: 'c#a', anchor: false, via: :get
583
+ # :format
584
+ # : Allows you to specify the default value for optional `format` segment or
585
+ # disable it by supplying `false`.
610
586
  #
611
- # [:format]
612
- # Allows you to specify the default value for optional +format+
613
- # segment or disable it by supplying +false+.
614
587
  def match(path, options = nil)
615
588
  end
616
589
 
617
590
  # Mount a Rack-based application to be used within the application.
618
591
  #
619
- # mount SomeRackApp, at: "some_route"
620
- #
621
- # Alternatively:
622
- #
623
- # mount(SomeRackApp => "some_route")
592
+ # mount SomeRackApp, at: "some_route"
624
593
  #
625
- # For options, see +match+, as +mount+ uses it internally.
594
+ # For options, see `match`, as `mount` uses it internally.
626
595
  #
627
- # All mounted applications come with routing helpers to access them.
628
- # These are named after the class specified, so for the above example
629
- # the helper is either +some_rack_app_path+ or +some_rack_app_url+.
630
- # To customize this helper's name, use the +:as+ option:
596
+ # All mounted applications come with routing helpers to access them. These are
597
+ # named after the class specified, so for the above example the helper is either
598
+ # `some_rack_app_path` or `some_rack_app_url`. To customize this helper's name,
599
+ # use the `:as` option:
631
600
  #
632
- # mount(SomeRackApp => "some_route", as: "exciting")
601
+ # mount(SomeRackApp, at: "some_route", as: "exciting")
633
602
  #
634
- # This will generate the +exciting_path+ and +exciting_url+ helpers
635
- # which can be used to navigate to this mounted app.
636
- def mount(app, options = nil)
637
- if options
638
- path = options.delete(:at)
639
- elsif Hash === app
640
- options = app
641
- app, path = options.find { |k, _| k.respond_to?(:call) }
642
- options.delete(app) if app
603
+ # This will generate the `exciting_path` and `exciting_url` helpers which can be
604
+ # used to navigate to this mounted 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
643
627
  end
644
628
 
645
629
  raise ArgumentError, "A rack application must be specified" unless app.respond_to?(:call)
646
- raise ArgumentError, <<~MSG unless path
630
+ raise ArgumentError, <<~MSG unless path_or_action
647
631
  Must be called with mount point
648
632
 
649
633
  mount SomeRackApp, at: "some_route"
@@ -652,12 +636,12 @@ module ActionDispatch
652
636
  MSG
653
637
 
654
638
  rails_app = rails_app? app
655
- options[:as] ||= app_name(app, rails_app)
639
+ as = app_name(app, rails_app) if as == DEFAULT
656
640
 
657
- target_as = name_for_action(options[:as], path)
658
- options[:via] ||= :all
641
+ target_as = name_for_action(as, path_or_action)
642
+ via ||= :all
659
643
 
660
- 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)
661
645
 
662
646
  define_generate_prefix(app, target_as) if rails_app
663
647
  self
@@ -669,7 +653,7 @@ module ActionDispatch
669
653
  alias_method :default_url_options, :default_url_options=
670
654
 
671
655
  def with_default_scope(scope, &block)
672
- scope(scope) do
656
+ scope(**scope) do
673
657
  instance_exec(&block)
674
658
  end
675
659
  end
@@ -680,6 +664,24 @@ module ActionDispatch
680
664
  end
681
665
 
682
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
+
683
685
  def rails_app?(app)
684
686
  app.is_a?(Class) && app < Rails::Railtie
685
687
  end
@@ -706,7 +708,8 @@ module ActionDispatch
706
708
  prefix_options.reverse_merge!(options[:_recall].slice(*_route.segment_keys))
707
709
  end
708
710
 
709
- # We must actually delete prefix segment keys to avoid passing them to next url_for.
711
+ # We must actually delete prefix segment keys to avoid passing them to next
712
+ # url_for.
710
713
  _route.segment_keys.each { |k| options.delete(k) }
711
714
  _url_helpers.public_send("#{name}_path", prefix_options)
712
715
  end
@@ -728,150 +731,268 @@ module ActionDispatch
728
731
  end
729
732
 
730
733
  module HttpHelpers
731
- # Define a route that only recognizes HTTP GET.
732
- # For supported arguments, see match[rdoc-ref:Base#match]
733
- #
734
- # get 'bacon', to: 'food#bacon'
735
- def get(*args, &block)
736
- map_method(:get, args, &block)
734
+ # Define a route that only recognizes HTTP GET. For supported arguments, see
735
+ # [match](rdoc-ref:Base#match)
736
+ #
737
+ # get 'bacon', to: 'food#bacon'
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
737
756
  end
738
757
 
739
- # Define a route that only recognizes HTTP POST.
740
- # For supported arguments, see match[rdoc-ref:Base#match]
741
- #
742
- # post 'bacon', to: 'food#bacon'
743
- def post(*args, &block)
744
- map_method(:post, args, &block)
758
+ # Define a route that only recognizes HTTP POST. For supported arguments, see
759
+ # [match](rdoc-ref:Base#match)
760
+ #
761
+ # post 'bacon', to: 'food#bacon'
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
745
780
  end
746
781
 
747
- # Define a route that only recognizes HTTP PATCH.
748
- # For supported arguments, see match[rdoc-ref:Base#match]
749
- #
750
- # patch 'bacon', to: 'food#bacon'
751
- def patch(*args, &block)
752
- map_method(:patch, args, &block)
782
+ # Define a route that only recognizes HTTP PATCH. For supported arguments, see
783
+ # [match](rdoc-ref:Base#match)
784
+ #
785
+ # patch 'bacon', to: 'food#bacon'
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
753
804
  end
754
805
 
755
- # Define a route that only recognizes HTTP PUT.
756
- # For supported arguments, see match[rdoc-ref:Base#match]
757
- #
758
- # put 'bacon', to: 'food#bacon'
759
- def put(*args, &block)
760
- map_method(:put, args, &block)
806
+ # Define a route that only recognizes HTTP PUT. For supported arguments, see
807
+ # [match](rdoc-ref:Base#match)
808
+ #
809
+ # put 'bacon', to: 'food#bacon'
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
761
828
  end
762
829
 
763
- # Define a route that only recognizes HTTP DELETE.
764
- # For supported arguments, see match[rdoc-ref:Base#match]
765
- #
766
- # delete 'broccoli', to: 'food#broccoli'
767
- def delete(*args, &block)
768
- map_method(:delete, args, &block)
830
+ # Define a route that only recognizes HTTP DELETE. For supported arguments, see
831
+ # [match](rdoc-ref:Base#match)
832
+ #
833
+ # delete 'broccoli', to: 'food#broccoli'
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
769
852
  end
770
853
 
771
- # Define a route that only recognizes HTTP OPTIONS.
772
- # For supported arguments, see match[rdoc-ref:Base#match]
773
- #
774
- # options 'carrots', to: 'food#carrots'
775
- def options(*args, &block)
776
- map_method(:options, args, &block)
854
+ # Define a route that only recognizes HTTP OPTIONS. For supported arguments, see
855
+ # [match](rdoc-ref:Base#match)
856
+ #
857
+ # options 'carrots', to: 'food#carrots'
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
777
876
  end
778
877
 
779
- private
780
- def map_method(method, args, &block)
781
- options = args.extract_options!
782
- options[:via] = method
783
- match(*args, options, &block)
784
- self
785
- 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
786
903
  end
787
904
 
788
- # You may wish to organize groups of controllers under a namespace.
789
- # Most commonly, you might group a number of administrative controllers
790
- # under an +admin+ namespace. You would place these controllers under
791
- # the <tt>app/controllers/admin</tt> directory, and you can group them
792
- # together in your router:
905
+ # You may wish to organize groups of controllers under a namespace. Most
906
+ # commonly, you might group a number of administrative controllers under an
907
+ # `admin` namespace. You would place these controllers under the
908
+ # `app/controllers/admin` directory, and you can group them together in your
909
+ # router:
793
910
  #
794
- # namespace "admin" do
795
- # resources :posts, :comments
796
- # end
911
+ # namespace "admin" do
912
+ # resources :posts, :comments
913
+ # end
797
914
  #
798
915
  # This will create a number of routes for each of the posts and comments
799
- # controller. For +Admin::PostsController+, \Rails will create:
916
+ # controller. For `Admin::PostsController`, Rails will create:
800
917
  #
801
- # GET /admin/posts
802
- # GET /admin/posts/new
803
- # POST /admin/posts
804
- # GET /admin/posts/1
805
- # GET /admin/posts/1/edit
806
- # PATCH/PUT /admin/posts/1
807
- # DELETE /admin/posts/1
918
+ # GET /admin/posts
919
+ # GET /admin/posts/new
920
+ # POST /admin/posts
921
+ # GET /admin/posts/1
922
+ # GET /admin/posts/1/edit
923
+ # PATCH/PUT /admin/posts/1
924
+ # DELETE /admin/posts/1
808
925
  #
809
926
  # If you want to route /posts (without the prefix /admin) to
810
- # +Admin::PostsController+, you could use
927
+ # `Admin::PostsController`, you could use
811
928
  #
812
- # scope module: "admin" do
813
- # resources :posts
814
- # end
929
+ # scope module: "admin" do
930
+ # resources :posts
931
+ # end
815
932
  #
816
933
  # or, for a single case
817
934
  #
818
- # resources :posts, module: "admin"
935
+ # resources :posts, module: "admin"
819
936
  #
820
- # If you want to route /admin/posts to +PostsController+
821
- # (without the <tt>Admin::</tt> module prefix), you could use
937
+ # If you want to route /admin/posts to `PostsController` (without the `Admin::`
938
+ # module prefix), you could use
822
939
  #
823
- # scope "/admin" do
824
- # resources :posts
825
- # end
940
+ # scope "/admin" do
941
+ # resources :posts
942
+ # end
826
943
  #
827
944
  # or, for a single case
828
945
  #
829
- # resources :posts, path: "/admin/posts"
946
+ # resources :posts, path: "/admin/posts"
830
947
  #
831
- # In each of these cases, the named routes remain the same as if you did
832
- # not use scope. In the last case, the following paths map to
833
- # +PostsController+:
948
+ # In each of these cases, the named routes remain the same as if you did not use
949
+ # scope. In the last case, the following paths map to `PostsController`:
834
950
  #
835
- # GET /admin/posts
836
- # GET /admin/posts/new
837
- # POST /admin/posts
838
- # GET /admin/posts/1
839
- # GET /admin/posts/1/edit
840
- # PATCH/PUT /admin/posts/1
841
- # DELETE /admin/posts/1
951
+ # GET /admin/posts
952
+ # GET /admin/posts/new
953
+ # POST /admin/posts
954
+ # GET /admin/posts/1
955
+ # GET /admin/posts/1/edit
956
+ # PATCH/PUT /admin/posts/1
957
+ # DELETE /admin/posts/1
842
958
  module Scoping
843
959
  # Scopes a set of routes to the given default options.
844
960
  #
845
961
  # Take the following route definition as an example:
846
962
  #
847
- # scope path: ":account_id", as: "account" do
848
- # resources :projects
849
- # end
963
+ # scope path: ":account_id", as: "account" do
964
+ # resources :projects
965
+ # end
850
966
  #
851
- # This generates helpers such as +account_projects_path+, just like +resources+ does.
852
- # The difference here being that the routes generated are like /:account_id/projects,
853
- # rather than /accounts/:account_id/projects.
967
+ # This generates helpers such as `account_projects_path`, just like `resources`
968
+ # does. The difference here being that the routes generated are like
969
+ # /:account_id/projects, rather than /accounts/:account_id/projects.
854
970
  #
855
- # === Options
971
+ # ### Options
856
972
  #
857
- # Takes same options as <tt>Base#match</tt> and <tt>Resources#resources</tt>.
973
+ # Takes same options as `Base#match` and `Resources#resources`.
858
974
  #
859
- # # route /posts (without the prefix /admin) to +Admin::PostsController+
860
- # scope module: "admin" do
861
- # resources :posts
862
- # end
975
+ # # route /posts (without the prefix /admin) to Admin::PostsController
976
+ # scope module: "admin" do
977
+ # resources :posts
978
+ # end
863
979
  #
864
- # # prefix the posts resource's requests with '/admin'
865
- # scope path: "/admin" do
866
- # resources :posts
867
- # end
980
+ # # prefix the posts resource's requests with '/admin'
981
+ # scope path: "/admin" do
982
+ # resources :posts
983
+ # end
868
984
  #
869
- # # prefix the routing helper name: +sekret_posts_path+ instead of +posts_path+
870
- # scope as: "sekret" do
871
- # resources :posts
872
- # end
873
- def scope(*args)
874
- options = args.extract_options!.dup
985
+ # # prefix the routing helper name: sekret_posts_path instead of posts_path
986
+ # scope as: "sekret" do
987
+ # resources :posts
988
+ # end
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
+
875
996
  scope = {}
876
997
 
877
998
  options[:path] = args.flatten.join("/") if args.any?
@@ -892,9 +1013,8 @@ module ActionDispatch
892
1013
  block, options[:constraints] = options[:constraints], {}
893
1014
  end
894
1015
 
895
- if options.key?(:only) || options.key?(:except)
896
- scope[:action_options] = { only: options.delete(:only),
897
- except: options.delete(:except) }
1016
+ if only || except
1017
+ scope[:action_options] = { only:, except: }
898
1018
  end
899
1019
 
900
1020
  if options.key? :anchor
@@ -926,9 +1046,9 @@ module ActionDispatch
926
1046
 
927
1047
  # Scopes routes to a specific controller
928
1048
  #
929
- # controller "food" do
930
- # match "bacon", action: :bacon, via: :get
931
- # end
1049
+ # controller "food" do
1050
+ # match "bacon", action: :bacon, via: :get
1051
+ # end
932
1052
  def controller(controller)
933
1053
  @scope = @scope.new(controller: controller)
934
1054
  yield
@@ -938,121 +1058,132 @@ module ActionDispatch
938
1058
 
939
1059
  # Scopes routes to a specific namespace. For example:
940
1060
  #
941
- # namespace :admin do
942
- # resources :posts
943
- # end
1061
+ # namespace :admin do
1062
+ # resources :posts
1063
+ # end
944
1064
  #
945
1065
  # This generates the following routes:
946
1066
  #
947
- # admin_posts GET /admin/posts(.:format) admin/posts#index
948
- # admin_posts POST /admin/posts(.:format) admin/posts#create
949
- # new_admin_post GET /admin/posts/new(.:format) admin/posts#new
950
- # edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
951
- # admin_post GET /admin/posts/:id(.:format) admin/posts#show
952
- # admin_post PATCH/PUT /admin/posts/:id(.:format) admin/posts#update
953
- # admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
954
- #
955
- # === Options
956
- #
957
- # The +:path+, +:as+, +:module+, +:shallow_path+, and +:shallow_prefix+
958
- # options all default to the name of the namespace.
959
- #
960
- # For options, see <tt>Base#match</tt>. For +:shallow_path+ option, see
961
- # <tt>Resources#resources</tt>.
962
- #
963
- # # accessible through /sekret/posts rather than /admin/posts
964
- # namespace :admin, path: "sekret" do
965
- # resources :posts
966
- # end
967
- #
968
- # # maps to +Sekret::PostsController+ rather than +Admin::PostsController+
969
- # namespace :admin, module: "sekret" do
970
- # resources :posts
971
- # end
972
- #
973
- # # generates +sekret_posts_path+ rather than +admin_posts_path+
974
- # namespace :admin, as: "sekret" do
975
- # resources :posts
976
- # end
977
- def namespace(path, options = {}, &block)
978
- path = path.to_s
979
-
980
- defaults = {
981
- module: path,
982
- as: options.fetch(:as, path),
983
- shallow_path: options.fetch(:path, path),
984
- shallow_prefix: options.fetch(:as, path)
985
- }
1067
+ # admin_posts GET /admin/posts(.:format) admin/posts#index
1068
+ # admin_posts POST /admin/posts(.:format) admin/posts#create
1069
+ # new_admin_post GET /admin/posts/new(.:format) admin/posts#new
1070
+ # edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
1071
+ # admin_post GET /admin/posts/:id(.:format) admin/posts#show
1072
+ # admin_post PATCH/PUT /admin/posts/:id(.:format) admin/posts#update
1073
+ # admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
1074
+ #
1075
+ # ### Options
1076
+ #
1077
+ # The `:path`, `:as`, `:module`, `:shallow_path`, and `:shallow_prefix` options
1078
+ # all default to the name of the namespace.
1079
+ #
1080
+ # For options, see `Base#match`. For `:shallow_path` option, see
1081
+ # `Resources#resources`.
1082
+ #
1083
+ # # accessible through /sekret/posts rather than /admin/posts
1084
+ # namespace :admin, path: "sekret" do
1085
+ # resources :posts
1086
+ # end
1087
+ #
1088
+ # # maps to Sekret::PostsController rather than Admin::PostsController
1089
+ # namespace :admin, module: "sekret" do
1090
+ # resources :posts
1091
+ # end
1092
+ #
1093
+ # # generates sekret_posts_path rather than admin_posts_path
1094
+ # namespace :admin, as: "sekret" do
1095
+ # resources :posts
1096
+ # end
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
986
1105
 
987
- path_scope(options.delete(:path) { path }) do
988
- scope(defaults.merge!(options), &block)
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
1112
+
1113
+ path_scope(path) do
1114
+ scope(**options, as:, shallow_path:, shallow_prefix:, &block)
989
1115
  end
990
1116
  end
991
1117
 
992
- # === Parameter Restriction
993
- # Allows you to constrain the nested routes based on a set of rules.
994
- # For instance, in order to change the routes to allow for a dot character in the +id+ parameter:
1118
+ # ### Parameter Restriction
1119
+ # Allows you to constrain the nested routes based on a set of rules. For
1120
+ # instance, in order to change the routes to allow for a dot character in the
1121
+ # `id` parameter:
995
1122
  #
996
- # constraints(id: /\d+\.\d+/) do
997
- # resources :posts
998
- # end
1123
+ # constraints(id: /\d+\.\d+/) do
1124
+ # resources :posts
1125
+ # end
999
1126
  #
1000
- # Now routes such as +/posts/1+ will no longer be valid, but +/posts/1.1+ will be.
1001
- # The +id+ parameter must match the constraint passed in for this example.
1127
+ # Now routes such as `/posts/1` will no longer be valid, but `/posts/1.1` will
1128
+ # be. The `id` parameter must match the constraint passed in for this example.
1002
1129
  #
1003
1130
  # You may use this to also restrict other parameters:
1004
1131
  #
1005
- # resources :posts do
1006
- # constraints(post_id: /\d+\.\d+/) do
1007
- # resources :comments
1132
+ # resources :posts do
1133
+ # constraints(post_id: /\d+\.\d+/) do
1134
+ # resources :comments
1135
+ # end
1008
1136
  # end
1009
- # end
1010
1137
  #
1011
- # === Restricting based on IP
1138
+ # ### Restricting based on IP
1012
1139
  #
1013
1140
  # Routes can also be constrained to an IP or a certain range of IP addresses:
1014
1141
  #
1015
- # constraints(ip: /192\.168\.\d+\.\d+/) do
1016
- # resources :posts
1017
- # end
1142
+ # constraints(ip: /192\.168\.\d+\.\d+/) do
1143
+ # resources :posts
1144
+ # end
1018
1145
  #
1019
- # Any user connecting from the 192.168.* range will be able to see this resource,
1020
- # where as any user connecting outside of this range will be told there is no such route.
1146
+ # Any user connecting from the 192.168.* range will be able to see this
1147
+ # resource, where as any user connecting outside of this range will be told
1148
+ # there is no such route.
1021
1149
  #
1022
- # === Dynamic request matching
1150
+ # ### Dynamic request matching
1023
1151
  #
1024
1152
  # Requests to routes can be constrained based on specific criteria:
1025
1153
  #
1026
- # constraints(-> (req) { /iPhone/.match?(req.env["HTTP_USER_AGENT"]) }) do
1027
- # resources :iphones
1028
- # end
1154
+ # constraints(-> (req) { /iPhone/.match?(req.env["HTTP_USER_AGENT"]) }) do
1155
+ # resources :iphones
1156
+ # end
1029
1157
  #
1030
- # You are able to move this logic out into a class if it is too complex for routes.
1031
- # This class must have a +matches?+ method defined on it which either returns +true+
1032
- # if the user should be given access to that route, or +false+ if the user should not.
1158
+ # You are able to move this logic out into a class if it is too complex for
1159
+ # routes. This class must have a `matches?` method defined on it which either
1160
+ # returns `true` if the user should be given access to that route, or `false` if
1161
+ # the user should not.
1033
1162
  #
1034
- # class Iphone
1035
- # def self.matches?(request)
1036
- # /iPhone/.match?(request.env["HTTP_USER_AGENT"])
1037
- # end
1038
- # end
1163
+ # class Iphone
1164
+ # def self.matches?(request)
1165
+ # /iPhone/.match?(request.env["HTTP_USER_AGENT"])
1166
+ # end
1167
+ # end
1039
1168
  #
1040
- # An expected place for this code would be +lib/constraints+.
1169
+ # An expected place for this code would be `lib/constraints`.
1041
1170
  #
1042
1171
  # This class is then used like this:
1043
1172
  #
1044
- # constraints(Iphone) do
1045
- # resources :iphones
1046
- # end
1173
+ # constraints(Iphone) do
1174
+ # resources :iphones
1175
+ # end
1047
1176
  def constraints(constraints = {}, &block)
1048
1177
  scope(constraints: constraints, &block)
1049
1178
  end
1050
1179
 
1051
1180
  # Allows you to set default parameters for a route, such as this:
1052
- # defaults id: 'home' do
1053
- # match 'scoped_pages/(:id)', to: 'pages#show'
1054
- # end
1055
- # Using this, the +:id+ parameter here will default to 'home'.
1181
+ #
1182
+ # defaults id: 'home' do
1183
+ # match 'scoped_pages/(:id)', to: 'pages#show'
1184
+ # end
1185
+ #
1186
+ # Using this, the `:id` parameter here will default to 'home'.
1056
1187
  def defaults(defaults = {})
1057
1188
  @scope = @scope.new(defaults: merge_defaults_scope(@scope[:defaults], defaults))
1058
1189
  yield
@@ -1128,60 +1259,74 @@ module ActionDispatch
1128
1259
  end
1129
1260
  end
1130
1261
 
1131
- # Resource routing allows you to quickly declare all of the common routes
1132
- # for a given resourceful controller. Instead of declaring separate routes
1133
- # for your +index+, +show+, +new+, +edit+, +create+, +update+, and +destroy+
1134
- # actions, a resourceful route declares them in a single line of code:
1262
+ # Resource routing allows you to quickly declare all of the common routes for a
1263
+ # given resourceful controller. Instead of declaring separate routes for your
1264
+ # `index`, `show`, `new`, `edit`, `create`, `update`, and `destroy` actions, a
1265
+ # resourceful route declares them in a single line of code:
1135
1266
  #
1136
- # resources :photos
1267
+ # resources :photos
1137
1268
  #
1138
- # Sometimes, you have a resource that clients always look up without
1139
- # referencing an ID. A common example, /profile always shows the profile of
1140
- # the currently logged in user. In this case, you can use a singular resource
1141
- # to map /profile (rather than /profile/:id) to the show action.
1269
+ # Sometimes, you have a resource that clients always look up without referencing
1270
+ # an ID. A common example, /profile always shows the profile of the currently
1271
+ # logged in user. In this case, you can use a singular resource to map /profile
1272
+ # (rather than /profile/:id) to the show action.
1142
1273
  #
1143
- # resource :profile
1274
+ # resource :profile
1144
1275
  #
1145
- # It's common to have resources that are logically children of other
1146
- # resources:
1276
+ # It's common to have resources that are logically children of other resources:
1147
1277
  #
1148
- # resources :magazines do
1149
- # resources :ads
1150
- # end
1278
+ # resources :magazines do
1279
+ # resources :ads
1280
+ # end
1151
1281
  #
1152
1282
  # You may wish to organize groups of controllers under a namespace. Most
1153
- # commonly, you might group a number of administrative controllers under
1154
- # an +admin+ namespace. You would place these controllers under the
1155
- # <tt>app/controllers/admin</tt> directory, and you can group them together
1156
- # in your router:
1157
- #
1158
- # namespace "admin" do
1159
- # resources :posts, :comments
1160
- # end
1283
+ # commonly, you might group a number of administrative controllers under an
1284
+ # `admin` namespace. You would place these controllers under the
1285
+ # `app/controllers/admin` directory, and you can group them together in your
1286
+ # router:
1161
1287
  #
1162
- # By default the +:id+ parameter doesn't accept dots. If you need to
1163
- # use dots as part of the +:id+ parameter add a constraint which
1164
- # overrides this restriction, e.g:
1288
+ # namespace "admin" do
1289
+ # resources :posts, :comments
1290
+ # end
1165
1291
  #
1166
- # resources :articles, id: /[^\/]+/
1292
+ # By default the `:id` parameter doesn't accept dots. If you need to use dots as
1293
+ # part of the `:id` parameter add a constraint which overrides this restriction,
1294
+ # e.g:
1167
1295
  #
1168
- # This allows any character other than a slash as part of your +:id+.
1296
+ # resources :articles, id: /[^\/]+/
1169
1297
  #
1298
+ # This allows any character other than a slash as part of your `:id`.
1170
1299
  module Resources
1171
- # CANONICAL_ACTIONS holds all actions that does not need a prefix or
1172
- # a path appended since they fit properly in their scope level.
1300
+ # CANONICAL_ACTIONS holds all actions that does not need a prefix or a path
1301
+ # appended since they fit properly in their scope level.
1173
1302
  VALID_ON_OPTIONS = [:new, :collection, :member]
1174
1303
  RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns]
1175
1304
  CANONICAL_ACTIONS = %w(index create new show update destroy)
1176
1305
 
1177
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
+
1178
1317
  attr_reader :controller, :path, :param
1179
1318
 
1180
- def initialize(entities, api_only, shallow, options = {})
1319
+ def initialize(entities, api_only, shallow, only: nil, except: nil, **options)
1181
1320
  if options[:param].to_s.include?(":")
1182
1321
  raise ArgumentError, ":param option can't contain colons"
1183
1322
  end
1184
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
+
1185
1330
  @name = entities.to_s
1186
1331
  @path = (options[:path] || @name).to_s
1187
1332
  @controller = (options[:controller] || @name).to_s
@@ -1190,16 +1335,12 @@ module ActionDispatch
1190
1335
  @options = options
1191
1336
  @shallow = shallow
1192
1337
  @api_only = api_only
1193
- @only = options.delete :only
1194
- @except = options.delete :except
1338
+ @only = only
1339
+ @except = except
1195
1340
  end
1196
1341
 
1197
1342
  def default_actions
1198
- if @api_only
1199
- [:index, :create, :show, :update, :destroy]
1200
- else
1201
- [:index, :create, :new, :show, :update, :destroy, :edit]
1202
- end
1343
+ self.class.default_actions(@api_only)
1203
1344
  end
1204
1345
 
1205
1346
  def actions
@@ -1232,8 +1373,8 @@ module ActionDispatch
1232
1373
 
1233
1374
  alias :member_name :singular
1234
1375
 
1235
- # Checks for uncountable plurals, and appends "_index" if the plural
1236
- # and singular form are the same.
1376
+ # Checks for uncountable plurals, and appends "_index" if the plural and
1377
+ # singular form are the same.
1237
1378
  def collection_name
1238
1379
  singular == plural ? "#{plural}_index" : plural
1239
1380
  end
@@ -1267,10 +1408,25 @@ module ActionDispatch
1267
1408
  end
1268
1409
 
1269
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
1270
1416
  end
1271
1417
 
1272
1418
  class SingletonResource < Resource # :nodoc:
1273
- 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)
1274
1430
  super
1275
1431
  @as = nil
1276
1432
  @controller = (options[:controller] || plural).to_s
@@ -1278,11 +1434,7 @@ module ActionDispatch
1278
1434
  end
1279
1435
 
1280
1436
  def default_actions
1281
- if @api_only
1282
- [:show, :create, :update, :destroy]
1283
- else
1284
- [:show, :create, :update, :destroy, :new, :edit]
1285
- end
1437
+ self.class.default_actions(@api_only)
1286
1438
  end
1287
1439
 
1288
1440
  def plural
@@ -1306,50 +1458,51 @@ module ActionDispatch
1306
1458
  @scope[:path_names].merge!(options)
1307
1459
  end
1308
1460
 
1309
- # Sometimes, you have a resource that clients always look up without
1310
- # referencing an ID. A common example, /profile always shows the
1311
- # profile of the currently logged in user. In this case, you can use
1312
- # a singular resource to map /profile (rather than /profile/:id) to
1313
- # the show action:
1461
+ # Sometimes, you have a resource that clients always look up without referencing
1462
+ # an ID. A common example, /profile always shows the profile of the currently
1463
+ # logged in user. In this case, you can use a singular resource to map /profile
1464
+ # (rather than /profile/:id) to the show action:
1314
1465
  #
1315
- # resource :profile
1466
+ # resource :profile
1316
1467
  #
1317
- # This creates six different routes in your application, all mapping to
1318
- # the +Profiles+ controller (note that the controller is named after
1319
- # the plural):
1468
+ # This creates six different routes in your application, all mapping to the
1469
+ # `Profiles` controller (note that the controller is named after the plural):
1320
1470
  #
1321
- # GET /profile/new
1322
- # GET /profile
1323
- # GET /profile/edit
1324
- # PATCH/PUT /profile
1325
- # DELETE /profile
1326
- # POST /profile
1471
+ # GET /profile/new
1472
+ # GET /profile
1473
+ # GET /profile/edit
1474
+ # PATCH/PUT /profile
1475
+ # DELETE /profile
1476
+ # POST /profile
1327
1477
  #
1328
- # If you want instances of a model to work with this resource via
1329
- # record identification (e.g. in +form_with+ or +redirect_to+), you
1330
- # will need to call resolve[rdoc-ref:CustomUrls#resolve]:
1478
+ # If you want instances of a model to work with this resource via record
1479
+ # identification (e.g. in `form_with` or `redirect_to`), you will need to call
1480
+ # [resolve](rdoc-ref:CustomUrls#resolve):
1331
1481
  #
1332
- # resource :profile
1333
- # resolve('Profile') { [:profile] }
1482
+ # resource :profile
1483
+ # resolve('Profile') { [:profile] }
1334
1484
  #
1335
- # # Enables this to work with singular routes:
1336
- # form_with(model: @profile) {}
1485
+ # # Enables this to work with singular routes:
1486
+ # form_with(model: @profile) {}
1337
1487
  #
1338
- # === Options
1339
- # Takes same options as resources[rdoc-ref:#resources]
1340
- def resource(*resources, &block)
1341
- options = resources.extract_options!.dup
1488
+ # ### Options
1489
+ # Takes same options as [resources](rdoc-ref:#resources)
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
1342
1495
 
1343
- if apply_common_behavior_for(:resource, resources, options, &block)
1496
+ if apply_common_behavior_for(:resource, resources, concerns:, **options, &block)
1344
1497
  return self
1345
1498
  end
1346
1499
 
1347
1500
  with_scope_level(:resource) do
1348
- options = apply_action_options options
1349
- 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
1350
1503
  yield if block_given?
1351
1504
 
1352
- concerns(options[:concerns]) if options[:concerns]
1505
+ concerns(*concerns) if concerns
1353
1506
 
1354
1507
  new do
1355
1508
  get :new
@@ -1366,156 +1519,163 @@ module ActionDispatch
1366
1519
  self
1367
1520
  end
1368
1521
 
1369
- # In \Rails, a resourceful route provides a mapping between HTTP verbs
1370
- # and URLs and controller actions. By convention, each action also maps
1371
- # to particular CRUD operations in a database. A single entry in the
1372
- # routing file, such as
1522
+ # In Rails, a resourceful route provides a mapping between HTTP verbs and URLs
1523
+ # and controller actions. By convention, each action also maps to particular
1524
+ # CRUD operations in a database. A single entry in the routing file, such as
1373
1525
  #
1374
- # resources :photos
1526
+ # resources :photos
1375
1527
  #
1376
- # creates seven different routes in your application, all mapping to
1377
- # the +Photos+ controller:
1528
+ # creates seven different routes in your application, all mapping to the
1529
+ # `Photos` controller:
1378
1530
  #
1379
- # GET /photos
1380
- # GET /photos/new
1381
- # POST /photos
1382
- # GET /photos/:id
1383
- # GET /photos/:id/edit
1384
- # PATCH/PUT /photos/:id
1385
- # DELETE /photos/:id
1531
+ # GET /photos
1532
+ # GET /photos/new
1533
+ # POST /photos
1534
+ # GET /photos/:id
1535
+ # GET /photos/:id/edit
1536
+ # PATCH/PUT /photos/:id
1537
+ # DELETE /photos/:id
1386
1538
  #
1387
1539
  # Resources can also be nested infinitely by using this block syntax:
1388
1540
  #
1389
- # resources :photos do
1390
- # resources :comments
1391
- # end
1541
+ # resources :photos do
1542
+ # resources :comments
1543
+ # end
1392
1544
  #
1393
1545
  # This generates the following comments routes:
1394
1546
  #
1395
- # GET /photos/:photo_id/comments
1396
- # GET /photos/:photo_id/comments/new
1397
- # POST /photos/:photo_id/comments
1398
- # GET /photos/:photo_id/comments/:id
1399
- # GET /photos/:photo_id/comments/:id/edit
1400
- # PATCH/PUT /photos/:photo_id/comments/:id
1401
- # DELETE /photos/:photo_id/comments/:id
1547
+ # GET /photos/:photo_id/comments
1548
+ # GET /photos/:photo_id/comments/new
1549
+ # POST /photos/:photo_id/comments
1550
+ # GET /photos/:photo_id/comments/:id
1551
+ # GET /photos/:photo_id/comments/:id/edit
1552
+ # PATCH/PUT /photos/:photo_id/comments/:id
1553
+ # DELETE /photos/:photo_id/comments/:id
1402
1554
  #
1403
- # === Options
1404
- # Takes same options as match[rdoc-ref:Base#match] as well as:
1555
+ # ### Options
1556
+ # Takes same options as [match](rdoc-ref:Base#match) as well as:
1405
1557
  #
1406
- # [:path_names]
1407
- # Allows you to change the segment component of the +edit+ and +new+ actions.
1408
- # Actions not specified are not changed.
1558
+ # :path_names
1559
+ # : Allows you to change the segment component of the `edit` and `new`
1560
+ # actions. Actions not specified are not changed.
1409
1561
  #
1410
- # resources :posts, path_names: { new: "brand_new" }
1562
+ # resources :posts, path_names: { new: "brand_new" }
1411
1563
  #
1412
- # The above example will now change /posts/new to /posts/brand_new.
1564
+ # The above example will now change /posts/new to /posts/brand_new.
1413
1565
  #
1414
- # [:path]
1415
- # Allows you to change the path prefix for the resource.
1566
+ # :path
1567
+ # : Allows you to change the path prefix for the resource.
1416
1568
  #
1417
- # resources :posts, path: 'postings'
1569
+ # resources :posts, path: 'postings'
1418
1570
  #
1419
- # The resource and all segments will now route to /postings instead of /posts.
1571
+ # The resource and all segments will now route to /postings instead of
1572
+ # /posts.
1420
1573
  #
1421
- # [:only]
1422
- # Only generate routes for the given actions.
1574
+ # :only
1575
+ # : Only generate routes for the given actions.
1423
1576
  #
1424
- # resources :cows, only: :show
1425
- # resources :cows, only: [:show, :index]
1577
+ # resources :cows, only: :show
1578
+ # resources :cows, only: [:show, :index]
1426
1579
  #
1427
- # [:except]
1428
- # Generate all routes except for the given actions.
1580
+ # :except
1581
+ # : Generate all routes except for the given actions.
1429
1582
  #
1430
- # resources :cows, except: :show
1431
- # resources :cows, except: [:show, :index]
1583
+ # resources :cows, except: :show
1584
+ # resources :cows, except: [:show, :index]
1432
1585
  #
1433
- # [:shallow]
1434
- # Generates shallow routes for nested resource(s). When placed on a parent resource,
1435
- # generates shallow routes for all nested resources.
1586
+ # :shallow
1587
+ # : Generates shallow routes for nested resource(s). When placed on a parent
1588
+ # resource, generates shallow routes for all nested resources.
1436
1589
  #
1437
- # resources :posts, shallow: true do
1438
- # resources :comments
1439
- # end
1590
+ # resources :posts, shallow: true do
1591
+ # resources :comments
1592
+ # end
1440
1593
  #
1441
- # Is the same as:
1594
+ # Is the same as:
1442
1595
  #
1443
- # resources :posts do
1444
- # resources :comments, except: [:show, :edit, :update, :destroy]
1445
- # end
1446
- # resources :comments, only: [:show, :edit, :update, :destroy]
1596
+ # resources :posts do
1597
+ # resources :comments, except: [:show, :edit, :update, :destroy]
1598
+ # end
1599
+ # resources :comments, only: [:show, :edit, :update, :destroy]
1447
1600
  #
1448
- # This allows URLs for resources that otherwise would be deeply nested such
1449
- # as a comment on a blog post like <tt>/posts/a-long-permalink/comments/1234</tt>
1450
- # to be shortened to just <tt>/comments/1234</tt>.
1601
+ # This allows URLs for resources that otherwise would be deeply nested such
1602
+ # as a comment on a blog post like `/posts/a-long-permalink/comments/1234`
1603
+ # to be shortened to just `/comments/1234`.
1451
1604
  #
1452
- # Set <tt>shallow: false</tt> on a child resource to ignore a parent's shallow parameter.
1605
+ # Set `shallow: false` on a child resource to ignore a parent's shallow
1606
+ # parameter.
1453
1607
  #
1454
- # [:shallow_path]
1455
- # Prefixes nested shallow routes with the specified path.
1608
+ # :shallow_path
1609
+ # : Prefixes nested shallow routes with the specified path.
1456
1610
  #
1457
- # scope shallow_path: "sekret" do
1458
- # resources :posts do
1459
- # resources :comments, shallow: true
1460
- # end
1461
- # end
1611
+ # scope shallow_path: "sekret" do
1612
+ # resources :posts do
1613
+ # resources :comments, shallow: true
1614
+ # end
1615
+ # end
1462
1616
  #
1463
- # The +comments+ resource here will have the following routes generated for it:
1617
+ # The `comments` resource here will have the following routes generated for
1618
+ # it:
1464
1619
  #
1465
- # post_comments GET /posts/:post_id/comments(.:format)
1466
- # post_comments POST /posts/:post_id/comments(.:format)
1467
- # new_post_comment GET /posts/:post_id/comments/new(.:format)
1468
- # edit_comment GET /sekret/comments/:id/edit(.:format)
1469
- # comment GET /sekret/comments/:id(.:format)
1470
- # comment PATCH/PUT /sekret/comments/:id(.:format)
1471
- # comment DELETE /sekret/comments/:id(.:format)
1620
+ # post_comments GET /posts/:post_id/comments(.:format)
1621
+ # post_comments POST /posts/:post_id/comments(.:format)
1622
+ # new_post_comment GET /posts/:post_id/comments/new(.:format)
1623
+ # edit_comment GET /sekret/comments/:id/edit(.:format)
1624
+ # comment GET /sekret/comments/:id(.:format)
1625
+ # comment PATCH/PUT /sekret/comments/:id(.:format)
1626
+ # comment DELETE /sekret/comments/:id(.:format)
1472
1627
  #
1473
- # [:shallow_prefix]
1474
- # Prefixes nested shallow route names with specified prefix.
1628
+ # :shallow_prefix
1629
+ # : Prefixes nested shallow route names with specified prefix.
1475
1630
  #
1476
- # scope shallow_prefix: "sekret" do
1477
- # resources :posts do
1478
- # resources :comments, shallow: true
1479
- # end
1480
- # end
1631
+ # scope shallow_prefix: "sekret" do
1632
+ # resources :posts do
1633
+ # resources :comments, shallow: true
1634
+ # end
1635
+ # end
1636
+ #
1637
+ # The `comments` resource here will have the following routes generated for
1638
+ # it:
1481
1639
  #
1482
- # The +comments+ resource here will have the following routes generated for it:
1640
+ # post_comments GET /posts/:post_id/comments(.:format)
1641
+ # post_comments POST /posts/:post_id/comments(.:format)
1642
+ # new_post_comment GET /posts/:post_id/comments/new(.:format)
1643
+ # edit_sekret_comment GET /comments/:id/edit(.:format)
1644
+ # sekret_comment GET /comments/:id(.:format)
1645
+ # sekret_comment PATCH/PUT /comments/:id(.:format)
1646
+ # sekret_comment DELETE /comments/:id(.:format)
1483
1647
  #
1484
- # post_comments GET /posts/:post_id/comments(.:format)
1485
- # post_comments POST /posts/:post_id/comments(.:format)
1486
- # new_post_comment GET /posts/:post_id/comments/new(.:format)
1487
- # edit_sekret_comment GET /comments/:id/edit(.:format)
1488
- # sekret_comment GET /comments/:id(.:format)
1489
- # sekret_comment PATCH/PUT /comments/:id(.:format)
1490
- # sekret_comment DELETE /comments/:id(.:format)
1648
+ # :format
1649
+ # : Allows you to specify the default value for optional `format` segment or
1650
+ # disable it by supplying `false`.
1491
1651
  #
1492
- # [:format]
1493
- # Allows you to specify the default value for optional +format+
1494
- # segment or disable it by supplying +false+.
1652
+ # :param
1653
+ # : Allows you to override the default param name of `:id` in the URL.
1495
1654
  #
1496
- # [:param]
1497
- # Allows you to override the default param name of +:id+ in the URL.
1498
1655
  #
1499
- # === Examples
1656
+ # ### Examples
1500
1657
  #
1501
- # # routes call +Admin::PostsController+
1502
- # resources :posts, module: "admin"
1658
+ # # routes call Admin::PostsController
1659
+ # resources :posts, module: "admin"
1503
1660
  #
1504
- # # resource actions are at /admin/posts.
1505
- # resources :posts, path: "admin/posts"
1506
- def resources(*resources, &block)
1507
- options = resources.extract_options!.dup
1661
+ # # resource actions are at /admin/posts.
1662
+ # resources :posts, path: "admin/posts"
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
1508
1668
 
1509
- if apply_common_behavior_for(:resources, resources, options, &block)
1669
+ if apply_common_behavior_for(:resources, resources, concerns:, **options, &block)
1510
1670
  return self
1511
1671
  end
1512
1672
 
1513
1673
  with_scope_level(:resources) do
1514
- options = apply_action_options options
1515
- 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
1516
1676
  yield if block_given?
1517
1677
 
1518
- concerns(options[:concerns]) if options[:concerns]
1678
+ concerns(*concerns) if concerns
1519
1679
 
1520
1680
  collection do
1521
1681
  get :index if parent_resource.actions.include?(:index)
@@ -1535,16 +1695,15 @@ module ActionDispatch
1535
1695
 
1536
1696
  # To add a route to the collection:
1537
1697
  #
1538
- # resources :photos do
1539
- # collection do
1540
- # get 'search'
1698
+ # resources :photos do
1699
+ # collection do
1700
+ # get 'search'
1701
+ # end
1541
1702
  # end
1542
- # end
1543
1703
  #
1544
- # This will enable \Rails to recognize paths such as <tt>/photos/search</tt>
1545
- # with GET, and route to the search action of +PhotosController+. It will also
1546
- # create the <tt>search_photos_url</tt> and <tt>search_photos_path</tt>
1547
- # route helpers.
1704
+ # This will enable Rails to recognize paths such as `/photos/search` with GET,
1705
+ # and route to the search action of `PhotosController`. It will also create the
1706
+ # `search_photos_url` and `search_photos_path` route helpers.
1548
1707
  def collection(&block)
1549
1708
  unless resource_scope?
1550
1709
  raise ArgumentError, "can't use collection outside resource(s) scope"
@@ -1557,15 +1716,15 @@ module ActionDispatch
1557
1716
 
1558
1717
  # To add a member route, add a member block into the resource block:
1559
1718
  #
1560
- # resources :photos do
1561
- # member do
1562
- # get 'preview'
1719
+ # resources :photos do
1720
+ # member do
1721
+ # get 'preview'
1722
+ # end
1563
1723
  # end
1564
- # end
1565
1724
  #
1566
- # This will recognize <tt>/photos/1/preview</tt> with GET, and route to the
1567
- # preview action of +PhotosController+. It will also create the
1568
- # <tt>preview_photo_url</tt> and <tt>preview_photo_path</tt> helpers.
1725
+ # This will recognize `/photos/1/preview` with GET, and route to the preview
1726
+ # action of `PhotosController`. It will also create the `preview_photo_url` and
1727
+ # `preview_photo_path` helpers.
1569
1728
  def member(&block)
1570
1729
  unless resource_scope?
1571
1730
  raise ArgumentError, "can't use member outside resource(s) scope"
@@ -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
@@ -1632,29 +1791,28 @@ module ActionDispatch
1632
1791
  !parent_resource.singleton? && @scope[:shallow]
1633
1792
  end
1634
1793
 
1635
- # Loads another routes file with the given +name+ located inside the
1636
- # +config/routes+ directory. In that file, you can use the normal
1637
- # routing DSL, but <i>do not</i> surround it with a
1638
- # +Rails.application.routes.draw+ block.
1639
- #
1640
- # # config/routes.rb
1641
- # Rails.application.routes.draw do
1642
- # draw :admin # Loads `config/routes/admin.rb`
1643
- # draw "third_party/some_gem" # Loads `config/routes/third_party/some_gem.rb`
1644
- # end
1645
- #
1646
- # # config/routes/admin.rb
1647
- # namespace :admin do
1648
- # resources :accounts
1649
- # end
1650
- #
1651
- # # config/routes/third_party/some_gem.rb
1652
- # mount SomeGem::Engine, at: "/some_gem"
1653
- #
1654
- # <b>CAUTION:</b> Use this feature with care. Having multiple routes
1655
- # files can negatively impact discoverability and readability. For most
1656
- # applications — even those with a few hundred routes — it's easier for
1657
- # developers to have a single routes file.
1794
+ # Loads another routes file with the given `name` located inside the
1795
+ # `config/routes` directory. In that file, you can use the normal routing DSL,
1796
+ # but *do not* surround it with a `Rails.application.routes.draw` block.
1797
+ #
1798
+ # # config/routes.rb
1799
+ # Rails.application.routes.draw do
1800
+ # draw :admin # Loads `config/routes/admin.rb`
1801
+ # draw "third_party/some_gem" # Loads `config/routes/third_party/some_gem.rb`
1802
+ # end
1803
+ #
1804
+ # # config/routes/admin.rb
1805
+ # namespace :admin do
1806
+ # resources :accounts
1807
+ # end
1808
+ #
1809
+ # # config/routes/third_party/some_gem.rb
1810
+ # mount SomeGem::Engine, at: "/some_gem"
1811
+ #
1812
+ # **CAUTION:** Use this feature with care. Having multiple routes files can
1813
+ # negatively impact discoverability and readability. For most applications —
1814
+ # even those with a few hundred routes it's easier for developers to have a
1815
+ # single routes file.
1658
1816
  def draw(name)
1659
1817
  path = @draw_paths.find do |_path|
1660
1818
  File.exist? "#{_path}/#{name}.rb"
@@ -1671,59 +1829,77 @@ module ActionDispatch
1671
1829
  instance_eval(File.read(route_path), route_path.to_s)
1672
1830
  end
1673
1831
 
1674
- # Matches a URL pattern to one or more routes.
1675
- # For more information, see match[rdoc-ref:Base#match].
1676
- #
1677
- # match 'path' => 'controller#action', via: :patch
1678
- # match 'path', to: 'controller#action', via: :post
1679
- # match 'path', 'otherpath', on: :member, via: :get
1680
- def match(path, *rest, &block)
1681
- if rest.empty? && Hash === path
1682
- options = path
1683
- path, to = options.find { |name, _value| name.is_a?(String) }
1684
-
1685
- raise ArgumentError, "Route path not specified" if path.nil?
1832
+ # Matches a URL pattern to one or more routes. For more information, see
1833
+ # [match](rdoc-ref:Base#match).
1834
+ #
1835
+ # match 'path', to: 'controller#action', via: :post
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
1686
1863
 
1687
- case to
1688
- when Symbol
1689
- options[:action] = to
1690
- when String
1691
- if to.include?("#")
1692
- 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
1693
1875
  else
1694
- options[:controller] = to
1876
+ to ||= hash_to
1695
1877
  end
1696
- else
1697
- options[:to] = to
1698
1878
  end
1699
-
1700
- options.delete(path)
1701
- paths = [path]
1702
- else
1703
- options = rest.pop || {}
1704
- paths = [path] + rest
1705
1879
  end
1706
1880
 
1707
- if options.key?(:defaults)
1708
- defaults(options.delete(:defaults)) { map_match(paths, options, &block) }
1709
- else
1710
- 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
1711
1887
  end
1712
1888
  end
1713
1889
 
1714
- # You can specify what \Rails should route "/" to with the root method:
1890
+ # You can specify what Rails should route "/" to with the root method:
1715
1891
  #
1716
- # root to: 'pages#main'
1892
+ # root to: 'pages#main'
1717
1893
  #
1718
- # For options, see +match+, as +root+ uses it internally.
1894
+ # For options, see `match`, as `root` uses it internally.
1719
1895
  #
1720
1896
  # You can also pass a string which will expand
1721
1897
  #
1722
- # root 'pages#main'
1898
+ # root 'pages#main'
1723
1899
  #
1724
- # You should put the root route at the top of <tt>config/routes.rb</tt>,
1725
- # because this means it will be matched first. As this is the most popular route
1726
- # of most \Rails applications, this is beneficial.
1900
+ # You should put the root route at the top of `config/routes.rb`, because this
1901
+ # means it will be matched first. As this is the most popular route of most
1902
+ # Rails applications, this is beneficial.
1727
1903
  def root(path, options = {})
1728
1904
  if path.is_a?(String)
1729
1905
  options[:to] = path
@@ -1749,22 +1925,21 @@ module ActionDispatch
1749
1925
  @scope[:scope_level_resource]
1750
1926
  end
1751
1927
 
1752
- def apply_common_behavior_for(method, resources, options, &block)
1928
+ def apply_common_behavior_for(method, resources, shallow: nil, **options, &block)
1753
1929
  if resources.length > 1
1754
- resources.each { |r| public_send(method, r, options, &block) }
1930
+ resources.each { |r| public_send(method, r, shallow:, **options, &block) }
1755
1931
  return true
1756
1932
  end
1757
1933
 
1758
- if options[:shallow]
1759
- options.delete(:shallow)
1760
- shallow do
1761
- public_send(method, resources.pop, options, &block)
1934
+ if shallow
1935
+ self.shallow do
1936
+ public_send(method, resources.pop, **options, &block)
1762
1937
  end
1763
1938
  return true
1764
1939
  end
1765
1940
 
1766
1941
  if resource_scope?
1767
- nested { public_send(method, resources.pop, options, &block) }
1942
+ nested { public_send(method, resources.pop, shallow:, **options, &block) }
1768
1943
  return true
1769
1944
  end
1770
1945
 
@@ -1773,9 +1948,11 @@ module ActionDispatch
1773
1948
  end
1774
1949
 
1775
1950
  scope_options = options.slice!(*RESOURCE_OPTIONS)
1951
+ scope_options[:shallow] = shallow unless shallow.nil?
1952
+
1776
1953
  unless scope_options.empty?
1777
- scope(scope_options) do
1778
- public_send(method, resources.pop, options, &block)
1954
+ scope(**scope_options) do
1955
+ public_send(method, resources.pop, **options, &block)
1779
1956
  end
1780
1957
  return true
1781
1958
  end
@@ -1783,17 +1960,32 @@ module ActionDispatch
1783
1960
  false
1784
1961
  end
1785
1962
 
1786
- def apply_action_options(options)
1963
+ def apply_action_options(method, options)
1787
1964
  return options if action_options? options
1788
- options.merge scope_action_options
1965
+ options.merge scope_action_options(method)
1789
1966
  end
1790
1967
 
1791
1968
  def action_options?(options)
1792
1969
  options[:only] || options[:except]
1793
1970
  end
1794
1971
 
1795
- def scope_action_options
1796
- @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
1797
1989
  end
1798
1990
 
1799
1991
  def resource_scope?
@@ -1851,9 +2043,10 @@ module ActionDispatch
1851
2043
  end
1852
2044
 
1853
2045
  def shallow_scope
1854
- scope = { as: @scope[:shallow_prefix],
1855
- path: @scope[:shallow_path] }
1856
- @scope = @scope.new scope
2046
+ @scope = @scope.new(
2047
+ as: @scope[:shallow_prefix],
2048
+ path: @scope[:shallow_path],
2049
+ )
1857
2050
 
1858
2051
  yield
1859
2052
  ensure
@@ -1875,7 +2068,7 @@ module ActionDispatch
1875
2068
  end
1876
2069
 
1877
2070
  def prefix_name_for_action(as, action)
1878
- if as
2071
+ if as && as != DEFAULT
1879
2072
  prefix = as
1880
2073
  elsif !canonical_action?(action)
1881
2074
  prefix = action
@@ -1891,7 +2084,7 @@ module ActionDispatch
1891
2084
  name_prefix = @scope[:as]
1892
2085
 
1893
2086
  if parent_resource
1894
- return nil unless as || action
2087
+ return nil unless as != DEFAULT || action
1895
2088
 
1896
2089
  collection_name = parent_resource.collection_name
1897
2090
  member_name = parent_resource.member_name
@@ -1901,10 +2094,10 @@ module ActionDispatch
1901
2094
  candidate = action_name.select(&:present?).join("_")
1902
2095
 
1903
2096
  unless candidate.empty?
1904
- # If a name was not explicitly given, we check if it is valid
1905
- # and return nil in case it isn't. Otherwise, we pass the invalid name
1906
- # forward so the underlying router engine treats it and raises an exception.
1907
- if as.nil?
2097
+ # If a name was not explicitly given, we check if it is valid and return nil in
2098
+ # case it isn't. Otherwise, we pass the invalid name forward so the underlying
2099
+ # router engine treats it and raises an exception.
2100
+ if as == DEFAULT
1908
2101
  candidate unless !candidate.match?(/\A[_a-z]/i) || has_named_route?(candidate)
1909
2102
  else
1910
2103
  candidate
@@ -1935,42 +2128,35 @@ module ActionDispatch
1935
2128
  @scope = @scope.parent
1936
2129
  end
1937
2130
 
1938
- def map_match(paths, options)
1939
- 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)
1940
2133
  raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
1941
2134
  end
1942
2135
 
1943
2136
  if @scope[:to]
1944
- options[:to] ||= @scope[:to]
2137
+ to ||= @scope[:to]
1945
2138
  end
1946
2139
 
1947
2140
  if @scope[:controller] && @scope[:action]
1948
- options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
2141
+ to ||= "#{@scope[:controller]}##{@scope[:action]}"
1949
2142
  end
1950
2143
 
1951
- controller = options.delete(:controller) || @scope[:controller]
1952
- option_path = options.delete :path
1953
- to = options.delete :to
1954
- via = Mapping.check_via Array(options.delete(:via) {
1955
- @scope[:via]
1956
- })
1957
- formatted = options.delete(:format) { @scope[:format] }
1958
- anchor = options.delete(:anchor) { true }
1959
- options_constraints = options.delete(:constraints) || {}
1960
-
1961
- path_types = paths.group_by(&:class)
1962
- (path_types[String] || []).each do |_path|
1963
- route_options = options.dup
1964
- 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
1965
2153
  raise ArgumentError, "Ambiguous route definition. Both :path and the route path were specified as strings."
1966
2154
  end
1967
- to = get_to_from_path(_path, to, route_options[:action])
1968
- decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints)
1969
- end
1970
-
1971
- (path_types[Symbol] || []).each do |action|
1972
- route_options = options.dup
1973
- 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)
1974
2160
  end
1975
2161
 
1976
2162
  self
@@ -1991,28 +2177,28 @@ module ActionDispatch
1991
2177
  %r{^/?[-\w]+/[-\w/]+$}.match?(path)
1992
2178
  end
1993
2179
 
1994
- def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
1995
- if on = options.delete(:on)
1996
- 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) }
1997
2183
  else
1998
2184
  case @scope.scope_level
1999
2185
  when :resources
2000
- 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) }
2001
2187
  when :resource
2002
- 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) }
2003
2189
  else
2004
- 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)
2005
2191
  end
2006
2192
  end
2007
2193
  end
2008
2194
 
2009
- 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)
2010
2196
  path = path_for_action(action, _path)
2011
2197
  raise ArgumentError, "path is required" if path.blank?
2012
2198
 
2013
2199
  action = action.to_s
2014
2200
 
2015
- default_action = options.delete(:action) || @scope[:action]
2201
+ default_action = options_action || @scope[:action]
2016
2202
 
2017
2203
  if /^[\w\-\/]+$/.match?(action)
2018
2204
  default_action ||= action.tr("-", "_") unless action.include?("/")
@@ -2020,102 +2206,94 @@ module ActionDispatch
2020
2206
  action = nil
2021
2207
  end
2022
2208
 
2023
- as = if !options.fetch(:as, true) # if it's set to nil or false
2024
- options.delete(:as)
2025
- else
2026
- name_for_action(options.delete(:as), action)
2027
- end
2028
-
2029
- path = Mapping.normalize_path RFC2396_PARSER.escape(path), formatted
2030
- 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
2031
2212
 
2032
- 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)
2033
2214
  @set.add_route(mapping, as)
2034
2215
  end
2035
2216
 
2036
2217
  def match_root_route(options)
2037
- args = ["/", { as: :root, via: :get }.merge(options)]
2038
- match(*args)
2218
+ match("/", as: :root, via: :get, **options)
2039
2219
  end
2040
2220
  end
2041
2221
 
2042
- # Routing Concerns allow you to declare common routes that can be reused
2043
- # inside others resources and routes.
2222
+ # Routing Concerns allow you to declare common routes that can be reused inside
2223
+ # others resources and routes.
2044
2224
  #
2045
- # concern :commentable do
2046
- # resources :comments
2047
- # end
2225
+ # concern :commentable do
2226
+ # resources :comments
2227
+ # end
2048
2228
  #
2049
- # concern :image_attachable do
2050
- # resources :images, only: :index
2051
- # end
2229
+ # concern :image_attachable do
2230
+ # resources :images, only: :index
2231
+ # end
2052
2232
  #
2053
2233
  # These concerns are used in Resources routing:
2054
2234
  #
2055
- # resources :messages, concerns: [:commentable, :image_attachable]
2235
+ # resources :messages, concerns: [:commentable, :image_attachable]
2056
2236
  #
2057
2237
  # or in a scope or namespace:
2058
2238
  #
2059
- # namespace :posts do
2060
- # concerns :commentable
2061
- # end
2239
+ # namespace :posts do
2240
+ # concerns :commentable
2241
+ # end
2062
2242
  module Concerns
2063
2243
  # Define a routing concern using a name.
2064
2244
  #
2065
- # Concerns may be defined inline, using a block, or handled by
2066
- # another object, by passing that object as the second parameter.
2245
+ # Concerns may be defined inline, using a block, or handled by another object,
2246
+ # by passing that object as the second parameter.
2067
2247
  #
2068
- # The concern object, if supplied, should respond to <tt>call</tt>,
2069
- # which will receive two parameters:
2248
+ # The concern object, if supplied, should respond to `call`, which will receive
2249
+ # two parameters:
2070
2250
  #
2071
- # * The current mapper
2072
- # * A hash of options which the concern object may use
2251
+ # * The current mapper
2252
+ # * A hash of options which the concern object may use
2073
2253
  #
2074
- # Options may also be used by concerns defined in a block by accepting
2075
- # a block parameter. So, using a block, you might do something as
2076
- # simple as limit the actions available on certain resources, passing
2077
- # standard resource options through the concern:
2254
+ # Options may also be used by concerns defined in a block by accepting a block
2255
+ # parameter. So, using a block, you might do something as simple as limit the
2256
+ # actions available on certain resources, passing standard resource options
2257
+ # through the concern:
2078
2258
  #
2079
- # concern :commentable do |options|
2080
- # resources :comments, options
2081
- # end
2259
+ # concern :commentable do |options|
2260
+ # resources :comments, options
2261
+ # end
2082
2262
  #
2083
- # resources :posts, concerns: :commentable
2084
- # resources :archived_posts do
2085
- # # Don't allow comments on archived posts
2086
- # concerns :commentable, only: [:index, :show]
2087
- # end
2263
+ # resources :posts, concerns: :commentable
2264
+ # resources :archived_posts do
2265
+ # # Don't allow comments on archived posts
2266
+ # concerns :commentable, only: [:index, :show]
2267
+ # end
2088
2268
  #
2089
- # Or, using a callable object, you might implement something more
2090
- # specific to your application, which would be out of place in your
2091
- # routes file.
2269
+ # Or, using a callable object, you might implement something more specific to
2270
+ # your application, which would be out of place in your routes file.
2092
2271
  #
2093
- # # purchasable.rb
2094
- # class Purchasable
2095
- # def initialize(defaults = {})
2096
- # @defaults = defaults
2097
- # end
2272
+ # # purchasable.rb
2273
+ # class Purchasable
2274
+ # def initialize(defaults = {})
2275
+ # @defaults = defaults
2276
+ # end
2098
2277
  #
2099
- # def call(mapper, options = {})
2100
- # options = @defaults.merge(options)
2101
- # mapper.resources :purchases
2102
- # mapper.resources :receipts
2103
- # mapper.resources :returns if options[:returnable]
2278
+ # def call(mapper, options = {})
2279
+ # options = @defaults.merge(options)
2280
+ # mapper.resources :purchases
2281
+ # mapper.resources :receipts
2282
+ # mapper.resources :returns if options[:returnable]
2283
+ # end
2104
2284
  # end
2105
- # end
2106
2285
  #
2107
- # # routes.rb
2108
- # concern :purchasable, Purchasable.new(returnable: true)
2286
+ # # routes.rb
2287
+ # concern :purchasable, Purchasable.new(returnable: true)
2109
2288
  #
2110
- # resources :toys, concerns: :purchasable
2111
- # resources :electronics, concerns: :purchasable
2112
- # resources :pets do
2113
- # concerns :purchasable, returnable: false
2114
- # end
2289
+ # resources :toys, concerns: :purchasable
2290
+ # resources :electronics, concerns: :purchasable
2291
+ # resources :pets do
2292
+ # concerns :purchasable, returnable: false
2293
+ # end
2115
2294
  #
2116
- # Any routing helpers can be used inside a concern. If using a
2117
- # callable, they're accessible from the Mapper that's passed to
2118
- # <tt>call</tt>.
2295
+ # Any routing helpers can be used inside a concern. If using a callable, they're
2296
+ # accessible from the Mapper that's passed to `call`.
2119
2297
  def concern(name, callable = nil, &block)
2120
2298
  callable ||= lambda { |mapper, options| mapper.instance_exec(options, &block) }
2121
2299
  @concerns[name] = callable
@@ -2123,17 +2301,16 @@ module ActionDispatch
2123
2301
 
2124
2302
  # Use the named concerns
2125
2303
  #
2126
- # resources :posts do
2127
- # concerns :commentable
2128
- # end
2304
+ # resources :posts do
2305
+ # concerns :commentable
2306
+ # end
2129
2307
  #
2130
2308
  # Concerns also work in any routes helper that you want to use:
2131
2309
  #
2132
- # namespace :posts do
2133
- # concerns :commentable
2134
- # end
2135
- def concerns(*args)
2136
- options = args.extract_options!
2310
+ # namespace :posts do
2311
+ # concerns :commentable
2312
+ # end
2313
+ def concerns(*args, **options)
2137
2314
  args.flatten.each do |name|
2138
2315
  if concern = @concerns[name]
2139
2316
  concern.call(self, options)
@@ -2145,53 +2322,55 @@ module ActionDispatch
2145
2322
  end
2146
2323
 
2147
2324
  module CustomUrls
2148
- # Define custom URL helpers that will be added to the application's
2149
- # routes. This allows you to override and/or replace the default behavior
2150
- # of routing helpers, e.g:
2325
+ # Define custom URL helpers that will be added to the application's routes. This
2326
+ # allows you to override and/or replace the default behavior of routing helpers,
2327
+ # e.g:
2151
2328
  #
2152
- # direct :homepage do
2153
- # "https://rubyonrails.org"
2154
- # end
2329
+ # direct :homepage do
2330
+ # "https://rubyonrails.org"
2331
+ # end
2155
2332
  #
2156
- # direct :commentable do |model|
2157
- # [ model, anchor: model.dom_id ]
2158
- # end
2333
+ # direct :commentable do |model|
2334
+ # [ model, anchor: model.dom_id ]
2335
+ # end
2159
2336
  #
2160
- # direct :main do
2161
- # { controller: "pages", action: "index", subdomain: "www" }
2162
- # end
2337
+ # direct :main do
2338
+ # { controller: "pages", action: "index", subdomain: "www" }
2339
+ # end
2163
2340
  #
2164
- # The return value from the block passed to +direct+ must be a valid set of
2165
- # arguments for +url_for+ which will actually build the URL string. This can
2166
- # be one of the following:
2341
+ # The return value from the block passed to `direct` must be a valid set of
2342
+ # arguments for `url_for` which will actually build the URL string. This can be
2343
+ # one of the following:
2167
2344
  #
2168
- # * A string, which is treated as a generated URL
2169
- # * A hash, e.g. <tt>{ controller: "pages", action: "index" }</tt>
2170
- # * An array, which is passed to +polymorphic_url+
2171
- # * An Active Model instance
2172
- # * An Active Model class
2345
+ # * A string, which is treated as a generated URL
2346
+ # * A hash, e.g. `{ controller: "pages", action: "index" }`
2347
+ # * An array, which is passed to `polymorphic_url`
2348
+ # * An Active Model instance
2349
+ # * An Active Model class
2173
2350
  #
2174
- # NOTE: Other URL helpers can be called in the block but be careful not to invoke
2175
- # your custom URL helper again otherwise it will result in a stack overflow error.
2176
2351
  #
2177
- # You can also specify default options that will be passed through to
2178
- # your URL helper definition, e.g:
2352
+ # NOTE: Other URL helpers can be called in the block but be careful not to
2353
+ # invoke your custom URL helper again otherwise it will result in a stack
2354
+ # overflow error.
2179
2355
  #
2180
- # direct :browse, page: 1, size: 10 do |options|
2181
- # [ :products, options.merge(params.permit(:page, :size).to_h.symbolize_keys) ]
2182
- # end
2356
+ # You can also specify default options that will be passed through to your URL
2357
+ # helper definition, e.g:
2183
2358
  #
2184
- # In this instance the +params+ object comes from the context in which the
2185
- # block is executed, e.g. generating a URL inside a controller action or a view.
2186
- # If the block is executed where there isn't a +params+ object such as this:
2359
+ # direct :browse, page: 1, size: 10 do |options|
2360
+ # [ :products, options.merge(params.permit(:page, :size).to_h.symbolize_keys) ]
2361
+ # end
2362
+ #
2363
+ # In this instance the `params` object comes from the context in which the block
2364
+ # is executed, e.g. generating a URL inside a controller action or a view. If
2365
+ # the block is executed where there isn't a `params` object such as this:
2187
2366
  #
2188
- # Rails.application.routes.url_helpers.browse_path
2367
+ # Rails.application.routes.url_helpers.browse_path
2189
2368
  #
2190
- # then it will raise a +NameError+. Because of this you need to be aware of the
2369
+ # then it will raise a `NameError`. Because of this you need to be aware of the
2191
2370
  # context in which you will use your custom URL helper when defining it.
2192
2371
  #
2193
- # NOTE: The +direct+ method can't be used inside of a scope block such as
2194
- # +namespace+ or +scope+ and will raise an error if it detects that it is.
2372
+ # NOTE: The `direct` method can't be used inside of a scope block such as
2373
+ # `namespace` or `scope` and will raise an error if it detects that it is.
2195
2374
  def direct(name, options = {}, &block)
2196
2375
  unless @scope.root?
2197
2376
  raise RuntimeError, "The direct method can't be used inside a routes scope block"
@@ -2200,50 +2379,50 @@ module ActionDispatch
2200
2379
  @set.add_url_helper(name, options, &block)
2201
2380
  end
2202
2381
 
2203
- # Define custom polymorphic mappings of models to URLs. This alters the
2204
- # behavior of +polymorphic_url+ and consequently the behavior of
2205
- # +link_to+ and +form_for+ when passed a model instance, e.g:
2382
+ # Define custom polymorphic mappings of models to URLs. This alters the behavior
2383
+ # of `polymorphic_url` and consequently the behavior of `link_to`, `form_with`
2384
+ # and `form_for` when passed a model instance, e.g:
2206
2385
  #
2207
- # resource :basket
2386
+ # resource :basket
2208
2387
  #
2209
- # resolve "Basket" do
2210
- # [:basket]
2211
- # end
2388
+ # resolve "Basket" do
2389
+ # [:basket]
2390
+ # end
2212
2391
  #
2213
- # This will now generate "/basket" when a +Basket+ instance is passed to
2214
- # +link_to+ or +form_for+ instead of the standard "/baskets/:id".
2392
+ # This will now generate "/basket" when a `Basket` instance is passed to
2393
+ # `link_to`, `form_with` or `form_for` instead of the standard "/baskets/:id".
2215
2394
  #
2216
- # NOTE: This custom behavior only applies to simple polymorphic URLs where
2217
- # a single model instance is passed and not more complicated forms, e.g:
2395
+ # NOTE: This custom behavior only applies to simple polymorphic URLs where a
2396
+ # single model instance is passed and not more complicated forms, e.g:
2218
2397
  #
2219
- # # config/routes.rb
2220
- # resource :profile
2221
- # namespace :admin do
2222
- # resources :users
2223
- # end
2398
+ # # config/routes.rb
2399
+ # resource :profile
2400
+ # namespace :admin do
2401
+ # resources :users
2402
+ # end
2224
2403
  #
2225
- # resolve("User") { [:profile] }
2404
+ # resolve("User") { [:profile] }
2226
2405
  #
2227
- # # app/views/application/_menu.html.erb
2228
- # link_to "Profile", @current_user
2229
- # link_to "Profile", [:admin, @current_user]
2406
+ # # app/views/application/_menu.html.erb
2407
+ # link_to "Profile", @current_user
2408
+ # link_to "Profile", [:admin, @current_user]
2230
2409
  #
2231
- # The first +link_to+ will generate "/profile" but the second will generate
2232
- # the standard polymorphic URL of "/admin/users/1".
2410
+ # The first `link_to` will generate "/profile" but the second will generate the
2411
+ # standard polymorphic URL of "/admin/users/1".
2233
2412
  #
2234
- # You can pass options to a polymorphic mapping - the arity for the block
2235
- # needs to be two as the instance is passed as the first argument, e.g:
2413
+ # You can pass options to a polymorphic mapping - the arity for the block needs
2414
+ # to be two as the instance is passed as the first argument, e.g:
2236
2415
  #
2237
- # resolve "Basket", anchor: "items" do |basket, options|
2238
- # [:basket, options]
2239
- # end
2416
+ # resolve "Basket", anchor: "items" do |basket, options|
2417
+ # [:basket, options]
2418
+ # end
2240
2419
  #
2241
- # This generates the URL "/basket#items" because when the last item in an
2242
- # array passed to +polymorphic_url+ is a hash then it's treated as options
2243
- # to the URL helper that gets called.
2420
+ # This generates the URL "/basket#items" because when the last item in an array
2421
+ # passed to `polymorphic_url` is a hash then it's treated as options to the URL
2422
+ # helper that gets called.
2244
2423
  #
2245
- # NOTE: The +resolve+ method can't be used inside of a scope block such as
2246
- # +namespace+ or +scope+ and will raise an error if it detects that it is.
2424
+ # NOTE: The `resolve` method can't be used inside of a scope block such as
2425
+ # `namespace` or `scope` and will raise an error if it detects that it is.
2247
2426
  def resolve(*args, &block)
2248
2427
  unless @scope.root?
2249
2428
  raise RuntimeError, "The resolve method can't be used inside a routes scope block"
@@ -2268,9 +2447,9 @@ module ActionDispatch
2268
2447
 
2269
2448
  attr_reader :parent, :scope_level
2270
2449
 
2271
- def initialize(hash, parent = NULL, scope_level = nil)
2272
- @hash = hash
2450
+ def initialize(hash, parent = ROOT, scope_level = nil)
2273
2451
  @parent = parent
2452
+ @hash = parent ? parent.frame.merge(hash) : hash
2274
2453
  @scope_level = scope_level
2275
2454
  end
2276
2455
 
@@ -2283,7 +2462,7 @@ module ActionDispatch
2283
2462
  end
2284
2463
 
2285
2464
  def root?
2286
- @parent.null?
2465
+ @parent == ROOT
2287
2466
  end
2288
2467
 
2289
2468
  def resources?
@@ -2328,25 +2507,26 @@ module ActionDispatch
2328
2507
  end
2329
2508
 
2330
2509
  def [](key)
2331
- scope = find { |node| node.frame.key? key }
2332
- scope && scope.frame[key]
2510
+ frame[key]
2333
2511
  end
2334
2512
 
2513
+ def frame; @hash; end
2514
+
2335
2515
  include Enumerable
2336
2516
 
2337
2517
  def each
2338
2518
  node = self
2339
- until node.equal? NULL
2519
+ until node.equal? ROOT
2340
2520
  yield node
2341
2521
  node = node.parent
2342
2522
  end
2343
2523
  end
2344
2524
 
2345
- def frame; @hash; end
2346
-
2347
- NULL = Scope.new(nil, nil)
2525
+ ROOT = Scope.new({}, nil)
2348
2526
  end
2349
2527
 
2528
+ DEFAULT = Object.new # :nodoc:
2529
+
2350
2530
  def initialize(set) # :nodoc:
2351
2531
  @set = set
2352
2532
  @draw_paths = set.draw_paths