actionpack 4.2.10 → 7.2.0.rc1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (202) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +86 -600
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +13 -14
  5. data/lib/abstract_controller/asset_paths.rb +5 -1
  6. data/lib/abstract_controller/base.rb +166 -136
  7. data/lib/abstract_controller/caching/fragments.rb +149 -0
  8. data/lib/abstract_controller/caching.rb +68 -0
  9. data/lib/abstract_controller/callbacks.rb +126 -57
  10. data/lib/abstract_controller/collector.rb +13 -15
  11. data/lib/abstract_controller/deprecator.rb +9 -0
  12. data/lib/abstract_controller/error.rb +8 -0
  13. data/lib/abstract_controller/helpers.rb +181 -132
  14. data/lib/abstract_controller/logger.rb +5 -1
  15. data/lib/abstract_controller/railties/routes_helpers.rb +10 -3
  16. data/lib/abstract_controller/rendering.rb +56 -56
  17. data/lib/abstract_controller/translation.rb +29 -15
  18. data/lib/abstract_controller/url_for.rb +15 -11
  19. data/lib/abstract_controller.rb +21 -5
  20. data/lib/action_controller/api/api_rendering.rb +18 -0
  21. data/lib/action_controller/api.rb +154 -0
  22. data/lib/action_controller/base.rb +219 -155
  23. data/lib/action_controller/caching.rb +28 -68
  24. data/lib/action_controller/deprecator.rb +9 -0
  25. data/lib/action_controller/form_builder.rb +55 -0
  26. data/lib/action_controller/log_subscriber.rb +35 -22
  27. data/lib/action_controller/metal/allow_browser.rb +119 -0
  28. data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
  29. data/lib/action_controller/metal/conditional_get.rb +259 -122
  30. data/lib/action_controller/metal/content_security_policy.rb +86 -0
  31. data/lib/action_controller/metal/cookies.rb +9 -5
  32. data/lib/action_controller/metal/data_streaming.rb +87 -104
  33. data/lib/action_controller/metal/default_headers.rb +21 -0
  34. data/lib/action_controller/metal/etag_with_flash.rb +22 -0
  35. data/lib/action_controller/metal/etag_with_template_digest.rb +35 -26
  36. data/lib/action_controller/metal/exceptions.rb +71 -24
  37. data/lib/action_controller/metal/flash.rb +26 -19
  38. data/lib/action_controller/metal/head.rb +45 -36
  39. data/lib/action_controller/metal/helpers.rb +80 -64
  40. data/lib/action_controller/metal/http_authentication.rb +297 -244
  41. data/lib/action_controller/metal/implicit_render.rb +57 -9
  42. data/lib/action_controller/metal/instrumentation.rb +76 -64
  43. data/lib/action_controller/metal/live.rb +238 -176
  44. data/lib/action_controller/metal/logging.rb +22 -0
  45. data/lib/action_controller/metal/mime_responds.rb +177 -166
  46. data/lib/action_controller/metal/parameter_encoding.rb +84 -0
  47. data/lib/action_controller/metal/params_wrapper.rb +145 -118
  48. data/lib/action_controller/metal/permissions_policy.rb +38 -0
  49. data/lib/action_controller/metal/rate_limiting.rb +62 -0
  50. data/lib/action_controller/metal/redirecting.rb +203 -64
  51. data/lib/action_controller/metal/renderers.rb +108 -65
  52. data/lib/action_controller/metal/rendering.rb +216 -56
  53. data/lib/action_controller/metal/request_forgery_protection.rb +496 -163
  54. data/lib/action_controller/metal/rescue.rb +19 -21
  55. data/lib/action_controller/metal/streaming.rb +179 -138
  56. data/lib/action_controller/metal/strong_parameters.rb +1058 -382
  57. data/lib/action_controller/metal/testing.rb +11 -17
  58. data/lib/action_controller/metal/url_for.rb +37 -21
  59. data/lib/action_controller/metal.rb +236 -138
  60. data/lib/action_controller/railtie.rb +89 -11
  61. data/lib/action_controller/railties/helpers.rb +5 -1
  62. data/lib/action_controller/renderer.rb +161 -0
  63. data/lib/action_controller/template_assertions.rb +13 -0
  64. data/lib/action_controller/test_case.rb +425 -497
  65. data/lib/action_controller.rb +44 -22
  66. data/lib/action_dispatch/constants.rb +34 -0
  67. data/lib/action_dispatch/deprecator.rb +9 -0
  68. data/lib/action_dispatch/http/cache.rb +119 -63
  69. data/lib/action_dispatch/http/content_disposition.rb +47 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +364 -0
  71. data/lib/action_dispatch/http/filter_parameters.rb +36 -34
  72. data/lib/action_dispatch/http/filter_redirect.rb +24 -12
  73. data/lib/action_dispatch/http/headers.rb +66 -31
  74. data/lib/action_dispatch/http/mime_negotiation.rb +106 -75
  75. data/lib/action_dispatch/http/mime_type.rb +196 -136
  76. data/lib/action_dispatch/http/mime_types.rb +25 -7
  77. data/lib/action_dispatch/http/parameters.rb +97 -45
  78. data/lib/action_dispatch/http/permissions_policy.rb +187 -0
  79. data/lib/action_dispatch/http/rack_cache.rb +6 -0
  80. data/lib/action_dispatch/http/request.rb +299 -170
  81. data/lib/action_dispatch/http/response.rb +311 -160
  82. data/lib/action_dispatch/http/upload.rb +52 -23
  83. data/lib/action_dispatch/http/url.rb +201 -125
  84. data/lib/action_dispatch/journey/formatter.rb +110 -50
  85. data/lib/action_dispatch/journey/gtg/builder.rb +37 -50
  86. data/lib/action_dispatch/journey/gtg/simulator.rb +20 -17
  87. data/lib/action_dispatch/journey/gtg/transition_table.rb +96 -36
  88. data/lib/action_dispatch/journey/nfa/dot.rb +5 -14
  89. data/lib/action_dispatch/journey/nodes/node.rb +100 -20
  90. data/lib/action_dispatch/journey/parser.rb +19 -17
  91. data/lib/action_dispatch/journey/parser.y +4 -3
  92. data/lib/action_dispatch/journey/parser_extras.rb +14 -4
  93. data/lib/action_dispatch/journey/path/pattern.rb +79 -63
  94. data/lib/action_dispatch/journey/route.rb +108 -44
  95. data/lib/action_dispatch/journey/router/utils.rb +41 -29
  96. data/lib/action_dispatch/journey/router.rb +64 -57
  97. data/lib/action_dispatch/journey/routes.rb +23 -21
  98. data/lib/action_dispatch/journey/scanner.rb +28 -17
  99. data/lib/action_dispatch/journey/visitors.rb +100 -54
  100. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  101. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  102. data/lib/action_dispatch/journey.rb +7 -5
  103. data/lib/action_dispatch/log_subscriber.rb +25 -0
  104. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  105. data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
  106. data/lib/action_dispatch/middleware/callbacks.rb +7 -6
  107. data/lib/action_dispatch/middleware/cookies.rb +471 -328
  108. data/lib/action_dispatch/middleware/debug_exceptions.rb +149 -66
  109. data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
  110. data/lib/action_dispatch/middleware/debug_view.rb +73 -0
  111. data/lib/action_dispatch/middleware/exception_wrapper.rb +275 -73
  112. data/lib/action_dispatch/middleware/executor.rb +32 -0
  113. data/lib/action_dispatch/middleware/flash.rb +143 -101
  114. data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
  115. data/lib/action_dispatch/middleware/public_exceptions.rb +36 -27
  116. data/lib/action_dispatch/middleware/reloader.rb +10 -92
  117. data/lib/action_dispatch/middleware/remote_ip.rb +133 -107
  118. data/lib/action_dispatch/middleware/request_id.rb +29 -15
  119. data/lib/action_dispatch/middleware/server_timing.rb +78 -0
  120. data/lib/action_dispatch/middleware/session/abstract_store.rb +49 -27
  121. data/lib/action_dispatch/middleware/session/cache_store.rb +33 -16
  122. data/lib/action_dispatch/middleware/session/cookie_store.rb +86 -80
  123. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +15 -3
  124. data/lib/action_dispatch/middleware/show_exceptions.rb +66 -36
  125. data/lib/action_dispatch/middleware/ssl.rb +134 -36
  126. data/lib/action_dispatch/middleware/stack.rb +109 -44
  127. data/lib/action_dispatch/middleware/static.rb +159 -90
  128. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +7 -24
  132. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  133. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
  136. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
  137. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
  138. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +26 -7
  139. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +3 -3
  140. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
  141. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
  142. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +139 -15
  143. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -0
  144. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  145. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +6 -6
  146. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +7 -7
  147. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +9 -9
  148. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  149. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
  150. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  151. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +7 -4
  152. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +125 -93
  153. data/lib/action_dispatch/railtie.rb +44 -16
  154. data/lib/action_dispatch/request/session.rb +159 -69
  155. data/lib/action_dispatch/request/utils.rb +97 -23
  156. data/lib/action_dispatch/routing/endpoint.rb +11 -2
  157. data/lib/action_dispatch/routing/inspector.rb +195 -106
  158. data/lib/action_dispatch/routing/mapper.rb +1338 -955
  159. data/lib/action_dispatch/routing/polymorphic_routes.rb +234 -201
  160. data/lib/action_dispatch/routing/redirection.rb +78 -51
  161. data/lib/action_dispatch/routing/route_set.rb +460 -374
  162. data/lib/action_dispatch/routing/routes_proxy.rb +36 -12
  163. data/lib/action_dispatch/routing/url_for.rb +172 -124
  164. data/lib/action_dispatch/routing.rb +159 -158
  165. data/lib/action_dispatch/system_test_case.rb +206 -0
  166. data/lib/action_dispatch/system_testing/browser.rb +84 -0
  167. data/lib/action_dispatch/system_testing/driver.rb +85 -0
  168. data/lib/action_dispatch/system_testing/server.rb +33 -0
  169. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
  170. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
  171. data/lib/action_dispatch/testing/assertion_response.rb +48 -0
  172. data/lib/action_dispatch/testing/assertions/response.rb +71 -39
  173. data/lib/action_dispatch/testing/assertions/routing.rb +228 -103
  174. data/lib/action_dispatch/testing/assertions.rb +9 -6
  175. data/lib/action_dispatch/testing/integration.rb +486 -306
  176. data/lib/action_dispatch/testing/request_encoder.rb +60 -0
  177. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  178. data/lib/action_dispatch/testing/test_process.rb +35 -22
  179. data/lib/action_dispatch/testing/test_request.rb +29 -34
  180. data/lib/action_dispatch/testing/test_response.rb +48 -15
  181. data/lib/action_dispatch.rb +82 -40
  182. data/lib/action_pack/gem_version.rb +8 -4
  183. data/lib/action_pack/version.rb +6 -2
  184. data/lib/action_pack.rb +21 -18
  185. metadata +146 -56
  186. data/lib/action_controller/caching/fragments.rb +0 -103
  187. data/lib/action_controller/metal/force_ssl.rb +0 -97
  188. data/lib/action_controller/metal/hide_actions.rb +0 -40
  189. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  190. data/lib/action_controller/middleware.rb +0 -39
  191. data/lib/action_controller/model_naming.rb +0 -12
  192. data/lib/action_dispatch/http/parameter_filter.rb +0 -72
  193. data/lib/action_dispatch/journey/backwards.rb +0 -5
  194. data/lib/action_dispatch/journey/nfa/builder.rb +0 -76
  195. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
  196. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -163
  197. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  198. data/lib/action_dispatch/middleware/params_parser.rb +0 -60
  199. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +0 -27
  200. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  201. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  202. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,86 +1,81 @@
1
- require 'action_dispatch/journey'
2
- require 'forwardable'
3
- require 'active_support/concern'
4
- require 'active_support/core_ext/object/to_query'
5
- require 'active_support/core_ext/hash/slice'
6
- require 'active_support/core_ext/module/remove_method'
7
- require 'active_support/core_ext/array/extract_options'
8
- require 'active_support/core_ext/string/filters'
9
- require 'action_controller/metal/exceptions'
10
- require 'action_dispatch/http/request'
11
- require 'action_dispatch/routing/endpoint'
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "action_dispatch/journey"
6
+ require "active_support/core_ext/object/to_query"
7
+ require "active_support/core_ext/module/redefine_method"
8
+ require "active_support/core_ext/module/remove_method"
9
+ require "active_support/core_ext/array/extract_options"
10
+ require "action_controller/metal/exceptions"
11
+ require "action_dispatch/routing/endpoint"
12
12
 
13
13
  module ActionDispatch
14
14
  module Routing
15
- # :stopdoc:
15
+ # The RouteSet contains a collection of Route instances, representing the routes
16
+ # typically defined in `config/routes.rb`.
16
17
  class RouteSet
17
- # Since the router holds references to many parts of the system
18
- # like engines, controllers and the application itself, inspecting
19
- # the route set can actually be really slow, therefore we default
20
- # alias inspect to to_s.
21
- alias inspect to_s
18
+ # Returns a Route matching the given requirements, or `nil` if none are found.
19
+ #
20
+ # This is intended for use by tools such as Language Servers.
21
+ #
22
+ # Given the routes are defined as:
23
+ #
24
+ # resources :posts
25
+ #
26
+ # Then the following will return the Route for the `show` action:
27
+ #
28
+ # Rails.application.routes.from_requirements(controller: "posts", action: "show")
29
+ def from_requirements(requirements)
30
+ routes.find { |route| route.requirements == requirements }
31
+ end
32
+ # :stopdoc:
22
33
 
23
- mattr_accessor :relative_url_root
34
+ # Since the router holds references to many parts of the system like engines,
35
+ # controllers and the application itself, inspecting the route set can actually
36
+ # be really slow, therefore we default alias inspect to to_s.
37
+ alias inspect to_s
24
38
 
25
39
  class Dispatcher < Routing::Endpoint
26
- def initialize(defaults)
27
- @defaults = defaults
40
+ def initialize(raise_on_name_error)
41
+ @raise_on_name_error = raise_on_name_error
28
42
  end
29
43
 
30
44
  def dispatcher?; true; end
31
45
 
32
46
  def serve(req)
33
- req.check_path_parameters!
34
- params = req.path_parameters
35
-
36
- prepare_params!(params)
37
-
38
- # Just raise undefined constant errors if a controller was specified as default.
39
- unless controller = controller(params, @defaults.key?(:controller))
40
- return [404, {'X-Cascade' => 'pass'}, []]
47
+ params = req.path_parameters
48
+ controller = controller req
49
+ res = controller.make_response! req
50
+ dispatch(controller, params[:action], req, res)
51
+ rescue ActionController::RoutingError
52
+ if @raise_on_name_error
53
+ raise
54
+ else
55
+ [404, { Constants::X_CASCADE => "pass" }, []]
41
56
  end
42
-
43
- dispatch(controller, params[:action], req.env)
44
- end
45
-
46
- def prepare_params!(params)
47
- normalize_controller!(params)
48
- merge_default_action!(params)
49
57
  end
50
58
 
51
- # If this is a default_controller (i.e. a controller specified by the user)
52
- # we should raise an error in case it's not found, because it usually means
53
- # a user error. However, if the controller was retrieved through a dynamic
54
- # segment, as in :controller(/:action), we should simply return nil and
55
- # delegate the control back to Rack cascade. Besides, if this is not a default
56
- # controller, it means we should respect the @scope[:module] parameter.
57
- def controller(params, default_controller=true)
58
- if params && params.key?(:controller)
59
- controller_param = params[:controller]
60
- controller_reference(controller_param)
59
+ private
60
+ def controller(req)
61
+ req.controller_class
62
+ rescue NameError => e
63
+ raise ActionController::RoutingError, e.message, e.backtrace
61
64
  end
62
- rescue NameError => e
63
- raise ActionController::RoutingError, e.message, e.backtrace if default_controller
64
- end
65
-
66
- private
67
65
 
68
- def controller_reference(controller_param)
69
- const_name = "#{controller_param.camelize}Controller"
70
- ActiveSupport::Dependencies.constantize(const_name)
71
- end
72
-
73
- def dispatch(controller, action, env)
74
- controller.action(action).call(env)
75
- end
66
+ def dispatch(controller, action, req, res)
67
+ controller.dispatch(action, req, res)
68
+ end
69
+ end
76
70
 
77
- def normalize_controller!(params)
78
- params[:controller] = params[:controller].underscore if params.key?(:controller)
71
+ class StaticDispatcher < Dispatcher
72
+ def initialize(controller_class)
73
+ super(false)
74
+ @controller_class = controller_class
79
75
  end
80
76
 
81
- def merge_default_action!(params)
82
- params[:action] ||= 'index'
83
- end
77
+ private
78
+ def controller(_); @controller_class; end
84
79
  end
85
80
 
86
81
  # A NamedRouteCollection instance is a collection of named routes, and also
@@ -88,10 +83,11 @@ module ActionDispatch
88
83
  # named routes.
89
84
  class NamedRouteCollection
90
85
  include Enumerable
91
- attr_reader :routes, :url_helpers_module
86
+ attr_reader :routes, :url_helpers_module, :path_helpers_module
87
+ private :routes
92
88
 
93
89
  def initialize
94
- @routes = {}
90
+ @routes = {}
95
91
  @path_helpers = Set.new
96
92
  @url_helpers = Set.new
97
93
  @url_helpers_module = Module.new
@@ -103,25 +99,17 @@ module ActionDispatch
103
99
  @path_helpers.include?(key) || @url_helpers.include?(key)
104
100
  end
105
101
 
106
- def helpers
107
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
108
- `named_routes.helpers` is deprecated, please use `route_defined?(route_name)`
109
- to see if a named route was defined.
110
- MSG
111
- @path_helpers + @url_helpers
112
- end
113
-
114
102
  def helper_names
115
103
  @path_helpers.map(&:to_s) + @url_helpers.map(&:to_s)
116
104
  end
117
105
 
118
106
  def clear!
119
107
  @path_helpers.each do |helper|
120
- @path_helpers_module.send :undef_method, helper
108
+ @path_helpers_module.remove_method helper
121
109
  end
122
110
 
123
111
  @url_helpers.each do |helper|
124
- @url_helpers_module.send :undef_method, helper
112
+ @url_helpers_module.remove_method helper
125
113
  end
126
114
 
127
115
  @routes.clear
@@ -135,12 +123,14 @@ module ActionDispatch
135
123
  url_name = :"#{name}_url"
136
124
 
137
125
  if routes.key? key
138
- @path_helpers_module.send :undef_method, path_name
139
- @url_helpers_module.send :undef_method, url_name
126
+ @path_helpers_module.undef_method path_name
127
+ @url_helpers_module.undef_method url_name
140
128
  end
141
129
  routes[key] = route
142
- define_url_helper @path_helpers_module, route, path_name, route.defaults, name, LEGACY
143
- define_url_helper @url_helpers_module, route, url_name, route.defaults, name, UNKNOWN
130
+
131
+ helper = UrlHelper.create(route, route.defaults, name)
132
+ define_url_helper @path_helpers_module, path_name, helper, PATH
133
+ define_url_helper @url_helpers_module, url_name, helper, UNKNOWN
144
134
 
145
135
  @path_helpers << path_name
146
136
  @url_helpers << url_name
@@ -151,6 +141,7 @@ module ActionDispatch
151
141
  end
152
142
 
153
143
  def key?(name)
144
+ return unless name
154
145
  routes.key? name.to_sym
155
146
  end
156
147
 
@@ -158,8 +149,8 @@ module ActionDispatch
158
149
  alias [] get
159
150
  alias clear clear!
160
151
 
161
- def each
162
- routes.each { |name, route| yield name, route }
152
+ def each(&block)
153
+ routes.each(&block)
163
154
  self
164
155
  end
165
156
 
@@ -171,53 +162,71 @@ module ActionDispatch
171
162
  routes.length
172
163
  end
173
164
 
174
- def path_helpers_module(warn = false)
175
- if warn
176
- mod = @path_helpers_module
177
- helpers = @path_helpers
178
- Module.new do
179
- include mod
165
+ # Given a `name`, defines name_path and name_url helpers. Used by 'direct',
166
+ # 'resolve', and 'polymorphic' route helpers.
167
+ def add_url_helper(name, defaults, &block)
168
+ helper = CustomUrlHelper.new(name, defaults, &block)
169
+ path_name = :"#{name}_path"
170
+ url_name = :"#{name}_url"
180
171
 
181
- helpers.each do |meth|
182
- define_method(meth) do |*args, &block|
183
- ActiveSupport::Deprecation.warn("The method `#{meth}` cannot be used here as a full URL is required. Use `#{meth.to_s.sub(/_path$/, '_url')}` instead")
184
- super(*args, &block)
185
- end
186
- end
172
+ @path_helpers_module.module_eval do
173
+ redefine_method(path_name) do |*args|
174
+ helper.call(self, args, true)
175
+ end
176
+ end
177
+
178
+ @url_helpers_module.module_eval do
179
+ redefine_method(url_name) do |*args|
180
+ helper.call(self, args, false)
187
181
  end
188
- else
189
- @path_helpers_module
190
182
  end
183
+
184
+ @path_helpers << path_name
185
+ @url_helpers << url_name
186
+
187
+ self
191
188
  end
192
189
 
193
190
  class UrlHelper
194
- def self.create(route, options, route_name, url_strategy)
191
+ def self.create(route, options, route_name)
195
192
  if optimize_helper?(route)
196
- OptimizedUrlHelper.new(route, options, route_name, url_strategy)
193
+ OptimizedUrlHelper.new(route, options, route_name)
197
194
  else
198
- new route, options, route_name, url_strategy
195
+ new(route, options, route_name)
199
196
  end
200
197
  end
201
198
 
202
199
  def self.optimize_helper?(route)
203
- !route.glob? && route.path.requirements.empty?
200
+ route.path.requirements.empty? && !route.glob?
204
201
  end
205
202
 
206
- attr_reader :url_strategy, :route_name
203
+ attr_reader :route_name
207
204
 
208
205
  class OptimizedUrlHelper < UrlHelper
209
206
  attr_reader :arg_size
210
207
 
211
- def initialize(route, options, route_name, url_strategy)
208
+ def initialize(route, options, route_name)
212
209
  super
213
210
  @required_parts = @route.required_parts
214
211
  @arg_size = @required_parts.size
215
212
  end
216
213
 
217
- def call(t, args, inner_options)
214
+ def call(t, method_name, args, inner_options, url_strategy)
218
215
  if args.size == arg_size && !inner_options && optimize_routes_generation?(t)
219
216
  options = t.url_options.merge @options
220
- options[:path] = optimized_helper(args)
217
+ path = optimized_helper(args)
218
+ path << "/" if options[:trailing_slash] && !path.end_with?("/")
219
+ options[:path] = path
220
+
221
+ original_script_name = options.delete(:original_script_name)
222
+ script_name = t._routes.find_script_name(options)
223
+
224
+ if original_script_name
225
+ script_name = original_script_name + script_name
226
+ end
227
+
228
+ options[:script_name] = script_name
229
+
221
230
  url_strategy.call options
222
231
  else
223
232
  super
@@ -225,59 +234,59 @@ module ActionDispatch
225
234
  end
226
235
 
227
236
  private
237
+ def optimized_helper(args)
238
+ params = parameterize_args(args) do
239
+ raise_generation_error(args)
240
+ end
228
241
 
229
- def optimized_helper(args)
230
- params = parameterize_args(args)
231
- missing_keys = missing_keys(params)
232
-
233
- unless missing_keys.empty?
234
- raise_generation_error(params, missing_keys)
242
+ @route.format params
235
243
  end
236
244
 
237
- @route.format params
238
- end
239
-
240
- def optimize_routes_generation?(t)
241
- t.send(:optimize_routes_generation?)
242
- end
243
-
244
- def parameterize_args(args)
245
- params = {}
246
- @required_parts.zip(args.map(&:to_param)) { |k,v| params[k] = v }
247
- params
248
- end
245
+ def optimize_routes_generation?(t)
246
+ t.send(:optimize_routes_generation?)
247
+ end
249
248
 
250
- def missing_keys(args)
251
- args.select{ |part, arg| arg.nil? || arg.empty? }.keys
252
- end
249
+ def parameterize_args(args)
250
+ params = {}
251
+ @arg_size.times { |i|
252
+ key = @required_parts[i]
253
+ value = args[i].to_param
254
+ yield key if value.nil? || value.empty?
255
+ params[key] = value
256
+ }
257
+ params
258
+ end
253
259
 
254
- def raise_generation_error(args, missing_keys)
255
- constraints = Hash[@route.requirements.merge(args).sort_by{|k,v| k.to_s}]
256
- message = "No route matches #{constraints.inspect}"
257
- message << " missing required keys: #{missing_keys.sort.inspect}"
260
+ def raise_generation_error(args)
261
+ missing_keys = []
262
+ params = parameterize_args(args) { |missing_key|
263
+ missing_keys << missing_key
264
+ }
265
+ constraints = Hash[@route.requirements.merge(params).sort_by { |k, v| k.to_s }]
266
+ message = +"No route matches #{constraints.inspect}"
267
+ message << ", missing required keys: #{missing_keys.sort.inspect}"
258
268
 
259
- raise ActionController::UrlGenerationError, message
260
- end
269
+ raise ActionController::UrlGenerationError, message
270
+ end
261
271
  end
262
272
 
263
- def initialize(route, options, route_name, url_strategy)
273
+ def initialize(route, options, route_name)
264
274
  @options = options
265
275
  @segment_keys = route.segment_keys.uniq
266
276
  @route = route
267
- @url_strategy = url_strategy
268
277
  @route_name = route_name
269
278
  end
270
279
 
271
- def call(t, args, inner_options)
280
+ def call(t, method_name, args, inner_options, url_strategy)
272
281
  controller_options = t.url_options
273
282
  options = controller_options.merge @options
274
283
  hash = handle_positional_args(controller_options,
275
- deprecate_string_options(inner_options) || {},
284
+ inner_options || {},
276
285
  args,
277
286
  options,
278
287
  @segment_keys)
279
288
 
280
- t._routes.url_for(hash, route_name, url_strategy)
289
+ t._routes.url_for(hash, route_name, url_strategy, method_name)
281
290
  end
282
291
 
283
292
  def handle_positional_args(controller_options, inner_options, args, result, path_params)
@@ -291,118 +300,148 @@ module ActionDispatch
291
300
 
292
301
  if args.size < path_params_size
293
302
  path_params -= controller_options.keys
294
- path_params -= result.keys
303
+ path_params -= (result[:path_params] || {}).merge(result).keys
304
+ else
305
+ path_params = path_params.dup
306
+ end
307
+ inner_options.each_key do |key|
308
+ path_params.delete(key)
295
309
  end
296
- path_params.each { |param|
297
- value = inner_options.fetch(param) { args.shift }
298
310
 
299
- unless param == :format && value.nil?
300
- result[param] = value
301
- end
302
- }
311
+ args.each_with_index do |arg, index|
312
+ param = path_params[index]
313
+ result[param] = arg if param
314
+ end
303
315
  end
304
316
 
305
317
  result.merge!(inner_options)
306
318
  end
307
-
308
- DEPRECATED_STRING_OPTIONS = %w[controller action]
309
-
310
- def deprecate_string_options(options)
311
- options ||= {}
312
- deprecated_string_options = options.keys & DEPRECATED_STRING_OPTIONS
313
- if deprecated_string_options.any?
314
- msg = "Calling URL helpers with string keys #{deprecated_string_options.join(", ")} is deprecated. Use symbols instead."
315
- ActiveSupport::Deprecation.warn(msg)
316
- deprecated_string_options.each do |option|
317
- value = options.delete(option)
318
- options[option.to_sym] = value
319
- end
320
- end
321
- options
322
- end
323
319
  end
324
320
 
325
321
  private
326
- # Create a url helper allowing ordered parameters to be associated
327
- # with corresponding dynamic segments, so you can do:
328
- #
329
- # foo_url(bar, baz, bang)
330
- #
331
- # Instead of:
332
- #
333
- # foo_url(bar: bar, baz: baz, bang: bang)
334
- #
335
- # Also allow options hash, so you can do:
336
- #
337
- # foo_url(bar, baz, bang, sort_by: 'baz')
338
- #
339
- def define_url_helper(mod, route, name, opts, route_key, url_strategy)
340
- helper = UrlHelper.create(route, opts, route_key, url_strategy)
341
- mod.module_eval do
342
- define_method(name) do |*args|
343
- options = nil
344
- options = args.pop if args.last.is_a? Hash
345
- helper.call self, args, options
322
+ # Create a URL helper allowing ordered parameters to be associated with
323
+ # corresponding dynamic segments, so you can do:
324
+ #
325
+ # foo_url(bar, baz, bang)
326
+ #
327
+ # Instead of:
328
+ #
329
+ # foo_url(bar: bar, baz: baz, bang: bang)
330
+ #
331
+ # Also allow options hash, so you can do:
332
+ #
333
+ # foo_url(bar, baz, bang, sort_by: 'baz')
334
+ #
335
+ def define_url_helper(mod, name, helper, url_strategy)
336
+ mod.define_method(name) do |*args|
337
+ last = args.last
338
+ options = \
339
+ case last
340
+ when Hash
341
+ args.pop
342
+ when ActionController::Parameters
343
+ args.pop.to_h
344
+ end
345
+ helper.call(self, name, args, options, url_strategy)
346
346
  end
347
347
  end
348
- end
349
348
  end
350
349
 
351
- # strategy for building urls to send to the client
350
+ # strategy for building URLs to send to the client
352
351
  PATH = ->(options) { ActionDispatch::Http::URL.path_for(options) }
353
- FULL = ->(options) { ActionDispatch::Http::URL.full_url_for(options) }
354
352
  UNKNOWN = ->(options) { ActionDispatch::Http::URL.url_for(options) }
355
- LEGACY = ->(options) {
356
- if options.key?(:only_path)
357
- if options[:only_path]
358
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
359
- You are calling a `*_path` helper with the `only_path` option
360
- explicitly set to `true`. This option will stop working on
361
- path helpers in Rails 5. Simply remove the `only_path: true`
362
- argument from your call as it is redundant when applied to a
363
- path helper.
364
- MSG
365
-
366
- PATH.call(options)
367
- else
368
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
369
- You are calling a `*_path` helper with the `only_path` option
370
- explicitly set to `false`. This option will stop working on
371
- path helpers in Rails 5. Use the corresponding `*_url` helper
372
- instead.
373
- MSG
374
-
375
- FULL.call(options)
376
- end
377
- else
378
- PATH.call(options)
379
- end
380
- }
381
353
 
382
354
  attr_accessor :formatter, :set, :named_routes, :default_scope, :router
383
355
  attr_accessor :disable_clear_and_finalize, :resources_path_names
384
- attr_accessor :default_url_options, :request_class
356
+ attr_accessor :default_url_options, :draw_paths
357
+ attr_reader :env_key, :polymorphic_mappings
385
358
 
386
359
  alias :routes :set
387
360
 
388
361
  def self.default_resources_path_names
389
- { :new => 'new', :edit => 'edit' }
362
+ { new: "new", edit: "edit" }
363
+ end
364
+
365
+ def self.new_with_config(config)
366
+ route_set_config = DEFAULT_CONFIG
367
+
368
+ # engines apparently don't have this set
369
+ if config.respond_to? :relative_url_root
370
+ route_set_config.relative_url_root = config.relative_url_root
371
+ end
372
+
373
+ if config.respond_to? :api_only
374
+ route_set_config.api_only = config.api_only
375
+ end
376
+
377
+ new route_set_config
390
378
  end
391
379
 
392
- def initialize(request_class = ActionDispatch::Request)
380
+ Config = Struct.new :relative_url_root, :api_only
381
+
382
+ DEFAULT_CONFIG = Config.new(nil, false)
383
+
384
+ def initialize(config = DEFAULT_CONFIG)
393
385
  self.named_routes = NamedRouteCollection.new
394
386
  self.resources_path_names = self.class.default_resources_path_names
395
387
  self.default_url_options = {}
396
- self.request_class = request_class
388
+ self.draw_paths = []
397
389
 
390
+ @config = config
398
391
  @append = []
399
392
  @prepend = []
400
393
  @disable_clear_and_finalize = false
401
394
  @finalized = false
395
+ @env_key = "ROUTES_#{object_id}_SCRIPT_NAME"
396
+ @default_env = nil
402
397
 
403
398
  @set = Journey::Routes.new
404
399
  @router = Journey::Router.new @set
405
- @formatter = Journey::Formatter.new @set
400
+ @formatter = Journey::Formatter.new self
401
+ @polymorphic_mappings = {}
402
+ end
403
+
404
+ def eager_load!
405
+ router.eager_load!
406
+ routes.each(&:eager_load!)
407
+ formatter.eager_load!
408
+ nil
409
+ end
410
+
411
+ def relative_url_root
412
+ @config.relative_url_root
413
+ end
414
+
415
+ def api_only?
416
+ @config.api_only
417
+ end
418
+
419
+ def request_class
420
+ ActionDispatch::Request
421
+ end
422
+
423
+ def make_request(env)
424
+ request_class.new env
425
+ end
426
+ private :make_request
427
+
428
+ def default_env
429
+ if default_url_options != @default_env&.[]("action_dispatch.routes.default_url_options")
430
+ url_options = default_url_options.dup.freeze
431
+ uri = URI(ActionDispatch::Http::URL.full_url_for(host: "example.org", **url_options))
432
+
433
+ @default_env = {
434
+ "action_dispatch.routes" => self,
435
+ "action_dispatch.routes.default_url_options" => url_options,
436
+ "HTTPS" => uri.scheme == "https" ? "on" : "off",
437
+ "rack.url_scheme" => uri.scheme,
438
+ "HTTP_HOST" => uri.port == uri.default_port ? uri.host : "#{uri.host}:#{uri.port}",
439
+ "SCRIPT_NAME" => uri.path.chomp("/"),
440
+ "rack.input" => "",
441
+ }.freeze
442
+ end
443
+
444
+ @default_env
406
445
  end
407
446
 
408
447
  def draw(&block)
@@ -421,10 +460,6 @@ module ActionDispatch
421
460
  end
422
461
 
423
462
  def eval_block(block)
424
- if block.arity == 1
425
- raise "You are using the old router DSL which has been removed in Rails 3.1. " <<
426
- "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/"
427
- end
428
463
  mapper = Mapper.new(self)
429
464
  if default_scope
430
465
  mapper.with_default_scope(default_scope, &block)
@@ -445,27 +480,23 @@ module ActionDispatch
445
480
  named_routes.clear
446
481
  set.clear
447
482
  formatter.clear
483
+ @polymorphic_mappings.clear
448
484
  @prepend.each { |blk| eval_block(blk) }
449
485
  end
450
486
 
451
- def dispatcher(defaults)
452
- Routing::RouteSet::Dispatcher.new(defaults)
453
- end
454
-
455
487
  module MountedHelpers
456
488
  extend ActiveSupport::Concern
457
489
  include UrlFor
458
490
  end
459
491
 
460
- # Contains all the mounted helpers across different
461
- # engines and the `main_app` helper for the application.
462
- # You can include this in your classes if you want to
463
- # access routes for other engines.
492
+ # Contains all the mounted helpers across different engines and the `main_app`
493
+ # helper for the application. You can include this in your classes if you want
494
+ # to access routes for other engines.
464
495
  def mounted_helpers
465
496
  MountedHelpers
466
497
  end
467
498
 
468
- def define_mounted_helper(name)
499
+ def define_mounted_helper(name, script_namer = nil)
469
500
  return if MountedHelpers.method_defined?(name)
470
501
 
471
502
  routes = self
@@ -473,7 +504,7 @@ module ActionDispatch
473
504
 
474
505
  MountedHelpers.class_eval do
475
506
  define_method "_#{name}" do
476
- RoutesProxy.new(routes, _routes_context, helpers)
507
+ RoutesProxy.new(routes, _routes_context, helpers, script_namer)
477
508
  end
478
509
  end
479
510
 
@@ -485,6 +516,14 @@ module ActionDispatch
485
516
  end
486
517
 
487
518
  def url_helpers(supports_path = true)
519
+ if supports_path
520
+ @url_helpers_with_paths ||= generate_url_helpers(true)
521
+ else
522
+ @url_helpers_without_paths ||= generate_url_helpers(false)
523
+ end
524
+ end
525
+
526
+ def generate_url_helpers(supports_path)
488
527
  routes = self
489
528
 
490
529
  Module.new do
@@ -493,41 +532,76 @@ module ActionDispatch
493
532
 
494
533
  # Define url_for in the singleton level so one can do:
495
534
  # Rails.application.routes.url_helpers.url_for(args)
496
- @_routes = routes
497
- class << self
498
- delegate :url_for, :optimize_routes_generation?, to: '@_routes'
535
+ proxy_class = Class.new do
536
+ include UrlFor
537
+ include routes.named_routes.path_helpers_module
538
+ include routes.named_routes.url_helpers_module
539
+
499
540
  attr_reader :_routes
541
+
542
+ def initialize(routes)
543
+ @_routes = routes
544
+ end
545
+
546
+ def optimize_routes_generation?
547
+ @_routes.optimize_routes_generation?
548
+ end
549
+ end
550
+
551
+ @_proxy = proxy_class.new(routes)
552
+
553
+ class << self
554
+ def url_for(options)
555
+ @_proxy.url_for(options)
556
+ end
557
+
558
+ def full_url_for(options)
559
+ @_proxy.full_url_for(options)
560
+ end
561
+
562
+ def route_for(name, *args)
563
+ @_proxy.route_for(name, *args)
564
+ end
565
+
566
+ def optimize_routes_generation?
567
+ @_proxy.optimize_routes_generation?
568
+ end
569
+
570
+ def polymorphic_url(record_or_hash_or_array, options = {})
571
+ @_proxy.polymorphic_url(record_or_hash_or_array, options)
572
+ end
573
+
574
+ def polymorphic_path(record_or_hash_or_array, options = {})
575
+ @_proxy.polymorphic_path(record_or_hash_or_array, options)
576
+ end
577
+
578
+ def _routes; @_proxy._routes; end
500
579
  def url_options; {}; end
501
580
  end
502
581
 
503
582
  url_helpers = routes.named_routes.url_helpers_module
504
583
 
505
- # Make named_routes available in the module singleton
506
- # as well, so one can do:
584
+ # Make named_routes available in the module singleton as well, so one can do:
507
585
  # Rails.application.routes.url_helpers.posts_path
508
586
  extend url_helpers
509
587
 
510
- # Any class that includes this module will get all
511
- # named routes...
588
+ # Any class that includes this module will get all named routes...
512
589
  include url_helpers
513
590
 
514
591
  if supports_path
515
592
  path_helpers = routes.named_routes.path_helpers_module
516
- else
517
- path_helpers = routes.named_routes.path_helpers_module(true)
518
- end
519
593
 
520
- include path_helpers
521
- extend path_helpers
594
+ include path_helpers
595
+ extend path_helpers
596
+ end
522
597
 
523
598
  # plus a singleton class method called _routes ...
524
599
  included do
525
- singleton_class.send(:redefine_method, :_routes) { routes }
600
+ redefine_singleton_method(:_routes) { routes }
526
601
  end
527
602
 
528
- # And an instance method _routes. Note that
529
- # UrlFor (included in this module) add extra
530
- # conveniences for working with @_routes.
603
+ # And an instance method _routes. Note that UrlFor (included in this module) add
604
+ # extra conveniences for working with @_routes.
531
605
  define_method(:_routes) { @_routes || routes }
532
606
 
533
607
  define_method(:_generate_paths_by_default) do
@@ -535,6 +609,20 @@ module ActionDispatch
535
609
  end
536
610
 
537
611
  private :_generate_paths_by_default
612
+
613
+ # If the module is included more than once (for example, in a subclass of an
614
+ # ancestor that includes the module), ensure that the `_routes` singleton and
615
+ # instance methods return the desired route set by including a new copy of the
616
+ # module (recursively if necessary). Note that this method is called for each
617
+ # inclusion, whereas the above `included` block is run only for the initial
618
+ # inclusion of each copy.
619
+ def self.included(base)
620
+ super
621
+ if base.respond_to?(:_routes) && !base._routes.equal?(@_proxy._routes)
622
+ @dup_for_reinclude ||= self.dup
623
+ base.include @dup_for_reinclude
624
+ end
625
+ end
538
626
  end
539
627
  end
540
628
 
@@ -542,7 +630,7 @@ module ActionDispatch
542
630
  routes.empty?
543
631
  end
544
632
 
545
- def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true)
633
+ def add_route(mapping, name)
546
634
  raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i)
547
635
 
548
636
  if name && named_routes[name]
@@ -550,94 +638,80 @@ module ActionDispatch
550
638
  "You may have defined two routes with the same name using the `:as` option, or " \
551
639
  "you may be overriding a route already defined by a resource with the same naming. " \
552
640
  "For the latter, you can restrict the routes created with `resources` as explained here: \n" \
553
- "http://guides.rubyonrails.org/routing.html#restricting-the-routes-created"
641
+ "https://guides.rubyonrails.org/routing.html#restricting-the-routes-created"
554
642
  end
555
643
 
556
- path = conditions.delete :path_info
557
- ast = conditions.delete :parsed_path_info
558
- path = build_path(path, ast, requirements, anchor)
559
- conditions = build_conditions(conditions, path.names.map { |x| x.to_sym })
560
-
561
- route = @set.add_route(app, path, conditions, defaults, name)
644
+ route = @set.add_route(name, mapping)
562
645
  named_routes[name] = route if name
563
- route
564
- end
565
646
 
566
- def build_path(path, ast, requirements, anchor)
567
- strexp = Journey::Router::Strexp.new(
568
- ast,
569
- path,
570
- requirements,
571
- SEPARATORS,
572
- anchor)
573
-
574
- pattern = Journey::Path::Pattern.new(strexp)
575
-
576
- builder = Journey::GTG::Builder.new pattern.spec
647
+ if route.segment_keys.include?(:controller)
648
+ ActionDispatch.deprecator.warn(<<-MSG.squish)
649
+ Using a dynamic :controller segment in a route is deprecated and
650
+ will be removed in Rails 7.2.
651
+ MSG
652
+ end
577
653
 
578
- # Get all the symbol nodes followed by literals that are not the
579
- # dummy node.
580
- symbols = pattern.spec.grep(Journey::Nodes::Symbol).find_all { |n|
581
- builder.followpos(n).first.literal?
582
- }
654
+ if route.segment_keys.include?(:action)
655
+ ActionDispatch.deprecator.warn(<<-MSG.squish)
656
+ Using a dynamic :action segment in a route is deprecated and
657
+ will be removed in Rails 7.2.
658
+ MSG
659
+ end
583
660
 
584
- # Get all the symbol nodes preceded by literals.
585
- symbols.concat pattern.spec.find_all(&:literal?).map { |n|
586
- builder.followpos(n).first
587
- }.find_all(&:symbol?)
661
+ route
662
+ end
588
663
 
589
- symbols.each { |x|
590
- x.regexp = /(?:#{Regexp.union(x.regexp, '-')})+/
591
- }
664
+ def add_polymorphic_mapping(klass, options, &block)
665
+ @polymorphic_mappings[klass] = CustomUrlHelper.new(klass, options, &block)
666
+ end
592
667
 
593
- pattern
668
+ def add_url_helper(name, options, &block)
669
+ named_routes.add_url_helper(name, options, &block)
594
670
  end
595
- private :build_path
596
671
 
597
- def build_conditions(current_conditions, path_values)
598
- conditions = current_conditions.dup
672
+ class CustomUrlHelper
673
+ attr_reader :name, :defaults, :block
599
674
 
600
- # Rack-Mount requires that :request_method be a regular expression.
601
- # :request_method represents the HTTP verb that matches this route.
602
- #
603
- # Here we munge values before they get sent on to rack-mount.
604
- verbs = conditions[:request_method] || []
605
- unless verbs.empty?
606
- conditions[:request_method] = %r[^#{verbs.join('|')}$]
675
+ def initialize(name, defaults, &block)
676
+ @name = name
677
+ @defaults = defaults
678
+ @block = block
607
679
  end
608
680
 
609
- conditions.keep_if do |k, _|
610
- k == :action || k == :controller || k == :required_defaults ||
611
- @request_class.public_method_defined?(k) || path_values.include?(k)
612
- end
613
- end
614
- private :build_conditions
681
+ def call(t, args, only_path = false)
682
+ options = args.extract_options!
683
+ url = t.full_url_for(eval_block(t, args, options))
615
684
 
616
- class Generator
617
- PARAMETERIZE = lambda do |name, value|
618
- if name == :controller
619
- value
620
- elsif value.is_a?(Array)
621
- value.map { |v| v.to_param }.join('/')
622
- elsif param = value.to_param
623
- param
685
+ if only_path
686
+ "/" + url.partition(%r{(?<!/)/(?!/)}).last
687
+ else
688
+ url
624
689
  end
625
690
  end
626
691
 
692
+ private
693
+ def eval_block(t, args, options)
694
+ t.instance_exec(*args, merge_defaults(options), &block)
695
+ end
696
+
697
+ def merge_defaults(options)
698
+ defaults ? defaults.merge(options) : options
699
+ end
700
+ end
701
+
702
+ class Generator
627
703
  attr_reader :options, :recall, :set, :named_route
628
704
 
629
705
  def initialize(named_route, options, recall, set)
630
706
  @named_route = named_route
631
- @options = options.dup
632
- @recall = recall.dup
707
+ @options = options
708
+ @recall = recall
633
709
  @set = set
634
710
 
635
- normalize_recall!
636
711
  normalize_options!
637
712
  normalize_controller_action_id!
638
713
  use_relative_controller!
639
714
  normalize_controller!
640
- normalize_action!
641
715
  end
642
716
 
643
717
  def controller
@@ -651,52 +725,46 @@ module ActionDispatch
651
725
  def use_recall_for(key)
652
726
  if @recall[key] && (!@options.key?(key) || @options[key] == @recall[key])
653
727
  if !named_route_exists? || segment_keys.include?(key)
654
- @options[key] = @recall.delete(key)
728
+ @options[key] = @recall[key]
655
729
  end
656
730
  end
657
731
  end
658
732
 
659
- # Set 'index' as default action for recall
660
- def normalize_recall!
661
- @recall[:action] ||= 'index'
662
- end
663
-
664
733
  def normalize_options!
665
- # If an explicit :controller was given, always make :action explicit
666
- # too, so that action expiry works as expected for things like
734
+ # If an explicit :controller was given, always make :action explicit too, so
735
+ # that action expiry works as expected for things like
667
736
  #
668
- # generate({controller: 'content'}, {controller: 'content', action: 'show'})
737
+ # generate({controller: 'content'}, {controller: 'content', action: 'show'})
669
738
  #
670
- # (the above is from the unit tests). In the above case, because the
671
- # controller was explicitly given, but no action, the action is implied to
672
- # be "index", not the recalled action of "show".
739
+ # (the above is from the unit tests). In the above case, because the controller
740
+ # was explicitly given, but no action, the action is implied to be "index", not
741
+ # the recalled action of "show".
673
742
 
674
743
  if options[:controller]
675
- options[:action] ||= 'index'
744
+ options[:action] ||= "index"
676
745
  options[:controller] = options[:controller].to_s
677
746
  end
678
747
 
679
748
  if options.key?(:action)
680
- options[:action] = (options[:action] || 'index').to_s
749
+ options[:action] = (options[:action] || "index").to_s
681
750
  end
682
751
  end
683
752
 
684
- # This pulls :controller, :action, and :id out of the recall.
685
- # The recall key is only used if there is no key in the options
686
- # or if the key in the options is identical. If any of
687
- # :controller, :action or :id is not found, don't pull any
753
+ # This pulls :controller, :action, and :id out of the recall. The recall key is
754
+ # only used if there is no key in the options or if the key in the options is
755
+ # identical. If any of :controller, :action or :id is not found, don't pull any
688
756
  # more keys from the recall.
689
757
  def normalize_controller_action_id!
690
- use_recall_for(:controller) or return
691
- use_recall_for(:action) or return
758
+ use_recall_for(:controller) || return
759
+ use_recall_for(:action) || return
692
760
  use_recall_for(:id)
693
761
  end
694
762
 
695
- # if the current controller is "foo/bar/baz" and controller: "baz/bat"
696
- # is specified, the controller becomes "foo/baz/bat"
763
+ # if the current controller is "foo/bar/baz" and controller: "baz/bat" is
764
+ # specified, the controller becomes "foo/baz/bat"
697
765
  def use_relative_controller!
698
766
  if !named_route && different_controller? && !controller.start_with?("/")
699
- old_parts = current_controller.split('/')
767
+ old_parts = current_controller.split("/")
700
768
  size = controller.count("/") + 1
701
769
  parts = old_parts[0...-size] << controller
702
770
  @options[:controller] = parts.join("/")
@@ -705,20 +773,19 @@ module ActionDispatch
705
773
 
706
774
  # Remove leading slashes from controllers
707
775
  def normalize_controller!
708
- @options[:controller] = controller.sub(%r{^/}, '') if controller
709
- end
710
-
711
- # Move 'index' action from options to recall
712
- def normalize_action!
713
- if @options[:action] == 'index'
714
- @recall[:action] = @options.delete(:action)
776
+ if controller
777
+ if controller.start_with?("/")
778
+ @options[:controller] = controller[1..-1]
779
+ else
780
+ @options[:controller] = controller
781
+ end
715
782
  end
716
783
  end
717
784
 
718
- # Generates a path from routes, returns [path, params].
719
- # If no route is generated the formatter will raise ActionController::UrlGenerationError
785
+ # Generates a path from routes, returns a RouteWithParams or MissingRoute.
786
+ # MissingRoute will raise ActionController::UrlGenerationError.
720
787
  def generate
721
- @set.formatter.generate(named_route, options, recall, PARAMETERIZE)
788
+ @set.formatter.generate(named_route, options, recall)
722
789
  end
723
790
 
724
791
  def different_controller?
@@ -736,45 +803,46 @@ module ActionDispatch
736
803
  end
737
804
  end
738
805
 
739
- # Generate the path indicated by the arguments, and return an array of
740
- # the keys that were not used to generate it.
741
- def extra_keys(options, recall={})
806
+ # Generate the path indicated by the arguments, and return an array of the keys
807
+ # that were not used to generate it.
808
+ def extra_keys(options, recall = {})
742
809
  generate_extras(options, recall).last
743
810
  end
744
811
 
745
- def generate_extras(options, recall={})
746
- route_key = options.delete :use_route
747
- path, params = generate(route_key, options, recall)
748
- return path, params.keys
812
+ def generate_extras(options, recall = {})
813
+ if recall
814
+ options = options.merge(_recall: recall)
815
+ end
816
+
817
+ route_name = options.delete :use_route
818
+ generator = generate(route_name, options, recall)
819
+ path_info = path_for(options, route_name, [])
820
+ [URI(path_info).path, generator.params.except(:_recall).keys]
749
821
  end
750
822
 
751
- def generate(route_key, options, recall = {})
752
- Generator.new(route_key, options, recall, self).generate
823
+ def generate(route_name, options, recall = {}, method_name = nil)
824
+ Generator.new(route_name, options, recall, self).generate
753
825
  end
754
826
  private :generate
755
827
 
756
828
  RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length,
757
829
  :trailing_slash, :anchor, :params, :only_path, :script_name,
758
- :original_script_name, :relative_url_root]
830
+ :original_script_name]
759
831
 
760
832
  def optimize_routes_generation?
761
833
  default_url_options.empty?
762
834
  end
763
835
 
764
836
  def find_script_name(options)
765
- options.delete(:script_name) || find_relative_url_root(options) || ''
766
- end
767
-
768
- def find_relative_url_root(options)
769
- options.delete(:relative_url_root) || relative_url_root
837
+ options.delete(:script_name) || relative_url_root || ""
770
838
  end
771
839
 
772
- def path_for(options, route_name = nil)
773
- url_for(options, route_name, PATH)
840
+ def path_for(options, route_name = nil, reserved = RESERVED_OPTIONS)
841
+ url_for(options, route_name, PATH, nil, reserved)
774
842
  end
775
843
 
776
- # The +options+ argument must be a hash whose keys are *symbols*.
777
- def url_for(options, route_name = nil, url_strategy = UNKNOWN)
844
+ # The `options` argument must be a hash whose keys are **symbols**.
845
+ def url_for(options, route_name = nil, url_strategy = UNKNOWN, method_name = nil, reserved = RESERVED_OPTIONS)
778
846
  options = default_url_options.merge options
779
847
 
780
848
  user = password = nil
@@ -784,7 +852,7 @@ module ActionDispatch
784
852
  password = options.delete :password
785
853
  end
786
854
 
787
- recall = options.delete(:_recall) { {} }
855
+ recall = options.delete(:_recall) { {} }
788
856
 
789
857
  original_script_name = options.delete(:original_script_name)
790
858
  script_name = find_script_name options
@@ -794,12 +862,23 @@ module ActionDispatch
794
862
  end
795
863
 
796
864
  path_options = options.dup
797
- RESERVED_OPTIONS.each { |ro| path_options.delete ro }
865
+ reserved.each { |ro| path_options.delete ro }
798
866
 
799
- path, params = generate(route_name, path_options, recall)
867
+ route_with_params = generate(route_name, path_options, recall)
868
+ path = route_with_params.path(method_name)
869
+
870
+ if options[:trailing_slash] && !options[:format] && !path.end_with?("/")
871
+ path += "/"
872
+ end
873
+
874
+ params = route_with_params.params
800
875
 
801
876
  if options.key? :params
802
- params.merge! options[:params]
877
+ if options[:params].respond_to?(:to_hash)
878
+ params.merge! options[:params]
879
+ else
880
+ params[:params] = options[:params]
881
+ end
803
882
  end
804
883
 
805
884
  options[:path] = path
@@ -812,47 +891,54 @@ module ActionDispatch
812
891
  end
813
892
 
814
893
  def call(env)
815
- req = request_class.new(env)
894
+ req = make_request(env)
816
895
  req.path_info = Journey::Router::Utils.normalize_path(req.path_info)
817
896
  @router.serve(req)
818
897
  end
819
898
 
820
899
  def recognize_path(path, environment = {})
821
900
  method = (environment[:method] || "GET").to_s.upcase
822
- path = Journey::Router::Utils.normalize_path(path) unless path =~ %r{://}
901
+ path = Journey::Router::Utils.normalize_path(path) unless path&.include?("://")
823
902
  extras = environment[:extras] || {}
824
903
 
825
904
  begin
826
- env = Rack::MockRequest.env_for(path, {:method => method})
905
+ env = Rack::MockRequest.env_for(path, method: method)
827
906
  rescue URI::InvalidURIError => e
828
907
  raise ActionController::RoutingError, e.message
829
908
  end
830
909
 
831
- req = request_class.new(env)
910
+ req = make_request(env)
911
+ recognize_path_with_request(req, path, extras)
912
+ end
913
+
914
+ def recognize_path_with_request(req, path, extras, raise_on_missing: true)
832
915
  @router.recognize(req) do |route, params|
833
916
  params.merge!(extras)
834
917
  params.each do |key, value|
835
918
  if value.is_a?(String)
836
919
  value = value.dup.force_encoding(Encoding::BINARY)
837
- params[key] = URI.parser.unescape(value)
920
+ params[key] = URI::DEFAULT_PARSER.unescape(value)
838
921
  end
839
922
  end
840
- old_params = req.path_parameters
841
- req.path_parameters = old_params.merge params
923
+ req.path_parameters = params
842
924
  app = route.app
843
925
  if app.matches?(req) && app.dispatcher?
844
- dispatcher = app.app
845
-
846
- if dispatcher.controller(params, false)
847
- dispatcher.prepare_params!(params)
848
- return params
849
- else
926
+ begin
927
+ req.controller_class
928
+ rescue NameError
850
929
  raise ActionController::RoutingError, "A route matches #{path.inspect}, but references missing controller: #{params[:controller].camelize}Controller"
851
930
  end
931
+
932
+ return req.path_parameters
933
+ elsif app.matches?(req) && app.engine?
934
+ path_parameters = app.rack_app.routes.recognize_path_with_request(req, path, extras, raise_on_missing: false)
935
+ return path_parameters if path_parameters
852
936
  end
853
937
  end
854
938
 
855
- raise ActionController::RoutingError, "No route matches #{path.inspect}"
939
+ if raise_on_missing
940
+ raise ActionController::RoutingError, "No route matches #{path.inspect}"
941
+ end
856
942
  end
857
943
  end
858
944
  # :startdoc: