omg-actionpack 8.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
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,2372 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "active_support/core_ext/hash/slice"
6
+ require "active_support/core_ext/enumerable"
7
+ require "active_support/core_ext/array/extract_options"
8
+ require "active_support/core_ext/regexp"
9
+ require "action_dispatch/routing/redirection"
10
+ require "action_dispatch/routing/endpoint"
11
+
12
+ module ActionDispatch
13
+ module Routing
14
+ class Mapper
15
+ class BacktraceCleaner < ActiveSupport::BacktraceCleaner # :nodoc:
16
+ def initialize
17
+ super
18
+ remove_silencers!
19
+ add_core_silencer
20
+ add_stdlib_silencer
21
+ end
22
+ end
23
+
24
+ URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
25
+
26
+ cattr_accessor :route_source_locations, instance_accessor: false, default: false
27
+ cattr_accessor :backtrace_cleaner, instance_accessor: false, default: BacktraceCleaner.new
28
+
29
+ class Constraints < Routing::Endpoint # :nodoc:
30
+ attr_reader :app, :constraints
31
+
32
+ SERVE = ->(app, req) { app.serve req }
33
+ CALL = ->(app, req) { app.call req.env }
34
+
35
+ def initialize(app, constraints, strategy)
36
+ # Unwrap Constraints objects. I don't actually think it's possible to pass a
37
+ # Constraints object to this constructor, but there were multiple places that
38
+ # kept testing children of this object. I **think** they were just being
39
+ # defensive, but I have no idea.
40
+ if app.is_a?(self.class)
41
+ constraints += app.constraints
42
+ app = app.app
43
+ end
44
+
45
+ @strategy = strategy
46
+
47
+ @app, @constraints, = app, constraints
48
+ end
49
+
50
+ def dispatcher?; @strategy == SERVE; end
51
+
52
+ def matches?(req)
53
+ @constraints.all? do |constraint|
54
+ (constraint.respond_to?(:matches?) && constraint.matches?(req)) ||
55
+ (constraint.respond_to?(:call) && constraint.call(*constraint_args(constraint, req)))
56
+ end
57
+ end
58
+
59
+ def serve(req)
60
+ return [ 404, { Constants::X_CASCADE => "pass" }, [] ] unless matches?(req)
61
+
62
+ @strategy.call @app, req
63
+ end
64
+
65
+ private
66
+ def constraint_args(constraint, request)
67
+ arity = if constraint.respond_to?(:arity)
68
+ constraint.arity
69
+ else
70
+ constraint.method(:call).arity
71
+ end
72
+
73
+ if arity < 1
74
+ []
75
+ elsif arity == 1
76
+ [request]
77
+ else
78
+ [request.path_parameters, request]
79
+ end
80
+ end
81
+ end
82
+
83
+ class Mapping # :nodoc:
84
+ ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
85
+ OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z}
86
+
87
+ attr_reader :path, :requirements, :defaults, :to, :default_controller,
88
+ :default_action, :required_defaults, :ast, :scope_options
89
+
90
+ def self.build(scope, set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
91
+ scope_params = {
92
+ blocks: scope[:blocks] || [],
93
+ constraints: scope[:constraints] || {},
94
+ defaults: (scope[:defaults] || {}).dup,
95
+ module: scope[:module],
96
+ options: scope[:options] || {}
97
+ }
98
+
99
+ new set: set, ast: ast, controller: controller, default_action: default_action,
100
+ to: to, formatted: formatted, via: via, options_constraints: options_constraints,
101
+ anchor: anchor, scope_params: scope_params, options: scope_params[:options].merge(options)
102
+ end
103
+
104
+ def self.check_via(via)
105
+ if via.empty?
106
+ msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
107
+ "If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
108
+ "If you want to expose your action to GET, use `get` in the router:\n" \
109
+ " Instead of: match \"controller#action\"\n" \
110
+ " Do: get \"controller#action\""
111
+ raise ArgumentError, msg
112
+ end
113
+ via
114
+ end
115
+
116
+ def self.normalize_path(path, format)
117
+ path = Mapper.normalize_path(path)
118
+
119
+ if format == true
120
+ "#{path}.:format"
121
+ elsif optional_format?(path, format)
122
+ "#{path}(.:format)"
123
+ else
124
+ path
125
+ end
126
+ end
127
+
128
+ def self.optional_format?(path, format)
129
+ format != false && !path.match?(OPTIONAL_FORMAT_REGEX)
130
+ end
131
+
132
+ def initialize(set:, ast:, controller:, default_action:, to:, formatted:, via:, options_constraints:, anchor:, scope_params:, options:)
133
+ @defaults = scope_params[:defaults]
134
+ @set = set
135
+ @to = intern(to)
136
+ @default_controller = intern(controller)
137
+ @default_action = intern(default_action)
138
+ @anchor = anchor
139
+ @via = via
140
+ @internal = options.delete(:internal)
141
+ @scope_options = scope_params[:options]
142
+ ast = Journey::Ast.new(ast, formatted)
143
+
144
+ options = ast.wildcard_options.merge!(options)
145
+
146
+ options = normalize_options!(options, ast.path_params, scope_params[:module])
147
+
148
+ split_options = constraints(options, ast.path_params)
149
+
150
+ constraints = scope_params[:constraints].merge Hash[split_options[:constraints] || []]
151
+
152
+ if options_constraints.is_a?(Hash)
153
+ @defaults = Hash[options_constraints.find_all { |key, default|
154
+ URL_OPTIONS.include?(key) && (String === default || Integer === default)
155
+ }].merge @defaults
156
+ @blocks = scope_params[:blocks]
157
+ constraints.merge! options_constraints
158
+ else
159
+ @blocks = blocks(options_constraints)
160
+ end
161
+
162
+ requirements, conditions = split_constraints ast.path_params, constraints
163
+ verify_regexp_requirements requirements, ast.wildcard_options
164
+
165
+ formats = normalize_format(formatted)
166
+
167
+ @requirements = formats[:requirements].merge Hash[requirements]
168
+ @conditions = Hash[conditions]
169
+ @defaults = formats[:defaults].merge(@defaults).merge(normalize_defaults(options))
170
+
171
+ if ast.path_params.include?(:action) && !@requirements.key?(:action)
172
+ @defaults[:action] ||= "index"
173
+ end
174
+
175
+ @required_defaults = (split_options[:required_defaults] || []).map(&:first)
176
+
177
+ ast.requirements = @requirements
178
+ @path = Journey::Path::Pattern.new(ast, @requirements, JOINED_SEPARATORS, @anchor)
179
+ end
180
+
181
+ JOINED_SEPARATORS = SEPARATORS.join # :nodoc:
182
+
183
+ def make_route(name, precedence)
184
+ Journey::Route.new(name: name, app: application, path: path, constraints: conditions,
185
+ required_defaults: required_defaults, defaults: defaults,
186
+ request_method_match: request_method, precedence: precedence,
187
+ scope_options: scope_options, internal: @internal, source_location: route_source_location)
188
+ end
189
+
190
+ def application
191
+ app(@blocks)
192
+ end
193
+
194
+ def conditions
195
+ build_conditions @conditions, @set.request_class
196
+ end
197
+
198
+ def build_conditions(current_conditions, request_class)
199
+ conditions = current_conditions.dup
200
+
201
+ conditions.keep_if do |k, _|
202
+ request_class.public_method_defined?(k)
203
+ end
204
+ end
205
+ private :build_conditions
206
+
207
+ def request_method
208
+ @via.map { |x| Journey::Route.verb_matcher(x) }
209
+ end
210
+ private :request_method
211
+
212
+ private
213
+ def intern(object)
214
+ object.is_a?(String) ? -object : object
215
+ end
216
+
217
+ def normalize_options!(options, path_params, modyoule)
218
+ if path_params.include?(:controller)
219
+ raise ArgumentError, ":controller segment is not allowed within a namespace block" if modyoule
220
+
221
+ # Add a default constraint for :controller path segments that matches namespaced
222
+ # controllers with default routes like :controller/:action/:id(.:format), e.g:
223
+ # GET /admin/products/show/1
224
+ # # > { controller: 'admin/products', action: 'show', id: '1' }
225
+ options[:controller] ||= /.+?/
226
+ end
227
+
228
+ if to.respond_to?(:action) || to.respond_to?(:call)
229
+ options
230
+ else
231
+ if to.nil?
232
+ controller = default_controller
233
+ action = default_action
234
+ elsif to.is_a?(String)
235
+ if to.include?("#")
236
+ to_endpoint = to.split("#").map!(&:-@)
237
+ controller = to_endpoint[0]
238
+ action = to_endpoint[1]
239
+ else
240
+ controller = default_controller
241
+ action = to
242
+ end
243
+ else
244
+ raise ArgumentError, ":to must respond to `action` or `call`, or it must be a String that includes '#', or the controller should be implicit"
245
+ end
246
+
247
+ controller = add_controller_module(controller, modyoule)
248
+
249
+ options.merge! check_controller_and_action(path_params, controller, action)
250
+ end
251
+ end
252
+
253
+ def split_constraints(path_params, constraints)
254
+ constraints.partition do |key, requirement|
255
+ path_params.include?(key) || key == :controller
256
+ end
257
+ end
258
+
259
+ def normalize_format(formatted)
260
+ case formatted
261
+ when true
262
+ { requirements: { format: /.+/ },
263
+ defaults: {} }
264
+ when Regexp
265
+ { requirements: { format: formatted },
266
+ defaults: { format: nil } }
267
+ when String
268
+ { requirements: { format: Regexp.compile(formatted) },
269
+ defaults: { format: formatted } }
270
+ else
271
+ { requirements: {}, defaults: {} }
272
+ end
273
+ end
274
+
275
+ def verify_regexp_requirements(requirements, wildcard_options)
276
+ requirements.each do |requirement, regex|
277
+ next unless regex.is_a? Regexp
278
+
279
+ if ANCHOR_CHARACTERS_REGEX.match?(regex.source)
280
+ raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
281
+ end
282
+
283
+ if regex.multiline?
284
+ next if wildcard_options.key?(requirement)
285
+
286
+ raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{regex.inspect}"
287
+ end
288
+ end
289
+ end
290
+
291
+ def normalize_defaults(options)
292
+ Hash[options.reject { |_, default| Regexp === default }]
293
+ end
294
+
295
+ def app(blocks)
296
+ if to.respond_to?(:action)
297
+ Routing::RouteSet::StaticDispatcher.new to
298
+ elsif to.respond_to?(:call)
299
+ Constraints.new(to, blocks, Constraints::CALL)
300
+ elsif blocks.any?
301
+ Constraints.new(dispatcher(defaults.key?(:controller)), blocks, Constraints::SERVE)
302
+ else
303
+ dispatcher(defaults.key?(:controller))
304
+ end
305
+ end
306
+
307
+ def check_controller_and_action(path_params, controller, action)
308
+ hash = check_part(:controller, controller, path_params, {}) do |part|
309
+ translate_controller(part) {
310
+ message = +"'#{part}' is not a supported controller name. This can lead to potential routing problems."
311
+ message << " See https://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
312
+
313
+ raise ArgumentError, message
314
+ }
315
+ end
316
+
317
+ check_part(:action, action, path_params, hash) { |part|
318
+ part.is_a?(Regexp) ? part : part.to_s
319
+ }
320
+ end
321
+
322
+ def check_part(name, part, path_params, hash)
323
+ if part
324
+ hash[name] = yield(part)
325
+ else
326
+ unless path_params.include?(name)
327
+ message = "Missing :#{name} key on routes definition, please check your routes."
328
+ raise ArgumentError, message
329
+ end
330
+ end
331
+ hash
332
+ end
333
+
334
+ def add_controller_module(controller, modyoule)
335
+ if modyoule && !controller.is_a?(Regexp)
336
+ if controller&.start_with?("/")
337
+ -controller[1..-1]
338
+ else
339
+ -[modyoule, controller].compact.join("/")
340
+ end
341
+ else
342
+ controller
343
+ end
344
+ end
345
+
346
+ def translate_controller(controller)
347
+ return controller if Regexp === controller
348
+ return controller.to_s if /\A[a-z_0-9][a-z_0-9\/]*\z/.match?(controller)
349
+
350
+ yield
351
+ end
352
+
353
+ def blocks(callable_constraint)
354
+ unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?)
355
+ raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?"
356
+ end
357
+ [callable_constraint]
358
+ end
359
+
360
+ def constraints(options, path_params)
361
+ options.group_by do |key, option|
362
+ if Regexp === option
363
+ :constraints
364
+ else
365
+ if path_params.include?(key)
366
+ :path_params
367
+ else
368
+ :required_defaults
369
+ end
370
+ end
371
+ end
372
+ end
373
+
374
+ def dispatcher(raise_on_name_error)
375
+ Routing::RouteSet::Dispatcher.new raise_on_name_error
376
+ end
377
+
378
+ if Thread.respond_to?(:each_caller_location)
379
+ def route_source_location
380
+ if Mapper.route_source_locations
381
+ action_dispatch_dir = File.expand_path("..", __dir__)
382
+ Thread.each_caller_location do |location|
383
+ next if location.path.start_with?(action_dispatch_dir)
384
+
385
+ cleaned_path = Mapper.backtrace_cleaner.clean_frame(location.path)
386
+ next if cleaned_path.nil?
387
+
388
+ return "#{cleaned_path}:#{location.lineno}"
389
+ end
390
+ nil
391
+ end
392
+ end
393
+ else
394
+ def route_source_location
395
+ if Mapper.route_source_locations
396
+ action_dispatch_dir = File.expand_path("..", __dir__)
397
+ caller_locations.each do |location|
398
+ next if location.path.start_with?(action_dispatch_dir)
399
+
400
+ cleaned_path = Mapper.backtrace_cleaner.clean_frame(location.path)
401
+ next if cleaned_path.nil?
402
+
403
+ return "#{cleaned_path}:#{location.lineno}"
404
+ end
405
+ nil
406
+ end
407
+ end
408
+ end
409
+ end
410
+
411
+ # Invokes Journey::Router::Utils.normalize_path, then ensures that /(:locale)
412
+ # becomes (/:locale). Except for root cases, where the former is the correct
413
+ # one.
414
+ def self.normalize_path(path)
415
+ path = Journey::Router::Utils.normalize_path(path)
416
+
417
+ # the path for a root URL at this point can be something like
418
+ # "/(/:locale)(/:platform)/(:browser)", and we would want
419
+ # "/(:locale)(/:platform)(/:browser)" reverse "/(", "/((" etc to "(/", "((/" etc
420
+ path.gsub!(%r{/(\(+)/?}, '\1/')
421
+ # if a path is all optional segments, change the leading "(/" back to "/(" so it
422
+ # evaluates to "/" when interpreted with no options. Unless, however, at least
423
+ # one secondary segment consists of a static part, ex.
424
+ # "(/:locale)(/pages/:page)"
425
+ path.sub!(%r{^(\(+)/}, '/\1') if %r{^(\(+[^)]+\))(\(+/:[^)]+\))*$}.match?(path)
426
+ path
427
+ end
428
+
429
+ def self.normalize_name(name)
430
+ normalize_path(name)[1..-1].tr("/", "_")
431
+ end
432
+
433
+ module Base
434
+ # Matches a URL pattern to one or more routes.
435
+ #
436
+ # You should not use the `match` method in your router without specifying an
437
+ # HTTP method.
438
+ #
439
+ # If you want to expose your action to both GET and POST, use:
440
+ #
441
+ # # sets :controller, :action, and :id in params
442
+ # match ':controller/:action/:id', via: [:get, :post]
443
+ #
444
+ # Note that `:controller`, `:action`, and `:id` are interpreted as URL query
445
+ # parameters and thus available through `params` in an action.
446
+ #
447
+ # If you want to expose your action to GET, use `get` in the router:
448
+ #
449
+ # Instead of:
450
+ #
451
+ # match ":controller/:action/:id"
452
+ #
453
+ # Do:
454
+ #
455
+ # get ":controller/:action/:id"
456
+ #
457
+ # Two of these symbols are special, `:controller` maps to the controller and
458
+ # `:action` to the controller's action. A pattern can also map wildcard segments
459
+ # (globs) to params:
460
+ #
461
+ # get 'songs/*category/:title', to: 'songs#show'
462
+ #
463
+ # # 'songs/rock/classic/stairway-to-heaven' sets
464
+ # # params[:category] = 'rock/classic'
465
+ # # params[:title] = 'stairway-to-heaven'
466
+ #
467
+ # To match a wildcard parameter, it must have a name assigned to it. Without a
468
+ # variable name to attach the glob parameter to, the route can't be parsed.
469
+ #
470
+ # When a pattern points to an internal route, the route's `:action` and
471
+ # `:controller` should be set in options or hash shorthand. Examples:
472
+ #
473
+ # match 'photos/:id', to: 'photos#show', via: :get
474
+ # match 'photos/:id', controller: 'photos', action: 'show', via: :get
475
+ #
476
+ # A pattern can also point to a `Rack` endpoint i.e. anything that responds to
477
+ # `call`:
478
+ #
479
+ # match 'photos/:id', to: -> (hash) { [200, {}, ["Coming soon"]] }, via: :get
480
+ # match 'photos/:id', to: PhotoRackApp, via: :get
481
+ # # Yes, controller actions are just rack endpoints
482
+ # match 'photos/:id', to: PhotosController.action(:show), via: :get
483
+ #
484
+ # Because requesting various HTTP verbs with a single action has security
485
+ # implications, you must either specify the actions in the via options or use
486
+ # one of the [HttpHelpers](rdoc-ref:HttpHelpers) instead `match`
487
+ #
488
+ # ### Options
489
+ #
490
+ # Any options not seen here are passed on as params with the URL.
491
+ #
492
+ # :controller
493
+ # : The route's controller.
494
+ #
495
+ # :action
496
+ # : The route's action.
497
+ #
498
+ # :param
499
+ # : Overrides the default resource identifier `:id` (name of the dynamic
500
+ # segment used to generate the routes). You can access that segment from
501
+ # your controller using `params[<:param>]`. In your router:
502
+ #
503
+ # resources :users, param: :name
504
+ #
505
+ # The `users` resource here will have the following routes generated for it:
506
+ #
507
+ # GET /users(.:format)
508
+ # POST /users(.:format)
509
+ # GET /users/new(.:format)
510
+ # GET /users/:name/edit(.:format)
511
+ # GET /users/:name(.:format)
512
+ # PATCH/PUT /users/:name(.:format)
513
+ # DELETE /users/:name(.:format)
514
+ #
515
+ # You can override `ActiveRecord::Base#to_param` of a related model to
516
+ # construct a URL:
517
+ #
518
+ # class User < ActiveRecord::Base
519
+ # def to_param
520
+ # name
521
+ # end
522
+ # end
523
+ #
524
+ # user = User.find_by(name: 'Phusion')
525
+ # user_path(user) # => "/users/Phusion"
526
+ #
527
+ # :path
528
+ # : The path prefix for the routes.
529
+ #
530
+ # :module
531
+ # : The namespace for :controller.
532
+ #
533
+ # match 'path', to: 'c#a', module: 'sekret', controller: 'posts', via: :get
534
+ # # => Sekret::PostsController
535
+ #
536
+ # See `Scoping#namespace` for its scope equivalent.
537
+ #
538
+ # :as
539
+ # : The name used to generate routing helpers.
540
+ #
541
+ # :via
542
+ # : Allowed HTTP verb(s) for route.
543
+ #
544
+ # match 'path', to: 'c#a', via: :get
545
+ # match 'path', to: 'c#a', via: [:get, :post]
546
+ # match 'path', to: 'c#a', via: :all
547
+ #
548
+ # :to
549
+ # : Points to a `Rack` endpoint. Can be an object that responds to `call` or a
550
+ # string representing a controller's action.
551
+ #
552
+ # match 'path', to: 'controller#action', via: :get
553
+ # match 'path', to: -> (env) { [200, {}, ["Success!"]] }, via: :get
554
+ # match 'path', to: RackApp, via: :get
555
+ #
556
+ # :on
557
+ # : Shorthand for wrapping routes in a specific RESTful context. Valid values
558
+ # are `:member`, `:collection`, and `:new`. Only use within `resource(s)`
559
+ # block. For example:
560
+ #
561
+ # resource :bar do
562
+ # match 'foo', to: 'c#a', on: :member, via: [:get, :post]
563
+ # end
564
+ #
565
+ # Is equivalent to:
566
+ #
567
+ # resource :bar do
568
+ # member do
569
+ # match 'foo', to: 'c#a', via: [:get, :post]
570
+ # end
571
+ # end
572
+ #
573
+ # :constraints
574
+ # : Constrains parameters with a hash of regular expressions or an object that
575
+ # responds to `matches?`. In addition, constraints other than path can also
576
+ # be specified with any object that responds to `===` (e.g. String, Array,
577
+ # Range, etc.).
578
+ #
579
+ # match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via: :get
580
+ #
581
+ # match 'json_only', constraints: { format: 'json' }, via: :get
582
+ #
583
+ # class PermitList
584
+ # def matches?(request) request.remote_ip == '1.2.3.4' end
585
+ # end
586
+ # match 'path', to: 'c#a', constraints: PermitList.new, via: :get
587
+ #
588
+ # See `Scoping#constraints` for more examples with its scope equivalent.
589
+ #
590
+ # :defaults
591
+ # : Sets defaults for parameters
592
+ #
593
+ # # Sets params[:format] to 'jpg' by default
594
+ # match 'path', to: 'c#a', defaults: { format: 'jpg' }, via: :get
595
+ #
596
+ # See `Scoping#defaults` for its scope equivalent.
597
+ #
598
+ # :anchor
599
+ # : Boolean to anchor a `match` pattern. Default is true. When set to false,
600
+ # the pattern matches any request prefixed with the given path.
601
+ #
602
+ # # Matches any request starting with 'path'
603
+ # match 'path', to: 'c#a', anchor: false, via: :get
604
+ #
605
+ # :format
606
+ # : Allows you to specify the default value for optional `format` segment or
607
+ # disable it by supplying `false`.
608
+ #
609
+ def match(path, options = nil)
610
+ end
611
+
612
+ # Mount a Rack-based application to be used within the application.
613
+ #
614
+ # mount SomeRackApp, at: "some_route"
615
+ #
616
+ # For options, see `match`, as `mount` uses it internally.
617
+ #
618
+ # All mounted applications come with routing helpers to access them. These are
619
+ # named after the class specified, so for the above example the helper is either
620
+ # `some_rack_app_path` or `some_rack_app_url`. To customize this helper's name,
621
+ # use the `:as` option:
622
+ #
623
+ # mount(SomeRackApp, at: "some_route", as: "exciting")
624
+ #
625
+ # This will generate the `exciting_path` and `exciting_url` helpers which can be
626
+ # used to navigate to this mounted app.
627
+ def mount(app, options = nil)
628
+ if options
629
+ path = options.delete(:at)
630
+ elsif Hash === app
631
+ options = app
632
+ app, path = options.find { |k, _| k.respond_to?(:call) }
633
+ options.delete(app) if app
634
+ end
635
+
636
+ raise ArgumentError, "A rack application must be specified" unless app.respond_to?(:call)
637
+ raise ArgumentError, <<~MSG unless path
638
+ Must be called with mount point
639
+
640
+ mount SomeRackApp, at: "some_route"
641
+ or
642
+ mount(SomeRackApp => "some_route")
643
+ MSG
644
+
645
+ rails_app = rails_app? app
646
+ options[:as] ||= app_name(app, rails_app)
647
+
648
+ target_as = name_for_action(options[:as], path)
649
+ options[:via] ||= :all
650
+
651
+ match(path, { to: app, anchor: false, format: false }.merge(options))
652
+
653
+ define_generate_prefix(app, target_as) if rails_app
654
+ self
655
+ end
656
+
657
+ def default_url_options=(options)
658
+ @set.default_url_options = options
659
+ end
660
+ alias_method :default_url_options, :default_url_options=
661
+
662
+ def with_default_scope(scope, &block)
663
+ scope(scope) do
664
+ instance_exec(&block)
665
+ end
666
+ end
667
+
668
+ # Query if the following named route was already defined.
669
+ def has_named_route?(name)
670
+ @set.named_routes.key?(name)
671
+ end
672
+
673
+ private
674
+ def rails_app?(app)
675
+ app.is_a?(Class) && app < Rails::Railtie
676
+ end
677
+
678
+ def app_name(app, rails_app)
679
+ if rails_app
680
+ app.railtie_name
681
+ elsif app.is_a?(Class)
682
+ class_name = app.name
683
+ ActiveSupport::Inflector.underscore(class_name).tr("/", "_")
684
+ end
685
+ end
686
+
687
+ def define_generate_prefix(app, name)
688
+ _route = @set.named_routes.get name
689
+ _routes = @set
690
+ _url_helpers = @set.url_helpers
691
+
692
+ script_namer = ->(options) do
693
+ prefix_options = options.slice(*_route.segment_keys)
694
+ prefix_options[:script_name] = "" if options[:original_script_name]
695
+
696
+ if options[:_recall]
697
+ prefix_options.reverse_merge!(options[:_recall].slice(*_route.segment_keys))
698
+ end
699
+
700
+ # We must actually delete prefix segment keys to avoid passing them to next
701
+ # url_for.
702
+ _route.segment_keys.each { |k| options.delete(k) }
703
+ _url_helpers.public_send("#{name}_path", prefix_options)
704
+ end
705
+
706
+ app.routes.define_mounted_helper(name, script_namer)
707
+
708
+ app.routes.extend Module.new {
709
+ def optimize_routes_generation?; false; end
710
+
711
+ define_method :find_script_name do |options|
712
+ if options.key?(:script_name) && options[:script_name].present?
713
+ super(options)
714
+ else
715
+ script_namer.call(options)
716
+ end
717
+ end
718
+ }
719
+ end
720
+ end
721
+
722
+ module HttpHelpers
723
+ # Define a route that only recognizes HTTP GET. For supported arguments, see
724
+ # [match](rdoc-ref:Base#match)
725
+ #
726
+ # get 'bacon', to: 'food#bacon'
727
+ def get(*args, &block)
728
+ map_method(:get, args, &block)
729
+ end
730
+
731
+ # Define a route that only recognizes HTTP POST. For supported arguments, see
732
+ # [match](rdoc-ref:Base#match)
733
+ #
734
+ # post 'bacon', to: 'food#bacon'
735
+ def post(*args, &block)
736
+ map_method(:post, args, &block)
737
+ end
738
+
739
+ # Define a route that only recognizes HTTP PATCH. For supported arguments, see
740
+ # [match](rdoc-ref:Base#match)
741
+ #
742
+ # patch 'bacon', to: 'food#bacon'
743
+ def patch(*args, &block)
744
+ map_method(:patch, args, &block)
745
+ end
746
+
747
+ # Define a route that only recognizes HTTP PUT. For supported arguments, see
748
+ # [match](rdoc-ref:Base#match)
749
+ #
750
+ # put 'bacon', to: 'food#bacon'
751
+ def put(*args, &block)
752
+ map_method(:put, args, &block)
753
+ end
754
+
755
+ # Define a route that only recognizes HTTP DELETE. For supported arguments, see
756
+ # [match](rdoc-ref:Base#match)
757
+ #
758
+ # delete 'broccoli', to: 'food#broccoli'
759
+ def delete(*args, &block)
760
+ map_method(:delete, args, &block)
761
+ end
762
+
763
+ # Define a route that only recognizes HTTP OPTIONS. For supported arguments, see
764
+ # [match](rdoc-ref:Base#match)
765
+ #
766
+ # options 'carrots', to: 'food#carrots'
767
+ def options(*args, &block)
768
+ map_method(:options, args, &block)
769
+ end
770
+
771
+ # Define a route that recognizes HTTP CONNECT (and GET) requests. More
772
+ # specifically this recognizes HTTP/1 protocol upgrade requests and HTTP/2
773
+ # CONNECT requests with the protocol pseudo header. For supported arguments,
774
+ # see [match](rdoc-ref:Base#match)
775
+ #
776
+ # connect 'live', to: 'live#index'
777
+ def connect(*args, &block)
778
+ map_method([:get, :connect], args, &block)
779
+ end
780
+
781
+ private
782
+ def map_method(method, args, &block)
783
+ options = args.extract_options!
784
+ options[:via] = method
785
+ match(*args, options, &block)
786
+ self
787
+ end
788
+ end
789
+
790
+ # You may wish to organize groups of controllers under a namespace. Most
791
+ # commonly, you might group a number of administrative controllers under an
792
+ # `admin` namespace. You would place these controllers under the
793
+ # `app/controllers/admin` directory, and you can group them together in your
794
+ # router:
795
+ #
796
+ # namespace "admin" do
797
+ # resources :posts, :comments
798
+ # end
799
+ #
800
+ # This will create a number of routes for each of the posts and comments
801
+ # controller. For `Admin::PostsController`, Rails will create:
802
+ #
803
+ # GET /admin/posts
804
+ # GET /admin/posts/new
805
+ # POST /admin/posts
806
+ # GET /admin/posts/1
807
+ # GET /admin/posts/1/edit
808
+ # PATCH/PUT /admin/posts/1
809
+ # DELETE /admin/posts/1
810
+ #
811
+ # If you want to route /posts (without the prefix /admin) to
812
+ # `Admin::PostsController`, you could use
813
+ #
814
+ # scope module: "admin" do
815
+ # resources :posts
816
+ # end
817
+ #
818
+ # or, for a single case
819
+ #
820
+ # resources :posts, module: "admin"
821
+ #
822
+ # If you want to route /admin/posts to `PostsController` (without the `Admin::`
823
+ # module prefix), you could use
824
+ #
825
+ # scope "/admin" do
826
+ # resources :posts
827
+ # end
828
+ #
829
+ # or, for a single case
830
+ #
831
+ # resources :posts, path: "/admin/posts"
832
+ #
833
+ # In each of these cases, the named routes remain the same as if you did not use
834
+ # scope. In the last case, the following paths map to `PostsController`:
835
+ #
836
+ # GET /admin/posts
837
+ # GET /admin/posts/new
838
+ # POST /admin/posts
839
+ # GET /admin/posts/1
840
+ # GET /admin/posts/1/edit
841
+ # PATCH/PUT /admin/posts/1
842
+ # DELETE /admin/posts/1
843
+ module Scoping
844
+ # Scopes a set of routes to the given default options.
845
+ #
846
+ # Take the following route definition as an example:
847
+ #
848
+ # scope path: ":account_id", as: "account" do
849
+ # resources :projects
850
+ # end
851
+ #
852
+ # This generates helpers such as `account_projects_path`, just like `resources`
853
+ # does. The difference here being that the routes generated are like
854
+ # /:account_id/projects, rather than /accounts/:account_id/projects.
855
+ #
856
+ # ### Options
857
+ #
858
+ # Takes same options as `Base#match` and `Resources#resources`.
859
+ #
860
+ # # route /posts (without the prefix /admin) to +Admin::PostsController+
861
+ # scope module: "admin" do
862
+ # resources :posts
863
+ # end
864
+ #
865
+ # # prefix the posts resource's requests with '/admin'
866
+ # scope path: "/admin" do
867
+ # resources :posts
868
+ # end
869
+ #
870
+ # # prefix the routing helper name: +sekret_posts_path+ instead of +posts_path+
871
+ # scope as: "sekret" do
872
+ # resources :posts
873
+ # end
874
+ def scope(*args)
875
+ options = args.extract_options!.dup
876
+ scope = {}
877
+
878
+ options[:path] = args.flatten.join("/") if args.any?
879
+ options[:constraints] ||= {}
880
+
881
+ unless nested_scope?
882
+ options[:shallow_path] ||= options[:path] if options.key?(:path)
883
+ options[:shallow_prefix] ||= options[:as] if options.key?(:as)
884
+ end
885
+
886
+ if options[:constraints].is_a?(Hash)
887
+ defaults = options[:constraints].select do |k, v|
888
+ URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Integer))
889
+ end
890
+
891
+ options[:defaults] = defaults.merge(options[:defaults] || {})
892
+ else
893
+ block, options[:constraints] = options[:constraints], {}
894
+ end
895
+
896
+ if options.key?(:only) || options.key?(:except)
897
+ scope[:action_options] = { only: options.delete(:only),
898
+ except: options.delete(:except) }
899
+ end
900
+
901
+ if options.key? :anchor
902
+ raise ArgumentError, "anchor is ignored unless passed to `match`"
903
+ end
904
+
905
+ @scope.options.each do |option|
906
+ if option == :blocks
907
+ value = block
908
+ elsif option == :options
909
+ value = options
910
+ else
911
+ value = options.delete(option) { POISON }
912
+ end
913
+
914
+ unless POISON == value
915
+ scope[option] = send("merge_#{option}_scope", @scope[option], value)
916
+ end
917
+ end
918
+
919
+ @scope = @scope.new scope
920
+ yield
921
+ self
922
+ ensure
923
+ @scope = @scope.parent
924
+ end
925
+
926
+ POISON = Object.new # :nodoc:
927
+
928
+ # Scopes routes to a specific controller
929
+ #
930
+ # controller "food" do
931
+ # match "bacon", action: :bacon, via: :get
932
+ # end
933
+ def controller(controller)
934
+ @scope = @scope.new(controller: controller)
935
+ yield
936
+ ensure
937
+ @scope = @scope.parent
938
+ end
939
+
940
+ # Scopes routes to a specific namespace. For example:
941
+ #
942
+ # namespace :admin do
943
+ # resources :posts
944
+ # end
945
+ #
946
+ # This generates the following routes:
947
+ #
948
+ # admin_posts GET /admin/posts(.:format) admin/posts#index
949
+ # admin_posts POST /admin/posts(.:format) admin/posts#create
950
+ # new_admin_post GET /admin/posts/new(.:format) admin/posts#new
951
+ # edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
952
+ # admin_post GET /admin/posts/:id(.:format) admin/posts#show
953
+ # admin_post PATCH/PUT /admin/posts/:id(.:format) admin/posts#update
954
+ # admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
955
+ #
956
+ # ### Options
957
+ #
958
+ # The `:path`, `:as`, `:module`, `:shallow_path`, and `:shallow_prefix` options
959
+ # all default to the name of the namespace.
960
+ #
961
+ # For options, see `Base#match`. For `:shallow_path` option, see
962
+ # `Resources#resources`.
963
+ #
964
+ # # accessible through /sekret/posts rather than /admin/posts
965
+ # namespace :admin, path: "sekret" do
966
+ # resources :posts
967
+ # end
968
+ #
969
+ # # maps to +Sekret::PostsController+ rather than +Admin::PostsController+
970
+ # namespace :admin, module: "sekret" do
971
+ # resources :posts
972
+ # end
973
+ #
974
+ # # generates +sekret_posts_path+ rather than +admin_posts_path+
975
+ # namespace :admin, as: "sekret" do
976
+ # resources :posts
977
+ # end
978
+ def namespace(path, options = {}, &block)
979
+ path = path.to_s
980
+
981
+ defaults = {
982
+ module: path,
983
+ as: options.fetch(:as, path),
984
+ shallow_path: options.fetch(:path, path),
985
+ shallow_prefix: options.fetch(:as, path)
986
+ }
987
+
988
+ path_scope(options.delete(:path) { path }) do
989
+ scope(defaults.merge!(options), &block)
990
+ end
991
+ end
992
+
993
+ # ### Parameter Restriction
994
+ # Allows you to constrain the nested routes based on a set of rules. For
995
+ # instance, in order to change the routes to allow for a dot character in the
996
+ # `id` parameter:
997
+ #
998
+ # constraints(id: /\d+\.\d+/) do
999
+ # resources :posts
1000
+ # end
1001
+ #
1002
+ # Now routes such as `/posts/1` will no longer be valid, but `/posts/1.1` will
1003
+ # be. The `id` parameter must match the constraint passed in for this example.
1004
+ #
1005
+ # You may use this to also restrict other parameters:
1006
+ #
1007
+ # resources :posts do
1008
+ # constraints(post_id: /\d+\.\d+/) do
1009
+ # resources :comments
1010
+ # end
1011
+ # end
1012
+ #
1013
+ # ### Restricting based on IP
1014
+ #
1015
+ # Routes can also be constrained to an IP or a certain range of IP addresses:
1016
+ #
1017
+ # constraints(ip: /192\.168\.\d+\.\d+/) do
1018
+ # resources :posts
1019
+ # end
1020
+ #
1021
+ # Any user connecting from the 192.168.* range will be able to see this
1022
+ # resource, where as any user connecting outside of this range will be told
1023
+ # there is no such route.
1024
+ #
1025
+ # ### Dynamic request matching
1026
+ #
1027
+ # Requests to routes can be constrained based on specific criteria:
1028
+ #
1029
+ # constraints(-> (req) { /iPhone/.match?(req.env["HTTP_USER_AGENT"]) }) do
1030
+ # resources :iphones
1031
+ # end
1032
+ #
1033
+ # You are able to move this logic out into a class if it is too complex for
1034
+ # routes. This class must have a `matches?` method defined on it which either
1035
+ # returns `true` if the user should be given access to that route, or `false` if
1036
+ # the user should not.
1037
+ #
1038
+ # class Iphone
1039
+ # def self.matches?(request)
1040
+ # /iPhone/.match?(request.env["HTTP_USER_AGENT"])
1041
+ # end
1042
+ # end
1043
+ #
1044
+ # An expected place for this code would be `lib/constraints`.
1045
+ #
1046
+ # This class is then used like this:
1047
+ #
1048
+ # constraints(Iphone) do
1049
+ # resources :iphones
1050
+ # end
1051
+ def constraints(constraints = {}, &block)
1052
+ scope(constraints: constraints, &block)
1053
+ end
1054
+
1055
+ # Allows you to set default parameters for a route, such as this:
1056
+ # defaults id: 'home' do
1057
+ # match 'scoped_pages/(:id)', to: 'pages#show'
1058
+ # end
1059
+ #
1060
+ # Using this, the `:id` parameter here will default to 'home'.
1061
+ def defaults(defaults = {})
1062
+ @scope = @scope.new(defaults: merge_defaults_scope(@scope[:defaults], defaults))
1063
+ yield
1064
+ ensure
1065
+ @scope = @scope.parent
1066
+ end
1067
+
1068
+ private
1069
+ def merge_path_scope(parent, child)
1070
+ Mapper.normalize_path("#{parent}/#{child}")
1071
+ end
1072
+
1073
+ def merge_shallow_path_scope(parent, child)
1074
+ Mapper.normalize_path("#{parent}/#{child}")
1075
+ end
1076
+
1077
+ def merge_as_scope(parent, child)
1078
+ parent ? "#{parent}_#{child}" : child
1079
+ end
1080
+
1081
+ def merge_shallow_prefix_scope(parent, child)
1082
+ parent ? "#{parent}_#{child}" : child
1083
+ end
1084
+
1085
+ def merge_module_scope(parent, child)
1086
+ parent ? "#{parent}/#{child}" : child
1087
+ end
1088
+
1089
+ def merge_controller_scope(parent, child)
1090
+ child
1091
+ end
1092
+
1093
+ def merge_action_scope(parent, child)
1094
+ child
1095
+ end
1096
+
1097
+ def merge_via_scope(parent, child)
1098
+ child
1099
+ end
1100
+
1101
+ def merge_format_scope(parent, child)
1102
+ child
1103
+ end
1104
+
1105
+ def merge_path_names_scope(parent, child)
1106
+ merge_options_scope(parent, child)
1107
+ end
1108
+
1109
+ def merge_constraints_scope(parent, child)
1110
+ merge_options_scope(parent, child)
1111
+ end
1112
+
1113
+ def merge_defaults_scope(parent, child)
1114
+ merge_options_scope(parent, child)
1115
+ end
1116
+
1117
+ def merge_blocks_scope(parent, child)
1118
+ merged = parent ? parent.dup : []
1119
+ merged << child if child
1120
+ merged
1121
+ end
1122
+
1123
+ def merge_options_scope(parent, child)
1124
+ (parent || {}).merge(child)
1125
+ end
1126
+
1127
+ def merge_shallow_scope(parent, child)
1128
+ child ? true : false
1129
+ end
1130
+
1131
+ def merge_to_scope(parent, child)
1132
+ child
1133
+ end
1134
+ end
1135
+
1136
+ # Resource routing allows you to quickly declare all of the common routes for a
1137
+ # given resourceful controller. Instead of declaring separate routes for your
1138
+ # `index`, `show`, `new`, `edit`, `create`, `update`, and `destroy` actions, a
1139
+ # resourceful route declares them in a single line of code:
1140
+ #
1141
+ # resources :photos
1142
+ #
1143
+ # Sometimes, you have a resource that clients always look up without referencing
1144
+ # an ID. A common example, /profile always shows the profile of the currently
1145
+ # logged in user. In this case, you can use a singular resource to map /profile
1146
+ # (rather than /profile/:id) to the show action.
1147
+ #
1148
+ # resource :profile
1149
+ #
1150
+ # It's common to have resources that are logically children of other resources:
1151
+ #
1152
+ # resources :magazines do
1153
+ # resources :ads
1154
+ # end
1155
+ #
1156
+ # You may wish to organize groups of controllers under a namespace. Most
1157
+ # commonly, you might group a number of administrative controllers under an
1158
+ # `admin` namespace. You would place these controllers under the
1159
+ # `app/controllers/admin` directory, and you can group them together in your
1160
+ # router:
1161
+ #
1162
+ # namespace "admin" do
1163
+ # resources :posts, :comments
1164
+ # end
1165
+ #
1166
+ # By default the `:id` parameter doesn't accept dots. If you need to use dots as
1167
+ # part of the `:id` parameter add a constraint which overrides this restriction,
1168
+ # e.g:
1169
+ #
1170
+ # resources :articles, id: /[^\/]+/
1171
+ #
1172
+ # This allows any character other than a slash as part of your `:id`.
1173
+ module Resources
1174
+ # CANONICAL_ACTIONS holds all actions that does not need a prefix or a path
1175
+ # appended since they fit properly in their scope level.
1176
+ VALID_ON_OPTIONS = [:new, :collection, :member]
1177
+ RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns]
1178
+ CANONICAL_ACTIONS = %w(index create new show update destroy)
1179
+
1180
+ class Resource # :nodoc:
1181
+ attr_reader :controller, :path, :param
1182
+
1183
+ def initialize(entities, api_only, shallow, options = {})
1184
+ if options[:param].to_s.include?(":")
1185
+ raise ArgumentError, ":param option can't contain colons"
1186
+ end
1187
+
1188
+ @name = entities.to_s
1189
+ @path = (options[:path] || @name).to_s
1190
+ @controller = (options[:controller] || @name).to_s
1191
+ @as = options[:as]
1192
+ @param = (options[:param] || :id).to_sym
1193
+ @options = options
1194
+ @shallow = shallow
1195
+ @api_only = api_only
1196
+ @only = options.delete :only
1197
+ @except = options.delete :except
1198
+ end
1199
+
1200
+ def default_actions
1201
+ if @api_only
1202
+ [:index, :create, :show, :update, :destroy]
1203
+ else
1204
+ [:index, :create, :new, :show, :update, :destroy, :edit]
1205
+ end
1206
+ end
1207
+
1208
+ def actions
1209
+ if @except
1210
+ available_actions - Array(@except).map(&:to_sym)
1211
+ else
1212
+ available_actions
1213
+ end
1214
+ end
1215
+
1216
+ def available_actions
1217
+ if @only
1218
+ Array(@only).map(&:to_sym)
1219
+ else
1220
+ default_actions
1221
+ end
1222
+ end
1223
+
1224
+ def name
1225
+ @as || @name
1226
+ end
1227
+
1228
+ def plural
1229
+ @plural ||= name.to_s
1230
+ end
1231
+
1232
+ def singular
1233
+ @singular ||= name.to_s.singularize
1234
+ end
1235
+
1236
+ alias :member_name :singular
1237
+
1238
+ # Checks for uncountable plurals, and appends "_index" if the plural and
1239
+ # singular form are the same.
1240
+ def collection_name
1241
+ singular == plural ? "#{plural}_index" : plural
1242
+ end
1243
+
1244
+ def resource_scope
1245
+ controller
1246
+ end
1247
+
1248
+ alias :collection_scope :path
1249
+
1250
+ def member_scope
1251
+ "#{path}/:#{param}"
1252
+ end
1253
+
1254
+ alias :shallow_scope :member_scope
1255
+
1256
+ def new_scope(new_path)
1257
+ "#{path}/#{new_path}"
1258
+ end
1259
+
1260
+ def nested_param
1261
+ :"#{singular}_#{param}"
1262
+ end
1263
+
1264
+ def nested_scope
1265
+ "#{path}/:#{nested_param}"
1266
+ end
1267
+
1268
+ def shallow?
1269
+ @shallow
1270
+ end
1271
+
1272
+ def singleton?; false; end
1273
+ end
1274
+
1275
+ class SingletonResource < Resource # :nodoc:
1276
+ def initialize(entities, api_only, shallow, options)
1277
+ super
1278
+ @as = nil
1279
+ @controller = (options[:controller] || plural).to_s
1280
+ @as = options[:as]
1281
+ end
1282
+
1283
+ def default_actions
1284
+ if @api_only
1285
+ [:show, :create, :update, :destroy]
1286
+ else
1287
+ [:show, :create, :update, :destroy, :new, :edit]
1288
+ end
1289
+ end
1290
+
1291
+ def plural
1292
+ @plural ||= name.to_s.pluralize
1293
+ end
1294
+
1295
+ def singular
1296
+ @singular ||= name.to_s
1297
+ end
1298
+
1299
+ alias :member_name :singular
1300
+ alias :collection_name :singular
1301
+
1302
+ alias :member_scope :path
1303
+ alias :nested_scope :path
1304
+
1305
+ def singleton?; true; end
1306
+ end
1307
+
1308
+ def resources_path_names(options)
1309
+ @scope[:path_names].merge!(options)
1310
+ end
1311
+
1312
+ # Sometimes, you have a resource that clients always look up without referencing
1313
+ # an ID. A common example, /profile always shows the profile of the currently
1314
+ # logged in user. In this case, you can use a singular resource to map /profile
1315
+ # (rather than /profile/:id) to the show action:
1316
+ #
1317
+ # resource :profile
1318
+ #
1319
+ # This creates six different routes in your application, all mapping to the
1320
+ # `Profiles` controller (note that the controller is named after the plural):
1321
+ #
1322
+ # GET /profile/new
1323
+ # GET /profile
1324
+ # GET /profile/edit
1325
+ # PATCH/PUT /profile
1326
+ # DELETE /profile
1327
+ # POST /profile
1328
+ #
1329
+ # If you want instances of a model to work with this resource via record
1330
+ # identification (e.g. in `form_with` or `redirect_to`), you will need to call
1331
+ # [resolve](rdoc-ref:CustomUrls#resolve):
1332
+ #
1333
+ # resource :profile
1334
+ # resolve('Profile') { [:profile] }
1335
+ #
1336
+ # # Enables this to work with singular routes:
1337
+ # form_with(model: @profile) {}
1338
+ #
1339
+ # ### Options
1340
+ # Takes same options as [resources](rdoc-ref:#resources)
1341
+ def resource(*resources, &block)
1342
+ options = resources.extract_options!.dup
1343
+
1344
+ if apply_common_behavior_for(:resource, resources, options, &block)
1345
+ return self
1346
+ end
1347
+
1348
+ with_scope_level(:resource) do
1349
+ options = apply_action_options options
1350
+ resource_scope(SingletonResource.new(resources.pop, api_only?, @scope[:shallow], options)) do
1351
+ yield if block_given?
1352
+
1353
+ concerns(options[:concerns]) if options[:concerns]
1354
+
1355
+ new do
1356
+ get :new
1357
+ end if parent_resource.actions.include?(:new)
1358
+
1359
+ set_member_mappings_for_resource
1360
+
1361
+ collection do
1362
+ post :create
1363
+ end if parent_resource.actions.include?(:create)
1364
+ end
1365
+ end
1366
+
1367
+ self
1368
+ end
1369
+
1370
+ # In Rails, a resourceful route provides a mapping between HTTP verbs and URLs
1371
+ # and controller actions. By convention, each action also maps to particular
1372
+ # CRUD operations in a database. A single entry in the routing file, such as
1373
+ #
1374
+ # resources :photos
1375
+ #
1376
+ # creates seven different routes in your application, all mapping to the
1377
+ # `Photos` controller:
1378
+ #
1379
+ # GET /photos
1380
+ # GET /photos/new
1381
+ # POST /photos
1382
+ # GET /photos/:id
1383
+ # GET /photos/:id/edit
1384
+ # PATCH/PUT /photos/:id
1385
+ # DELETE /photos/:id
1386
+ #
1387
+ # Resources can also be nested infinitely by using this block syntax:
1388
+ #
1389
+ # resources :photos do
1390
+ # resources :comments
1391
+ # end
1392
+ #
1393
+ # This generates the following comments routes:
1394
+ #
1395
+ # GET /photos/:photo_id/comments
1396
+ # GET /photos/:photo_id/comments/new
1397
+ # POST /photos/:photo_id/comments
1398
+ # GET /photos/:photo_id/comments/:id
1399
+ # GET /photos/:photo_id/comments/:id/edit
1400
+ # PATCH/PUT /photos/:photo_id/comments/:id
1401
+ # DELETE /photos/:photo_id/comments/:id
1402
+ #
1403
+ # ### Options
1404
+ # Takes same options as [match](rdoc-ref:Base#match) as well as:
1405
+ #
1406
+ # :path_names
1407
+ # : Allows you to change the segment component of the `edit` and `new`
1408
+ # actions. Actions not specified are not changed.
1409
+ #
1410
+ # resources :posts, path_names: { new: "brand_new" }
1411
+ #
1412
+ # The above example will now change /posts/new to /posts/brand_new.
1413
+ #
1414
+ # :path
1415
+ # : Allows you to change the path prefix for the resource.
1416
+ #
1417
+ # resources :posts, path: 'postings'
1418
+ #
1419
+ # The resource and all segments will now route to /postings instead of
1420
+ # /posts.
1421
+ #
1422
+ # :only
1423
+ # : Only generate routes for the given actions.
1424
+ #
1425
+ # resources :cows, only: :show
1426
+ # resources :cows, only: [:show, :index]
1427
+ #
1428
+ # :except
1429
+ # : Generate all routes except for the given actions.
1430
+ #
1431
+ # resources :cows, except: :show
1432
+ # resources :cows, except: [:show, :index]
1433
+ #
1434
+ # :shallow
1435
+ # : Generates shallow routes for nested resource(s). When placed on a parent
1436
+ # resource, generates shallow routes for all nested resources.
1437
+ #
1438
+ # resources :posts, shallow: true do
1439
+ # resources :comments
1440
+ # end
1441
+ #
1442
+ # Is the same as:
1443
+ #
1444
+ # resources :posts do
1445
+ # resources :comments, except: [:show, :edit, :update, :destroy]
1446
+ # end
1447
+ # resources :comments, only: [:show, :edit, :update, :destroy]
1448
+ #
1449
+ # This allows URLs for resources that otherwise would be deeply nested such
1450
+ # as a comment on a blog post like `/posts/a-long-permalink/comments/1234`
1451
+ # to be shortened to just `/comments/1234`.
1452
+ #
1453
+ # Set `shallow: false` on a child resource to ignore a parent's shallow
1454
+ # parameter.
1455
+ #
1456
+ # :shallow_path
1457
+ # : Prefixes nested shallow routes with the specified path.
1458
+ #
1459
+ # scope shallow_path: "sekret" do
1460
+ # resources :posts do
1461
+ # resources :comments, shallow: true
1462
+ # end
1463
+ # end
1464
+ #
1465
+ # The `comments` resource here will have the following routes generated for
1466
+ # it:
1467
+ #
1468
+ # post_comments GET /posts/:post_id/comments(.:format)
1469
+ # post_comments POST /posts/:post_id/comments(.:format)
1470
+ # new_post_comment GET /posts/:post_id/comments/new(.:format)
1471
+ # edit_comment GET /sekret/comments/:id/edit(.:format)
1472
+ # comment GET /sekret/comments/:id(.:format)
1473
+ # comment PATCH/PUT /sekret/comments/:id(.:format)
1474
+ # comment DELETE /sekret/comments/:id(.:format)
1475
+ #
1476
+ # :shallow_prefix
1477
+ # : Prefixes nested shallow route names with specified prefix.
1478
+ #
1479
+ # scope shallow_prefix: "sekret" do
1480
+ # resources :posts do
1481
+ # resources :comments, shallow: true
1482
+ # end
1483
+ # end
1484
+ #
1485
+ # The `comments` resource here will have the following routes generated for
1486
+ # it:
1487
+ #
1488
+ # post_comments GET /posts/:post_id/comments(.:format)
1489
+ # post_comments POST /posts/:post_id/comments(.:format)
1490
+ # new_post_comment GET /posts/:post_id/comments/new(.:format)
1491
+ # edit_sekret_comment GET /comments/:id/edit(.:format)
1492
+ # sekret_comment GET /comments/:id(.:format)
1493
+ # sekret_comment PATCH/PUT /comments/:id(.:format)
1494
+ # sekret_comment DELETE /comments/:id(.:format)
1495
+ #
1496
+ # :format
1497
+ # : Allows you to specify the default value for optional `format` segment or
1498
+ # disable it by supplying `false`.
1499
+ #
1500
+ # :param
1501
+ # : Allows you to override the default param name of `:id` in the URL.
1502
+ #
1503
+ #
1504
+ # ### Examples
1505
+ #
1506
+ # # routes call +Admin::PostsController+
1507
+ # resources :posts, module: "admin"
1508
+ #
1509
+ # # resource actions are at /admin/posts.
1510
+ # resources :posts, path: "admin/posts"
1511
+ def resources(*resources, &block)
1512
+ options = resources.extract_options!.dup
1513
+
1514
+ if apply_common_behavior_for(:resources, resources, options, &block)
1515
+ return self
1516
+ end
1517
+
1518
+ with_scope_level(:resources) do
1519
+ options = apply_action_options options
1520
+ resource_scope(Resource.new(resources.pop, api_only?, @scope[:shallow], options)) do
1521
+ yield if block_given?
1522
+
1523
+ concerns(options[:concerns]) if options[:concerns]
1524
+
1525
+ collection do
1526
+ get :index if parent_resource.actions.include?(:index)
1527
+ post :create if parent_resource.actions.include?(:create)
1528
+ end
1529
+
1530
+ new do
1531
+ get :new
1532
+ end if parent_resource.actions.include?(:new)
1533
+
1534
+ set_member_mappings_for_resource
1535
+ end
1536
+ end
1537
+
1538
+ self
1539
+ end
1540
+
1541
+ # To add a route to the collection:
1542
+ #
1543
+ # resources :photos do
1544
+ # collection do
1545
+ # get 'search'
1546
+ # end
1547
+ # end
1548
+ #
1549
+ # This will enable Rails to recognize paths such as `/photos/search` with GET,
1550
+ # and route to the search action of `PhotosController`. It will also create the
1551
+ # `search_photos_url` and `search_photos_path` route helpers.
1552
+ def collection(&block)
1553
+ unless resource_scope?
1554
+ raise ArgumentError, "can't use collection outside resource(s) scope"
1555
+ end
1556
+
1557
+ with_scope_level(:collection) do
1558
+ path_scope(parent_resource.collection_scope, &block)
1559
+ end
1560
+ end
1561
+
1562
+ # To add a member route, add a member block into the resource block:
1563
+ #
1564
+ # resources :photos do
1565
+ # member do
1566
+ # get 'preview'
1567
+ # end
1568
+ # end
1569
+ #
1570
+ # This will recognize `/photos/1/preview` with GET, and route to the preview
1571
+ # action of `PhotosController`. It will also create the `preview_photo_url` and
1572
+ # `preview_photo_path` helpers.
1573
+ def member(&block)
1574
+ unless resource_scope?
1575
+ raise ArgumentError, "can't use member outside resource(s) scope"
1576
+ end
1577
+
1578
+ with_scope_level(:member) do
1579
+ if shallow?
1580
+ shallow_scope {
1581
+ path_scope(parent_resource.member_scope, &block)
1582
+ }
1583
+ else
1584
+ path_scope(parent_resource.member_scope, &block)
1585
+ end
1586
+ end
1587
+ end
1588
+
1589
+ def new(&block)
1590
+ unless resource_scope?
1591
+ raise ArgumentError, "can't use new outside resource(s) scope"
1592
+ end
1593
+
1594
+ with_scope_level(:new) do
1595
+ path_scope(parent_resource.new_scope(action_path(:new)), &block)
1596
+ end
1597
+ end
1598
+
1599
+ def nested(&block)
1600
+ unless resource_scope?
1601
+ raise ArgumentError, "can't use nested outside resource(s) scope"
1602
+ end
1603
+
1604
+ with_scope_level(:nested) do
1605
+ if shallow? && shallow_nesting_depth >= 1
1606
+ shallow_scope do
1607
+ path_scope(parent_resource.nested_scope) do
1608
+ scope(nested_options, &block)
1609
+ end
1610
+ end
1611
+ else
1612
+ path_scope(parent_resource.nested_scope) do
1613
+ scope(nested_options, &block)
1614
+ end
1615
+ end
1616
+ end
1617
+ end
1618
+
1619
+ # See ActionDispatch::Routing::Mapper::Scoping#namespace.
1620
+ def namespace(path, options = {})
1621
+ if resource_scope?
1622
+ nested { super }
1623
+ else
1624
+ super
1625
+ end
1626
+ end
1627
+
1628
+ def shallow
1629
+ @scope = @scope.new(shallow: true)
1630
+ yield
1631
+ ensure
1632
+ @scope = @scope.parent
1633
+ end
1634
+
1635
+ def shallow?
1636
+ !parent_resource.singleton? && @scope[:shallow]
1637
+ end
1638
+
1639
+ # Loads another routes file with the given `name` located inside the
1640
+ # `config/routes` directory. In that file, you can use the normal routing DSL,
1641
+ # but *do not* surround it with a `Rails.application.routes.draw` block.
1642
+ #
1643
+ # # config/routes.rb
1644
+ # Rails.application.routes.draw do
1645
+ # draw :admin # Loads `config/routes/admin.rb`
1646
+ # draw "third_party/some_gem" # Loads `config/routes/third_party/some_gem.rb`
1647
+ # end
1648
+ #
1649
+ # # config/routes/admin.rb
1650
+ # namespace :admin do
1651
+ # resources :accounts
1652
+ # end
1653
+ #
1654
+ # # config/routes/third_party/some_gem.rb
1655
+ # mount SomeGem::Engine, at: "/some_gem"
1656
+ #
1657
+ # **CAUTION:** Use this feature with care. Having multiple routes files can
1658
+ # negatively impact discoverability and readability. For most applications —
1659
+ # even those with a few hundred routes — it's easier for developers to have a
1660
+ # single routes file.
1661
+ def draw(name)
1662
+ path = @draw_paths.find do |_path|
1663
+ File.exist? "#{_path}/#{name}.rb"
1664
+ end
1665
+
1666
+ unless path
1667
+ msg = "Your router tried to #draw the external file #{name}.rb,\n" \
1668
+ "but the file was not found in:\n\n"
1669
+ msg += @draw_paths.map { |_path| " * #{_path}" }.join("\n")
1670
+ raise ArgumentError, msg
1671
+ end
1672
+
1673
+ route_path = "#{path}/#{name}.rb"
1674
+ instance_eval(File.read(route_path), route_path.to_s)
1675
+ end
1676
+
1677
+ # Matches a URL pattern to one or more routes. For more information, see
1678
+ # [match](rdoc-ref:Base#match).
1679
+ #
1680
+ # match 'path', to: 'controller#action', via: :post
1681
+ # match 'path', 'otherpath', on: :member, via: :get
1682
+ def match(path, *rest, &block)
1683
+ if rest.empty? && Hash === path
1684
+ options = path
1685
+ path, to = options.find { |name, _value| name.is_a?(String) }
1686
+
1687
+ raise ArgumentError, "Route path not specified" if path.nil?
1688
+
1689
+ case to
1690
+ when Symbol
1691
+ options[:action] = to
1692
+ when String
1693
+ if to.include?("#")
1694
+ options[:to] = to
1695
+ else
1696
+ options[:controller] = to
1697
+ end
1698
+ else
1699
+ options[:to] = to
1700
+ end
1701
+
1702
+ options.delete(path)
1703
+ paths = [path]
1704
+ else
1705
+ options = rest.pop || {}
1706
+ paths = [path] + rest
1707
+ end
1708
+
1709
+ if options.key?(:defaults)
1710
+ defaults(options.delete(:defaults)) { map_match(paths, options, &block) }
1711
+ else
1712
+ map_match(paths, options, &block)
1713
+ end
1714
+ end
1715
+
1716
+ # You can specify what Rails should route "/" to with the root method:
1717
+ #
1718
+ # root to: 'pages#main'
1719
+ #
1720
+ # For options, see `match`, as `root` uses it internally.
1721
+ #
1722
+ # You can also pass a string which will expand
1723
+ #
1724
+ # root 'pages#main'
1725
+ #
1726
+ # You should put the root route at the top of `config/routes.rb`, because this
1727
+ # means it will be matched first. As this is the most popular route of most
1728
+ # Rails applications, this is beneficial.
1729
+ def root(path, options = {})
1730
+ if path.is_a?(String)
1731
+ options[:to] = path
1732
+ elsif path.is_a?(Hash) && options.empty?
1733
+ options = path
1734
+ else
1735
+ raise ArgumentError, "must be called with a path and/or options"
1736
+ end
1737
+
1738
+ if @scope.resources?
1739
+ with_scope_level(:root) do
1740
+ path_scope(parent_resource.path) do
1741
+ match_root_route(options)
1742
+ end
1743
+ end
1744
+ else
1745
+ match_root_route(options)
1746
+ end
1747
+ end
1748
+
1749
+ private
1750
+ def parent_resource
1751
+ @scope[:scope_level_resource]
1752
+ end
1753
+
1754
+ def apply_common_behavior_for(method, resources, options, &block)
1755
+ if resources.length > 1
1756
+ resources.each { |r| public_send(method, r, options, &block) }
1757
+ return true
1758
+ end
1759
+
1760
+ if options[:shallow]
1761
+ options.delete(:shallow)
1762
+ shallow do
1763
+ public_send(method, resources.pop, options, &block)
1764
+ end
1765
+ return true
1766
+ end
1767
+
1768
+ if resource_scope?
1769
+ nested { public_send(method, resources.pop, options, &block) }
1770
+ return true
1771
+ end
1772
+
1773
+ options.keys.each do |k|
1774
+ (options[:constraints] ||= {})[k] = options.delete(k) if options[k].is_a?(Regexp)
1775
+ end
1776
+
1777
+ scope_options = options.slice!(*RESOURCE_OPTIONS)
1778
+ unless scope_options.empty?
1779
+ scope(scope_options) do
1780
+ public_send(method, resources.pop, options, &block)
1781
+ end
1782
+ return true
1783
+ end
1784
+
1785
+ false
1786
+ end
1787
+
1788
+ def apply_action_options(options)
1789
+ return options if action_options? options
1790
+ options.merge scope_action_options
1791
+ end
1792
+
1793
+ def action_options?(options)
1794
+ options[:only] || options[:except]
1795
+ end
1796
+
1797
+ def scope_action_options
1798
+ @scope[:action_options] || {}
1799
+ end
1800
+
1801
+ def resource_scope?
1802
+ @scope.resource_scope?
1803
+ end
1804
+
1805
+ def resource_method_scope?
1806
+ @scope.resource_method_scope?
1807
+ end
1808
+
1809
+ def nested_scope?
1810
+ @scope.nested?
1811
+ end
1812
+
1813
+ def with_scope_level(kind) # :doc:
1814
+ @scope = @scope.new_level(kind)
1815
+ yield
1816
+ ensure
1817
+ @scope = @scope.parent
1818
+ end
1819
+
1820
+ def resource_scope(resource, &block)
1821
+ @scope = @scope.new(scope_level_resource: resource)
1822
+
1823
+ controller(resource.resource_scope, &block)
1824
+ ensure
1825
+ @scope = @scope.parent
1826
+ end
1827
+
1828
+ def nested_options
1829
+ options = { as: parent_resource.member_name }
1830
+ options[:constraints] = {
1831
+ parent_resource.nested_param => param_constraint
1832
+ } if param_constraint?
1833
+
1834
+ options
1835
+ end
1836
+
1837
+ def shallow_nesting_depth
1838
+ @scope.find_all { |node|
1839
+ node.frame[:scope_level_resource]
1840
+ }.count { |node| node.frame[:scope_level_resource].shallow? }
1841
+ end
1842
+
1843
+ def param_constraint?
1844
+ @scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp)
1845
+ end
1846
+
1847
+ def param_constraint
1848
+ @scope[:constraints][parent_resource.param]
1849
+ end
1850
+
1851
+ def canonical_action?(action)
1852
+ resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
1853
+ end
1854
+
1855
+ def shallow_scope
1856
+ scope = { as: @scope[:shallow_prefix],
1857
+ path: @scope[:shallow_path] }
1858
+ @scope = @scope.new scope
1859
+
1860
+ yield
1861
+ ensure
1862
+ @scope = @scope.parent
1863
+ end
1864
+
1865
+ def path_for_action(action, path)
1866
+ return "#{@scope[:path]}/#{path}" if path
1867
+
1868
+ if canonical_action?(action)
1869
+ @scope[:path].to_s
1870
+ else
1871
+ "#{@scope[:path]}/#{action_path(action)}"
1872
+ end
1873
+ end
1874
+
1875
+ def action_path(name)
1876
+ @scope[:path_names][name.to_sym] || name
1877
+ end
1878
+
1879
+ def prefix_name_for_action(as, action)
1880
+ if as
1881
+ prefix = as
1882
+ elsif !canonical_action?(action)
1883
+ prefix = action
1884
+ end
1885
+
1886
+ if prefix && prefix != "/" && !prefix.empty?
1887
+ Mapper.normalize_name prefix.to_s.tr("-", "_")
1888
+ end
1889
+ end
1890
+
1891
+ def name_for_action(as, action)
1892
+ prefix = prefix_name_for_action(as, action)
1893
+ name_prefix = @scope[:as]
1894
+
1895
+ if parent_resource
1896
+ return nil unless as || action
1897
+
1898
+ collection_name = parent_resource.collection_name
1899
+ member_name = parent_resource.member_name
1900
+ end
1901
+
1902
+ action_name = @scope.action_name(name_prefix, prefix, collection_name, member_name)
1903
+ candidate = action_name.select(&:present?).join("_")
1904
+
1905
+ unless candidate.empty?
1906
+ # If a name was not explicitly given, we check if it is valid and return nil in
1907
+ # case it isn't. Otherwise, we pass the invalid name forward so the underlying
1908
+ # router engine treats it and raises an exception.
1909
+ if as.nil?
1910
+ candidate unless !candidate.match?(/\A[_a-z]/i) || has_named_route?(candidate)
1911
+ else
1912
+ candidate
1913
+ end
1914
+ end
1915
+ end
1916
+
1917
+ def set_member_mappings_for_resource # :doc:
1918
+ member do
1919
+ get :edit if parent_resource.actions.include?(:edit)
1920
+ get :show if parent_resource.actions.include?(:show)
1921
+ if parent_resource.actions.include?(:update)
1922
+ patch :update
1923
+ put :update
1924
+ end
1925
+ delete :destroy if parent_resource.actions.include?(:destroy)
1926
+ end
1927
+ end
1928
+
1929
+ def api_only? # :doc:
1930
+ @set.api_only?
1931
+ end
1932
+
1933
+ def path_scope(path)
1934
+ @scope = @scope.new(path: merge_path_scope(@scope[:path], path))
1935
+ yield
1936
+ ensure
1937
+ @scope = @scope.parent
1938
+ end
1939
+
1940
+ def map_match(paths, options)
1941
+ ActionDispatch.deprecator.warn(<<-MSG.squish) if paths.count > 1
1942
+ Mapping a route with multiple paths is deprecated and
1943
+ will be removed in Rails 8.1. Please use multiple method calls instead.
1944
+ MSG
1945
+
1946
+ if (on = options[:on]) && !VALID_ON_OPTIONS.include?(on)
1947
+ raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
1948
+ end
1949
+
1950
+ if @scope[:to]
1951
+ options[:to] ||= @scope[:to]
1952
+ end
1953
+
1954
+ if @scope[:controller] && @scope[:action]
1955
+ options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
1956
+ end
1957
+
1958
+ controller = options.delete(:controller) || @scope[:controller]
1959
+ option_path = options.delete :path
1960
+ to = options.delete :to
1961
+ via = Mapping.check_via Array(options.delete(:via) {
1962
+ @scope[:via]
1963
+ })
1964
+ formatted = options.delete(:format) { @scope[:format] }
1965
+ anchor = options.delete(:anchor) { true }
1966
+ options_constraints = options.delete(:constraints) || {}
1967
+
1968
+ path_types = paths.group_by(&:class)
1969
+ (path_types[String] || []).each do |_path|
1970
+ route_options = options.dup
1971
+ if _path && option_path
1972
+ raise ArgumentError, "Ambiguous route definition. Both :path and the route path were specified as strings."
1973
+ end
1974
+ to = get_to_from_path(_path, to, route_options[:action])
1975
+ decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints)
1976
+ end
1977
+
1978
+ (path_types[Symbol] || []).each do |action|
1979
+ route_options = options.dup
1980
+ decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints)
1981
+ end
1982
+
1983
+ self
1984
+ end
1985
+
1986
+ def get_to_from_path(path, to, action)
1987
+ return to if to || action
1988
+
1989
+ path_without_format = path.sub(/\(\.:format\)$/, "")
1990
+ if using_match_shorthand?(path_without_format)
1991
+ path_without_format.delete_prefix("/").sub(%r{/([^/]*)$}, '#\1').tr("-", "_")
1992
+ else
1993
+ nil
1994
+ end
1995
+ end
1996
+
1997
+ def using_match_shorthand?(path)
1998
+ %r{^/?[-\w]+/[-\w/]+$}.match?(path)
1999
+ end
2000
+
2001
+ def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
2002
+ if on = options.delete(:on)
2003
+ send(on) { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
2004
+ else
2005
+ case @scope.scope_level
2006
+ when :resources
2007
+ nested { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
2008
+ when :resource
2009
+ member { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
2010
+ else
2011
+ add_route(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
2012
+ end
2013
+ end
2014
+ end
2015
+
2016
+ def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints)
2017
+ path = path_for_action(action, _path)
2018
+ raise ArgumentError, "path is required" if path.blank?
2019
+
2020
+ action = action.to_s
2021
+
2022
+ default_action = options.delete(:action) || @scope[:action]
2023
+
2024
+ if /^[\w\-\/]+$/.match?(action)
2025
+ default_action ||= action.tr("-", "_") unless action.include?("/")
2026
+ else
2027
+ action = nil
2028
+ end
2029
+
2030
+ as = if !options.fetch(:as, true) # if it's set to nil or false
2031
+ options.delete(:as)
2032
+ else
2033
+ name_for_action(options.delete(:as), action)
2034
+ end
2035
+
2036
+ path = Mapping.normalize_path URI::RFC2396_PARSER.escape(path), formatted
2037
+ ast = Journey::Parser.parse path
2038
+
2039
+ mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
2040
+ @set.add_route(mapping, as)
2041
+ end
2042
+
2043
+ def match_root_route(options)
2044
+ args = ["/", { as: :root, via: :get }.merge(options)]
2045
+ match(*args)
2046
+ end
2047
+ end
2048
+
2049
+ # Routing Concerns allow you to declare common routes that can be reused inside
2050
+ # others resources and routes.
2051
+ #
2052
+ # concern :commentable do
2053
+ # resources :comments
2054
+ # end
2055
+ #
2056
+ # concern :image_attachable do
2057
+ # resources :images, only: :index
2058
+ # end
2059
+ #
2060
+ # These concerns are used in Resources routing:
2061
+ #
2062
+ # resources :messages, concerns: [:commentable, :image_attachable]
2063
+ #
2064
+ # or in a scope or namespace:
2065
+ #
2066
+ # namespace :posts do
2067
+ # concerns :commentable
2068
+ # end
2069
+ module Concerns
2070
+ # Define a routing concern using a name.
2071
+ #
2072
+ # Concerns may be defined inline, using a block, or handled by another object,
2073
+ # by passing that object as the second parameter.
2074
+ #
2075
+ # The concern object, if supplied, should respond to `call`, which will receive
2076
+ # two parameters:
2077
+ #
2078
+ # * The current mapper
2079
+ # * A hash of options which the concern object may use
2080
+ #
2081
+ # Options may also be used by concerns defined in a block by accepting a block
2082
+ # parameter. So, using a block, you might do something as simple as limit the
2083
+ # actions available on certain resources, passing standard resource options
2084
+ # through the concern:
2085
+ #
2086
+ # concern :commentable do |options|
2087
+ # resources :comments, options
2088
+ # end
2089
+ #
2090
+ # resources :posts, concerns: :commentable
2091
+ # resources :archived_posts do
2092
+ # # Don't allow comments on archived posts
2093
+ # concerns :commentable, only: [:index, :show]
2094
+ # end
2095
+ #
2096
+ # Or, using a callable object, you might implement something more specific to
2097
+ # your application, which would be out of place in your routes file.
2098
+ #
2099
+ # # purchasable.rb
2100
+ # class Purchasable
2101
+ # def initialize(defaults = {})
2102
+ # @defaults = defaults
2103
+ # end
2104
+ #
2105
+ # def call(mapper, options = {})
2106
+ # options = @defaults.merge(options)
2107
+ # mapper.resources :purchases
2108
+ # mapper.resources :receipts
2109
+ # mapper.resources :returns if options[:returnable]
2110
+ # end
2111
+ # end
2112
+ #
2113
+ # # routes.rb
2114
+ # concern :purchasable, Purchasable.new(returnable: true)
2115
+ #
2116
+ # resources :toys, concerns: :purchasable
2117
+ # resources :electronics, concerns: :purchasable
2118
+ # resources :pets do
2119
+ # concerns :purchasable, returnable: false
2120
+ # end
2121
+ #
2122
+ # Any routing helpers can be used inside a concern. If using a callable, they're
2123
+ # accessible from the Mapper that's passed to `call`.
2124
+ def concern(name, callable = nil, &block)
2125
+ callable ||= lambda { |mapper, options| mapper.instance_exec(options, &block) }
2126
+ @concerns[name] = callable
2127
+ end
2128
+
2129
+ # Use the named concerns
2130
+ #
2131
+ # resources :posts do
2132
+ # concerns :commentable
2133
+ # end
2134
+ #
2135
+ # Concerns also work in any routes helper that you want to use:
2136
+ #
2137
+ # namespace :posts do
2138
+ # concerns :commentable
2139
+ # end
2140
+ def concerns(*args)
2141
+ options = args.extract_options!
2142
+ args.flatten.each do |name|
2143
+ if concern = @concerns[name]
2144
+ concern.call(self, options)
2145
+ else
2146
+ raise ArgumentError, "No concern named #{name} was found!"
2147
+ end
2148
+ end
2149
+ end
2150
+ end
2151
+
2152
+ module CustomUrls
2153
+ # Define custom URL helpers that will be added to the application's routes. This
2154
+ # allows you to override and/or replace the default behavior of routing helpers,
2155
+ # e.g:
2156
+ #
2157
+ # direct :homepage do
2158
+ # "https://rubyonrails.org"
2159
+ # end
2160
+ #
2161
+ # direct :commentable do |model|
2162
+ # [ model, anchor: model.dom_id ]
2163
+ # end
2164
+ #
2165
+ # direct :main do
2166
+ # { controller: "pages", action: "index", subdomain: "www" }
2167
+ # end
2168
+ #
2169
+ # The return value from the block passed to `direct` must be a valid set of
2170
+ # arguments for `url_for` which will actually build the URL string. This can be
2171
+ # one of the following:
2172
+ #
2173
+ # * A string, which is treated as a generated URL
2174
+ # * A hash, e.g. `{ controller: "pages", action: "index" }`
2175
+ # * An array, which is passed to `polymorphic_url`
2176
+ # * An Active Model instance
2177
+ # * An Active Model class
2178
+ #
2179
+ #
2180
+ # NOTE: Other URL helpers can be called in the block but be careful not to
2181
+ # invoke your custom URL helper again otherwise it will result in a stack
2182
+ # overflow error.
2183
+ #
2184
+ # You can also specify default options that will be passed through to your URL
2185
+ # helper definition, e.g:
2186
+ #
2187
+ # direct :browse, page: 1, size: 10 do |options|
2188
+ # [ :products, options.merge(params.permit(:page, :size).to_h.symbolize_keys) ]
2189
+ # end
2190
+ #
2191
+ # In this instance the `params` object comes from the context in which the block
2192
+ # is executed, e.g. generating a URL inside a controller action or a view. If
2193
+ # the block is executed where there isn't a `params` object such as this:
2194
+ #
2195
+ # Rails.application.routes.url_helpers.browse_path
2196
+ #
2197
+ # then it will raise a `NameError`. Because of this you need to be aware of the
2198
+ # context in which you will use your custom URL helper when defining it.
2199
+ #
2200
+ # NOTE: The `direct` method can't be used inside of a scope block such as
2201
+ # `namespace` or `scope` and will raise an error if it detects that it is.
2202
+ def direct(name, options = {}, &block)
2203
+ unless @scope.root?
2204
+ raise RuntimeError, "The direct method can't be used inside a routes scope block"
2205
+ end
2206
+
2207
+ @set.add_url_helper(name, options, &block)
2208
+ end
2209
+
2210
+ # Define custom polymorphic mappings of models to URLs. This alters the behavior
2211
+ # of `polymorphic_url` and consequently the behavior of `link_to` and `form_for`
2212
+ # when passed a model instance, e.g:
2213
+ #
2214
+ # resource :basket
2215
+ #
2216
+ # resolve "Basket" do
2217
+ # [:basket]
2218
+ # end
2219
+ #
2220
+ # This will now generate "/basket" when a `Basket` instance is passed to
2221
+ # `link_to` or `form_for` instead of the standard "/baskets/:id".
2222
+ #
2223
+ # NOTE: This custom behavior only applies to simple polymorphic URLs where a
2224
+ # single model instance is passed and not more complicated forms, e.g:
2225
+ #
2226
+ # # config/routes.rb
2227
+ # resource :profile
2228
+ # namespace :admin do
2229
+ # resources :users
2230
+ # end
2231
+ #
2232
+ # resolve("User") { [:profile] }
2233
+ #
2234
+ # # app/views/application/_menu.html.erb
2235
+ # link_to "Profile", @current_user
2236
+ # link_to "Profile", [:admin, @current_user]
2237
+ #
2238
+ # The first `link_to` will generate "/profile" but the second will generate the
2239
+ # standard polymorphic URL of "/admin/users/1".
2240
+ #
2241
+ # You can pass options to a polymorphic mapping - the arity for the block needs
2242
+ # to be two as the instance is passed as the first argument, e.g:
2243
+ #
2244
+ # resolve "Basket", anchor: "items" do |basket, options|
2245
+ # [:basket, options]
2246
+ # end
2247
+ #
2248
+ # This generates the URL "/basket#items" because when the last item in an array
2249
+ # passed to `polymorphic_url` is a hash then it's treated as options to the URL
2250
+ # helper that gets called.
2251
+ #
2252
+ # NOTE: The `resolve` method can't be used inside of a scope block such as
2253
+ # `namespace` or `scope` and will raise an error if it detects that it is.
2254
+ def resolve(*args, &block)
2255
+ unless @scope.root?
2256
+ raise RuntimeError, "The resolve method can't be used inside a routes scope block"
2257
+ end
2258
+
2259
+ options = args.extract_options!
2260
+ args = args.flatten(1)
2261
+
2262
+ args.each do |klass|
2263
+ @set.add_polymorphic_mapping(klass, options, &block)
2264
+ end
2265
+ end
2266
+ end
2267
+
2268
+ class Scope # :nodoc:
2269
+ OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
2270
+ :controller, :action, :path_names, :constraints,
2271
+ :shallow, :blocks, :defaults, :via, :format, :options, :to]
2272
+
2273
+ RESOURCE_SCOPES = [:resource, :resources]
2274
+ RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
2275
+
2276
+ attr_reader :parent, :scope_level
2277
+
2278
+ def initialize(hash, parent = ROOT, scope_level = nil)
2279
+ @parent = parent
2280
+ @hash = parent ? parent.frame.merge(hash) : hash
2281
+ @scope_level = scope_level
2282
+ end
2283
+
2284
+ def nested?
2285
+ scope_level == :nested
2286
+ end
2287
+
2288
+ def null?
2289
+ @hash.nil? && @parent.nil?
2290
+ end
2291
+
2292
+ def root?
2293
+ @parent == ROOT
2294
+ end
2295
+
2296
+ def resources?
2297
+ scope_level == :resources
2298
+ end
2299
+
2300
+ def resource_method_scope?
2301
+ RESOURCE_METHOD_SCOPES.include? scope_level
2302
+ end
2303
+
2304
+ def action_name(name_prefix, prefix, collection_name, member_name)
2305
+ case scope_level
2306
+ when :nested
2307
+ [name_prefix, prefix]
2308
+ when :collection
2309
+ [prefix, name_prefix, collection_name]
2310
+ when :new
2311
+ [prefix, :new, name_prefix, member_name]
2312
+ when :member
2313
+ [prefix, name_prefix, member_name]
2314
+ when :root
2315
+ [name_prefix, collection_name, prefix]
2316
+ else
2317
+ [name_prefix, member_name, prefix]
2318
+ end
2319
+ end
2320
+
2321
+ def resource_scope?
2322
+ RESOURCE_SCOPES.include? scope_level
2323
+ end
2324
+
2325
+ def options
2326
+ OPTIONS
2327
+ end
2328
+
2329
+ def new(hash)
2330
+ self.class.new hash, self, scope_level
2331
+ end
2332
+
2333
+ def new_level(level)
2334
+ self.class.new(frame, self, level)
2335
+ end
2336
+
2337
+ def [](key)
2338
+ frame[key]
2339
+ end
2340
+
2341
+ def frame; @hash; end
2342
+
2343
+ include Enumerable
2344
+
2345
+ def each
2346
+ node = self
2347
+ until node.equal? ROOT
2348
+ yield node
2349
+ node = node.parent
2350
+ end
2351
+ end
2352
+
2353
+ ROOT = Scope.new({}, nil)
2354
+ end
2355
+
2356
+ def initialize(set) # :nodoc:
2357
+ @set = set
2358
+ @draw_paths = set.draw_paths
2359
+ @scope = Scope.new(path_names: @set.resources_path_names)
2360
+ @concerns = {}
2361
+ end
2362
+
2363
+ include Base
2364
+ include HttpHelpers
2365
+ include Redirection
2366
+ include Scoping
2367
+ include Concerns
2368
+ include Resources
2369
+ include CustomUrls
2370
+ end
2371
+ end
2372
+ end