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,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