omg-actionpack 8.0.0.alpha1

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