actionpack 5.2.3

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