actionpack 6.0.0

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

Potentially problematic release.


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

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