actionpack 3.2.19 → 4.2.11.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 (244) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +412 -503
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +11 -294
  5. data/lib/abstract_controller/asset_paths.rb +2 -2
  6. data/lib/abstract_controller/base.rb +52 -18
  7. data/lib/abstract_controller/callbacks.rb +87 -89
  8. data/lib/abstract_controller/collector.rb +17 -3
  9. data/lib/abstract_controller/helpers.rb +41 -14
  10. data/lib/abstract_controller/logger.rb +1 -2
  11. data/lib/abstract_controller/railties/routes_helpers.rb +3 -3
  12. data/lib/abstract_controller/rendering.rb +65 -118
  13. data/lib/abstract_controller/translation.rb +16 -1
  14. data/lib/abstract_controller/url_for.rb +7 -7
  15. data/lib/abstract_controller.rb +2 -10
  16. data/lib/action_controller/base.rb +61 -28
  17. data/lib/action_controller/caching/fragments.rb +30 -54
  18. data/lib/action_controller/caching.rb +38 -35
  19. data/lib/action_controller/log_subscriber.rb +35 -18
  20. data/lib/action_controller/metal/conditional_get.rb +103 -34
  21. data/lib/action_controller/metal/data_streaming.rb +20 -26
  22. data/lib/action_controller/metal/etag_with_template_digest.rb +50 -0
  23. data/lib/action_controller/metal/exceptions.rb +19 -6
  24. data/lib/action_controller/metal/flash.rb +41 -9
  25. data/lib/action_controller/metal/force_ssl.rb +70 -12
  26. data/lib/action_controller/metal/head.rb +30 -7
  27. data/lib/action_controller/metal/helpers.rb +11 -11
  28. data/lib/action_controller/metal/hide_actions.rb +0 -1
  29. data/lib/action_controller/metal/http_authentication.rb +140 -94
  30. data/lib/action_controller/metal/implicit_render.rb +1 -1
  31. data/lib/action_controller/metal/instrumentation.rb +11 -7
  32. data/lib/action_controller/metal/live.rb +328 -0
  33. data/lib/action_controller/metal/mime_responds.rb +161 -152
  34. data/lib/action_controller/metal/params_wrapper.rb +126 -81
  35. data/lib/action_controller/metal/rack_delegation.rb +10 -4
  36. data/lib/action_controller/metal/redirecting.rb +44 -41
  37. data/lib/action_controller/metal/renderers.rb +48 -19
  38. data/lib/action_controller/metal/rendering.rb +46 -11
  39. data/lib/action_controller/metal/request_forgery_protection.rb +250 -29
  40. data/lib/action_controller/metal/streaming.rb +30 -38
  41. data/lib/action_controller/metal/strong_parameters.rb +669 -0
  42. data/lib/action_controller/metal/testing.rb +12 -18
  43. data/lib/action_controller/metal/url_for.rb +31 -29
  44. data/lib/action_controller/metal.rb +31 -40
  45. data/lib/action_controller/model_naming.rb +12 -0
  46. data/lib/action_controller/railtie.rb +38 -18
  47. data/lib/action_controller/railties/helpers.rb +22 -0
  48. data/lib/action_controller/test_case.rb +359 -173
  49. data/lib/action_controller.rb +9 -16
  50. data/lib/action_dispatch/http/cache.rb +64 -11
  51. data/lib/action_dispatch/http/filter_parameters.rb +20 -10
  52. data/lib/action_dispatch/http/filter_redirect.rb +38 -0
  53. data/lib/action_dispatch/http/headers.rb +85 -17
  54. data/lib/action_dispatch/http/mime_negotiation.rb +55 -5
  55. data/lib/action_dispatch/http/mime_type.rb +167 -114
  56. data/lib/action_dispatch/http/mime_types.rb +2 -1
  57. data/lib/action_dispatch/http/parameter_filter.rb +44 -46
  58. data/lib/action_dispatch/http/parameters.rb +30 -46
  59. data/lib/action_dispatch/http/rack_cache.rb +2 -3
  60. data/lib/action_dispatch/http/request.rb +108 -45
  61. data/lib/action_dispatch/http/response.rb +247 -48
  62. data/lib/action_dispatch/http/upload.rb +60 -29
  63. data/lib/action_dispatch/http/url.rb +135 -45
  64. data/lib/action_dispatch/journey/backwards.rb +5 -0
  65. data/lib/action_dispatch/journey/formatter.rb +166 -0
  66. data/lib/action_dispatch/journey/gtg/builder.rb +162 -0
  67. data/lib/action_dispatch/journey/gtg/simulator.rb +47 -0
  68. data/lib/action_dispatch/journey/gtg/transition_table.rb +157 -0
  69. data/lib/action_dispatch/journey/nfa/builder.rb +76 -0
  70. data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
  71. data/lib/action_dispatch/journey/nfa/simulator.rb +47 -0
  72. data/lib/action_dispatch/journey/nfa/transition_table.rb +163 -0
  73. data/lib/action_dispatch/journey/nodes/node.rb +128 -0
  74. data/lib/action_dispatch/journey/parser.rb +198 -0
  75. data/lib/action_dispatch/journey/parser.y +49 -0
  76. data/lib/action_dispatch/journey/parser_extras.rb +23 -0
  77. data/lib/action_dispatch/journey/path/pattern.rb +193 -0
  78. data/lib/action_dispatch/journey/route.rb +125 -0
  79. data/lib/action_dispatch/journey/router/strexp.rb +27 -0
  80. data/lib/action_dispatch/journey/router/utils.rb +93 -0
  81. data/lib/action_dispatch/journey/router.rb +144 -0
  82. data/lib/action_dispatch/journey/routes.rb +80 -0
  83. data/lib/action_dispatch/journey/scanner.rb +61 -0
  84. data/lib/action_dispatch/journey/visitors.rb +221 -0
  85. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  86. data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
  87. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  88. data/lib/action_dispatch/journey.rb +5 -0
  89. data/lib/action_dispatch/middleware/callbacks.rb +16 -11
  90. data/lib/action_dispatch/middleware/cookies.rb +346 -125
  91. data/lib/action_dispatch/middleware/debug_exceptions.rb +52 -24
  92. data/lib/action_dispatch/middleware/exception_wrapper.rb +75 -9
  93. data/lib/action_dispatch/middleware/flash.rb +85 -72
  94. data/lib/action_dispatch/middleware/params_parser.rb +16 -31
  95. data/lib/action_dispatch/middleware/public_exceptions.rb +39 -14
  96. data/lib/action_dispatch/middleware/reloader.rb +16 -7
  97. data/lib/action_dispatch/middleware/remote_ip.rb +132 -40
  98. data/lib/action_dispatch/middleware/request_id.rb +3 -7
  99. data/lib/action_dispatch/middleware/session/abstract_store.rb +22 -20
  100. data/lib/action_dispatch/middleware/session/cache_store.rb +3 -3
  101. data/lib/action_dispatch/middleware/session/cookie_store.rb +84 -29
  102. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -3
  103. data/lib/action_dispatch/middleware/show_exceptions.rb +15 -44
  104. data/lib/action_dispatch/middleware/ssl.rb +72 -0
  105. data/lib/action_dispatch/middleware/stack.rb +6 -1
  106. data/lib/action_dispatch/middleware/static.rb +80 -23
  107. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +34 -0
  108. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  109. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +27 -0
  110. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +52 -0
  111. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  112. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +16 -0
  113. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  114. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +133 -5
  115. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  116. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  117. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  122. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  123. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
  124. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +200 -0
  125. data/lib/action_dispatch/railtie.rb +19 -6
  126. data/lib/action_dispatch/request/session.rb +193 -0
  127. data/lib/action_dispatch/request/utils.rb +35 -0
  128. data/lib/action_dispatch/routing/endpoint.rb +10 -0
  129. data/lib/action_dispatch/routing/inspector.rb +234 -0
  130. data/lib/action_dispatch/routing/mapper.rb +897 -436
  131. data/lib/action_dispatch/routing/polymorphic_routes.rb +213 -92
  132. data/lib/action_dispatch/routing/redirection.rb +97 -37
  133. data/lib/action_dispatch/routing/route_set.rb +432 -239
  134. data/lib/action_dispatch/routing/routes_proxy.rb +7 -4
  135. data/lib/action_dispatch/routing/url_for.rb +63 -34
  136. data/lib/action_dispatch/routing.rb +57 -89
  137. data/lib/action_dispatch/testing/assertions/dom.rb +2 -36
  138. data/lib/action_dispatch/testing/assertions/response.rb +24 -38
  139. data/lib/action_dispatch/testing/assertions/routing.rb +55 -54
  140. data/lib/action_dispatch/testing/assertions/selector.rb +2 -434
  141. data/lib/action_dispatch/testing/assertions/tag.rb +2 -137
  142. data/lib/action_dispatch/testing/assertions.rb +11 -7
  143. data/lib/action_dispatch/testing/integration.rb +88 -72
  144. data/lib/action_dispatch/testing/test_process.rb +9 -6
  145. data/lib/action_dispatch/testing/test_request.rb +13 -9
  146. data/lib/action_dispatch/testing/test_response.rb +1 -5
  147. data/lib/action_dispatch.rb +24 -21
  148. data/lib/action_pack/gem_version.rb +15 -0
  149. data/lib/action_pack/version.rb +5 -7
  150. data/lib/action_pack.rb +1 -1
  151. metadata +181 -292
  152. data/lib/abstract_controller/layouts.rb +0 -423
  153. data/lib/abstract_controller/view_paths.rb +0 -96
  154. data/lib/action_controller/caching/actions.rb +0 -185
  155. data/lib/action_controller/caching/pages.rb +0 -187
  156. data/lib/action_controller/caching/sweeping.rb +0 -97
  157. data/lib/action_controller/deprecated/integration_test.rb +0 -2
  158. data/lib/action_controller/deprecated/performance_test.rb +0 -1
  159. data/lib/action_controller/deprecated.rb +0 -3
  160. data/lib/action_controller/metal/compatibility.rb +0 -65
  161. data/lib/action_controller/metal/responder.rb +0 -286
  162. data/lib/action_controller/metal/session_management.rb +0 -14
  163. data/lib/action_controller/railties/paths.rb +0 -25
  164. data/lib/action_controller/record_identifier.rb +0 -85
  165. data/lib/action_controller/vendor/html-scanner/html/document.rb +0 -68
  166. data/lib/action_controller/vendor/html-scanner/html/node.rb +0 -532
  167. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +0 -177
  168. data/lib/action_controller/vendor/html-scanner/html/selector.rb +0 -830
  169. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +0 -107
  170. data/lib/action_controller/vendor/html-scanner/html/version.rb +0 -11
  171. data/lib/action_controller/vendor/html-scanner.rb +0 -20
  172. data/lib/action_dispatch/middleware/best_standards_support.rb +0 -30
  173. data/lib/action_dispatch/middleware/body_proxy.rb +0 -30
  174. data/lib/action_dispatch/middleware/head.rb +0 -18
  175. data/lib/action_dispatch/middleware/rescue.rb +0 -26
  176. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +0 -31
  177. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +0 -26
  178. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +0 -10
  179. data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +0 -2
  180. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +0 -15
  181. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +0 -17
  182. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +0 -2
  183. data/lib/action_dispatch/testing/performance_test.rb +0 -10
  184. data/lib/action_view/asset_paths.rb +0 -142
  185. data/lib/action_view/base.rb +0 -220
  186. data/lib/action_view/buffers.rb +0 -43
  187. data/lib/action_view/context.rb +0 -36
  188. data/lib/action_view/flows.rb +0 -79
  189. data/lib/action_view/helpers/active_model_helper.rb +0 -50
  190. data/lib/action_view/helpers/asset_paths.rb +0 -7
  191. data/lib/action_view/helpers/asset_tag_helper.rb +0 -457
  192. data/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +0 -146
  193. data/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +0 -93
  194. data/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +0 -193
  195. data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +0 -148
  196. data/lib/action_view/helpers/atom_feed_helper.rb +0 -200
  197. data/lib/action_view/helpers/cache_helper.rb +0 -64
  198. data/lib/action_view/helpers/capture_helper.rb +0 -203
  199. data/lib/action_view/helpers/controller_helper.rb +0 -25
  200. data/lib/action_view/helpers/csrf_helper.rb +0 -32
  201. data/lib/action_view/helpers/date_helper.rb +0 -1062
  202. data/lib/action_view/helpers/debug_helper.rb +0 -40
  203. data/lib/action_view/helpers/form_helper.rb +0 -1486
  204. data/lib/action_view/helpers/form_options_helper.rb +0 -658
  205. data/lib/action_view/helpers/form_tag_helper.rb +0 -685
  206. data/lib/action_view/helpers/javascript_helper.rb +0 -110
  207. data/lib/action_view/helpers/number_helper.rb +0 -622
  208. data/lib/action_view/helpers/output_safety_helper.rb +0 -38
  209. data/lib/action_view/helpers/record_tag_helper.rb +0 -111
  210. data/lib/action_view/helpers/rendering_helper.rb +0 -90
  211. data/lib/action_view/helpers/sanitize_helper.rb +0 -259
  212. data/lib/action_view/helpers/tag_helper.rb +0 -160
  213. data/lib/action_view/helpers/text_helper.rb +0 -426
  214. data/lib/action_view/helpers/translation_helper.rb +0 -91
  215. data/lib/action_view/helpers/url_helper.rb +0 -693
  216. data/lib/action_view/helpers.rb +0 -60
  217. data/lib/action_view/locale/en.yml +0 -160
  218. data/lib/action_view/log_subscriber.rb +0 -28
  219. data/lib/action_view/lookup_context.rb +0 -254
  220. data/lib/action_view/path_set.rb +0 -89
  221. data/lib/action_view/railtie.rb +0 -55
  222. data/lib/action_view/renderer/abstract_renderer.rb +0 -41
  223. data/lib/action_view/renderer/partial_renderer.rb +0 -415
  224. data/lib/action_view/renderer/renderer.rb +0 -54
  225. data/lib/action_view/renderer/streaming_template_renderer.rb +0 -106
  226. data/lib/action_view/renderer/template_renderer.rb +0 -94
  227. data/lib/action_view/template/error.rb +0 -128
  228. data/lib/action_view/template/handlers/builder.rb +0 -26
  229. data/lib/action_view/template/handlers/erb.rb +0 -125
  230. data/lib/action_view/template/handlers.rb +0 -50
  231. data/lib/action_view/template/resolver.rb +0 -272
  232. data/lib/action_view/template/text.rb +0 -30
  233. data/lib/action_view/template.rb +0 -337
  234. data/lib/action_view/test_case.rb +0 -245
  235. data/lib/action_view/testing/resolvers.rb +0 -50
  236. data/lib/action_view.rb +0 -84
  237. data/lib/sprockets/assets.rake +0 -99
  238. data/lib/sprockets/bootstrap.rb +0 -37
  239. data/lib/sprockets/compressors.rb +0 -83
  240. data/lib/sprockets/helpers/isolated_helper.rb +0 -13
  241. data/lib/sprockets/helpers/rails_helper.rb +0 -182
  242. data/lib/sprockets/helpers.rb +0 -6
  243. data/lib/sprockets/railtie.rb +0 -62
  244. data/lib/sprockets/static_compiler.rb +0 -56
@@ -1,232 +1,363 @@
1
1
  require 'active_support/core_ext/hash/except'
2
- require 'active_support/core_ext/object/blank'
3
- require 'active_support/core_ext/object/inclusion'
2
+ require 'active_support/core_ext/hash/reverse_merge'
3
+ require 'active_support/core_ext/hash/slice'
4
4
  require 'active_support/core_ext/enumerable'
5
+ require 'active_support/core_ext/array/extract_options'
6
+ require 'active_support/core_ext/module/remove_method'
7
+ require 'active_support/core_ext/string/filters'
5
8
  require 'active_support/inflector'
6
9
  require 'action_dispatch/routing/redirection'
10
+ require 'action_dispatch/routing/endpoint'
11
+ require 'active_support/deprecation'
7
12
 
8
13
  module ActionDispatch
9
14
  module Routing
10
15
  class Mapper
11
- class Constraints #:nodoc:
12
- def self.new(app, constraints, request = Rack::Request)
13
- if constraints.any?
14
- super(app, constraints, request)
15
- else
16
- app
17
- end
18
- end
16
+ URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
19
17
 
18
+ class Constraints < Endpoint #:nodoc:
20
19
  attr_reader :app, :constraints
21
20
 
22
- def initialize(app, constraints, request)
23
- @app, @constraints, @request = app, constraints, request
24
- end
21
+ def initialize(app, constraints, dispatcher_p)
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
25
30
 
26
- def matches?(env)
27
- req = @request.new(env)
31
+ @dispatcher = dispatcher_p
28
32
 
29
- @constraints.each { |constraint|
30
- if constraint.respond_to?(:matches?) && !constraint.matches?(req)
31
- return false
32
- elsif constraint.respond_to?(:call) && !constraint.call(*constraint_args(constraint, req))
33
- return false
34
- end
35
- }
33
+ @app, @constraints, = app, constraints
34
+ end
36
35
 
37
- return true
38
- ensure
39
- req.reset_parameters
36
+ def dispatcher?; @dispatcher; 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
40
43
  end
41
44
 
42
- def call(env)
43
- matches?(env) ? @app.call(env) : [ 404, {'X-Cascade' => 'pass'}, [] ]
45
+ def serve(req)
46
+ return [ 404, {'X-Cascade' => 'pass'}, [] ] unless matches?(req)
47
+
48
+ if dispatcher?
49
+ @app.serve req
50
+ else
51
+ @app.call req.env
52
+ end
44
53
  end
45
54
 
46
55
  private
47
56
  def constraint_args(constraint, request)
48
- constraint.arity == 1 ? [request] : [request.symbolized_path_parameters, request]
57
+ constraint.arity == 1 ? [request] : [request.path_parameters, request]
49
58
  end
50
59
  end
51
60
 
52
61
  class Mapping #:nodoc:
53
- IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix]
54
62
  ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
55
- WILDCARD_PATH = %r{\*([^/\)]+)\)?$}
63
+ OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z}
64
+
65
+ attr_reader :requirements, :conditions, :defaults
66
+ attr_reader :to, :default_controller, :default_action, :as, :anchor
67
+
68
+ def self.build(scope, set, path, as, options)
69
+ options = scope[:options].merge(options) if scope[:options]
70
+
71
+ options.delete :only
72
+ options.delete :except
73
+ options.delete :shallow_path
74
+ options.delete :shallow_prefix
75
+ options.delete :shallow
76
+
77
+ defaults = (scope[:defaults] || {}).merge options.delete(:defaults) || {}
78
+
79
+ new scope, set, path, defaults, as, options
80
+ end
81
+
82
+ def initialize(scope, set, path, defaults, as, options)
83
+ @requirements, @conditions = {}, {}
84
+ @defaults = defaults
85
+ @set = set
86
+
87
+ @to = options.delete :to
88
+ @default_controller = options.delete(:controller) || scope[:controller]
89
+ @default_action = options.delete(:action) || scope[:action]
90
+ @as = as
91
+ @anchor = options.delete :anchor
92
+
93
+ formatted = options.delete :format
94
+ via = Array(options.delete(:via) { [] })
95
+ options_constraints = options.delete :constraints
96
+
97
+ path = normalize_path! path, formatted
98
+ ast = path_ast path
99
+ path_params = path_params ast
100
+
101
+ options = normalize_options!(options, formatted, path_params, ast, scope[:module])
102
+
103
+
104
+ split_constraints(path_params, scope[:constraints]) if scope[:constraints]
105
+ constraints = constraints(options, path_params)
106
+
107
+ split_constraints path_params, constraints
108
+
109
+ @blocks = blocks(options_constraints, scope[:blocks])
56
110
 
57
- def initialize(set, scope, path, options)
58
- @set, @scope = set, scope
59
- @options = (@scope[:options] || {}).merge(options)
60
- @path = normalize_path(path)
61
- normalize_options!
111
+ if options_constraints.is_a?(Hash)
112
+ split_constraints path_params, options_constraints
113
+ options_constraints.each do |key, default|
114
+ if URL_OPTIONS.include?(key) && (String === default || Integer === default)
115
+ @defaults[key] ||= default
116
+ end
117
+ end
118
+ end
119
+
120
+ normalize_format!(formatted)
121
+
122
+ @conditions[:path_info] = path
123
+ @conditions[:parsed_path_info] = ast
124
+
125
+ add_request_method(via, @conditions)
126
+ normalize_defaults!(options)
62
127
  end
63
128
 
64
129
  def to_route
65
- [ app, conditions, requirements, defaults, @options[:as], @options[:anchor] ]
130
+ [ app(@blocks), conditions, requirements, defaults, as, anchor ]
66
131
  end
67
132
 
68
133
  private
69
134
 
70
- def normalize_options!
71
- @options.merge!(default_controller_and_action)
72
-
73
- requirements.each do |name, requirement|
74
- # segment_keys.include?(k.to_s) || k == :controller
75
- next unless Regexp === requirement && !constraints[name]
135
+ def normalize_path!(path, format)
136
+ path = Mapper.normalize_path(path)
76
137
 
77
- if requirement.source =~ ANCHOR_CHARACTERS_REGEX
78
- raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
79
- end
80
- if requirement.multiline?
81
- raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
82
- end
138
+ if format == true
139
+ "#{path}.:format"
140
+ elsif optional_format?(path, format)
141
+ "#{path}(.:format)"
142
+ else
143
+ path
83
144
  end
84
145
  end
85
146
 
86
- # match "account/overview"
87
- def using_match_shorthand?(path, options)
88
- path && (options[:to] || options[:action]).nil? && path =~ SHORTHAND_REGEX
147
+ def optional_format?(path, format)
148
+ format != false && path !~ OPTIONAL_FORMAT_REGEX
89
149
  end
90
150
 
91
- def normalize_path(path)
92
- raise ArgumentError, "path is required" if path.blank?
93
- path = Mapper.normalize_path(path)
151
+ def normalize_options!(options, formatted, path_params, path_ast, modyoule)
152
+ # Add a constraint for wildcard route to make it non-greedy and match the
153
+ # optional format part of the route by default
154
+ if formatted != false
155
+ path_ast.grep(Journey::Nodes::Star) do |node|
156
+ options[node.name.to_sym] ||= /.+?/
157
+ end
158
+ end
94
159
 
95
- if path.match(':controller')
96
- raise ArgumentError, ":controller segment is not allowed within a namespace block" if @scope[:module]
160
+ if path_params.include?(:controller)
161
+ raise ArgumentError, ":controller segment is not allowed within a namespace block" if modyoule
97
162
 
98
163
  # Add a default constraint for :controller path segments that matches namespaced
99
164
  # controllers with default routes like :controller/:action/:id(.:format), e.g:
100
165
  # GET /admin/products/show/1
101
- # => { :controller => 'admin/products', :action => 'show', :id => '1' }
102
- @options[:controller] ||= /.+?/
166
+ # => { controller: 'admin/products', action: 'show', id: '1' }
167
+ options[:controller] ||= /.+?/
103
168
  end
104
169
 
105
- # Add a constraint for wildcard route to make it non-greedy and match the
106
- # optional format part of the route by default
107
- if path.match(WILDCARD_PATH) && @options[:format] != false
108
- @options[$1.to_sym] ||= /.+?/
170
+ if to.respond_to? :call
171
+ options
172
+ else
173
+ to_endpoint = split_to to
174
+ controller = to_endpoint[0] || default_controller
175
+ action = to_endpoint[1] || default_action
176
+
177
+ controller = add_controller_module(controller, modyoule)
178
+
179
+ options.merge! check_controller_and_action(path_params, controller, action)
109
180
  end
181
+ end
110
182
 
111
- if @options[:format] == false
112
- @options.delete(:format)
113
- path
114
- elsif path.include?(":format") || path.end_with?('/')
115
- path
116
- elsif @options[:format] == true
117
- "#{path}.:format"
118
- else
119
- "#{path}(.:format)"
183
+ def split_constraints(path_params, constraints)
184
+ constraints.each_pair do |key, requirement|
185
+ if path_params.include?(key) || key == :controller
186
+ verify_regexp_requirement(requirement) if requirement.is_a?(Regexp)
187
+ @requirements[key] = requirement
188
+ else
189
+ @conditions[key] = requirement
190
+ end
120
191
  end
121
192
  end
122
193
 
123
- def app
124
- Constraints.new(
125
- to.respond_to?(:call) ? to : Routing::RouteSet::Dispatcher.new(:defaults => defaults),
126
- blocks,
127
- @set.request_class
128
- )
194
+ def normalize_format!(formatted)
195
+ if formatted == true
196
+ @requirements[:format] ||= /.+/
197
+ elsif Regexp === formatted
198
+ @requirements[:format] = formatted
199
+ @defaults[:format] = nil
200
+ elsif String === formatted
201
+ @requirements[:format] = Regexp.compile(formatted)
202
+ @defaults[:format] = formatted
203
+ end
129
204
  end
130
205
 
131
- def conditions
132
- { :path_info => @path }.merge(constraints).merge(request_method_condition)
206
+ def verify_regexp_requirement(requirement)
207
+ if requirement.source =~ ANCHOR_CHARACTERS_REGEX
208
+ raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
209
+ end
210
+
211
+ if requirement.multiline?
212
+ raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
213
+ end
133
214
  end
134
215
 
135
- def requirements
136
- @requirements ||= (@options[:constraints].is_a?(Hash) ? @options[:constraints] : {}).tap do |requirements|
137
- requirements.reverse_merge!(@scope[:constraints]) if @scope[:constraints]
138
- @options.each { |k, v| requirements[k] = v if v.is_a?(Regexp) }
216
+ def normalize_defaults!(options)
217
+ options.each_pair do |key, default|
218
+ unless Regexp === default
219
+ @defaults[key] = default
220
+ end
139
221
  end
140
222
  end
141
223
 
142
- def defaults
143
- @defaults ||= (@options[:defaults] || {}).tap do |defaults|
144
- defaults.reverse_merge!(@scope[:defaults]) if @scope[:defaults]
145
- @options.each { |k, v| defaults[k] = v unless v.is_a?(Regexp) || IGNORE_OPTIONS.include?(k.to_sym) }
224
+ def verify_callable_constraint(callable_constraint)
225
+ unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?)
226
+ raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?"
146
227
  end
147
228
  end
148
229
 
149
- def default_controller_and_action
150
- if to.respond_to?(:call)
151
- { }
152
- else
153
- if to.is_a?(String)
154
- controller, action = to.split('#')
155
- elsif to.is_a?(Symbol)
156
- action = to.to_s
157
- end
230
+ def add_request_method(via, conditions)
231
+ return if via == [:all]
158
232
 
159
- controller ||= default_controller
160
- action ||= default_action
233
+ if via.empty?
234
+ msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
235
+ "If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
236
+ "If you want to expose your action to GET, use `get` in the router:\n" \
237
+ " Instead of: match \"controller#action\"\n" \
238
+ " Do: get \"controller#action\""
239
+ raise ArgumentError, msg
240
+ end
161
241
 
162
- unless controller.is_a?(Regexp)
163
- controller = [@scope[:module], controller].compact.join("/").presence
164
- end
242
+ conditions[:request_method] = via.map { |m| m.to_s.dasherize.upcase }
243
+ end
165
244
 
166
- if controller.is_a?(String) && controller =~ %r{\A/}
167
- raise ArgumentError, "controller name should not start with a slash"
168
- end
245
+ def app(blocks)
246
+ if to.respond_to?(:call)
247
+ Constraints.new(to, blocks, false)
248
+ elsif blocks.any?
249
+ Constraints.new(dispatcher(defaults), blocks, true)
250
+ else
251
+ dispatcher(defaults)
252
+ end
253
+ end
169
254
 
170
- controller = controller.to_s unless controller.is_a?(Regexp)
171
- action = action.to_s unless action.is_a?(Regexp)
255
+ def check_controller_and_action(path_params, controller, action)
256
+ hash = check_part(:controller, controller, path_params, {}) do |part|
257
+ translate_controller(part) {
258
+ message = "'#{part}' is not a supported controller name. This can lead to potential routing problems."
259
+ message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
172
260
 
173
- if controller.blank? && segment_keys.exclude?("controller")
174
- raise ArgumentError, "missing :controller"
175
- end
261
+ raise ArgumentError, message
262
+ }
263
+ end
176
264
 
177
- if action.blank? && segment_keys.exclude?("action")
178
- raise ArgumentError, "missing :action"
179
- end
265
+ check_part(:action, action, path_params, hash) { |part|
266
+ part.is_a?(Regexp) ? part : part.to_s
267
+ }
268
+ end
180
269
 
181
- hash = {}
182
- hash[:controller] = controller unless controller.blank?
183
- hash[:action] = action unless action.blank?
184
- hash
270
+ def check_part(name, part, path_params, hash)
271
+ if part
272
+ hash[name] = yield(part)
273
+ else
274
+ unless path_params.include?(name)
275
+ message = "Missing :#{name} key on routes definition, please check your routes."
276
+ raise ArgumentError, message
277
+ end
278
+ end
279
+ hash
280
+ end
281
+
282
+ def split_to(to)
283
+ case to
284
+ when Symbol
285
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
286
+ Defining a route where `to` is a symbol is deprecated.
287
+ Please change `to: :#{to}` to `action: :#{to}`.
288
+ MSG
289
+
290
+ [nil, to.to_s]
291
+ when /#/ then to.split('#')
292
+ when String
293
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
294
+ Defining a route where `to` is a controller without an action is deprecated.
295
+ Please change `to: '#{to}'` to `controller: '#{to}'`.
296
+ MSG
297
+
298
+ [to, nil]
299
+ else
300
+ []
185
301
  end
186
302
  end
187
303
 
188
- def blocks
189
- constraints = @options[:constraints]
190
- if constraints.present? && !constraints.is_a?(Hash)
191
- [constraints]
304
+ def add_controller_module(controller, modyoule)
305
+ if modyoule && !controller.is_a?(Regexp)
306
+ if controller =~ %r{\A/}
307
+ controller[1..-1]
308
+ else
309
+ [modyoule, controller].compact.join("/")
310
+ end
192
311
  else
193
- @scope[:blocks] || []
312
+ controller
194
313
  end
195
314
  end
196
315
 
197
- def constraints
198
- @constraints ||= requirements.reject { |k, v| segment_keys.include?(k.to_s) || k == :controller }
316
+ def translate_controller(controller)
317
+ return controller if Regexp === controller
318
+ return controller.to_s if controller =~ /\A[a-z_0-9][a-z_0-9\/]*\z/
319
+
320
+ yield
199
321
  end
200
322
 
201
- def request_method_condition
202
- if via = @options[:via]
203
- list = Array(via).map { |m| m.to_s.dasherize.upcase }
204
- { :request_method => list }
323
+ def blocks(options_constraints, scope_blocks)
324
+ if options_constraints && !options_constraints.is_a?(Hash)
325
+ verify_callable_constraint(options_constraints)
326
+ [options_constraints]
205
327
  else
206
- { }
328
+ scope_blocks || []
207
329
  end
208
330
  end
209
331
 
210
- def segment_keys
211
- @segment_keys ||= Journey::Path::Pattern.new(
212
- Journey::Router::Strexp.compile(@path, requirements, SEPARATORS)
213
- ).names
332
+ def constraints(options, path_params)
333
+ constraints = {}
334
+ required_defaults = []
335
+ options.each_pair do |key, option|
336
+ if Regexp === option
337
+ constraints[key] = option
338
+ else
339
+ required_defaults << key unless path_params.include?(key)
340
+ end
341
+ end
342
+ @conditions[:required_defaults] = required_defaults
343
+ constraints
214
344
  end
215
345
 
216
- def to
217
- @options[:to]
346
+ def path_params(ast)
347
+ ast.grep(Journey::Nodes::Symbol).map { |n| n.name.to_sym }
218
348
  end
219
349
 
220
- def default_controller
221
- @options[:controller] || @scope[:controller]
350
+ def path_ast(path)
351
+ parser = Journey::Parser.new
352
+ parser.parse path
222
353
  end
223
354
 
224
- def default_action
225
- @options[:action] || @scope[:action]
355
+ def dispatcher(defaults)
356
+ @set.dispatcher defaults
226
357
  end
227
358
  end
228
359
 
229
- # Invokes Rack::Mount::Utils.normalize path and ensure that
360
+ # Invokes Journey::Router::Utils.normalize_path and ensure that
230
361
  # (:locale) becomes (/:locale) instead of /(:locale). Except
231
362
  # for root cases, where the latter is the correct one.
232
363
  def self.normalize_path(path)
@@ -236,54 +367,83 @@ module ActionDispatch
236
367
  end
237
368
 
238
369
  def self.normalize_name(name)
239
- normalize_path(name)[1..-1].gsub("/", "_")
370
+ normalize_path(name)[1..-1].tr("/", "_")
240
371
  end
241
372
 
242
373
  module Base
243
374
  # You can specify what Rails should route "/" to with the root method:
244
375
  #
245
- # root :to => 'pages#main'
376
+ # root to: 'pages#main'
246
377
  #
247
378
  # For options, see +match+, as +root+ uses it internally.
248
379
  #
380
+ # You can also pass a string which will expand
381
+ #
382
+ # root 'pages#main'
383
+ #
249
384
  # You should put the root route at the top of <tt>config/routes.rb</tt>,
250
385
  # because this means it will be matched first. As this is the most popular route
251
386
  # of most Rails applications, this is beneficial.
252
387
  def root(options = {})
253
- match '/', { :as => :root }.merge(options)
388
+ match '/', { :as => :root, :via => :get }.merge!(options)
254
389
  end
255
390
 
256
- # Matches a url pattern to one or more routes. Any symbols in a pattern
257
- # are interpreted as url query parameters and thus available as +params+
258
- # in an action:
391
+ # Matches a url pattern to one or more routes.
392
+ #
393
+ # You should not use the +match+ method in your router
394
+ # without specifying an HTTP method.
395
+ #
396
+ # If you want to expose your action to both GET and POST, use:
259
397
  #
260
398
  # # sets :controller, :action and :id in params
261
- # match ':controller/:action/:id'
399
+ # match ':controller/:action/:id', via: [:get, :post]
400
+ #
401
+ # Note that +:controller+, +:action+ and +:id+ are interpreted as url
402
+ # query parameters and thus available through +params+ in an action.
403
+ #
404
+ # If you want to expose your action to GET, use +get+ in the router:
405
+ #
406
+ # Instead of:
407
+ #
408
+ # match ":controller/:action/:id"
409
+ #
410
+ # Do:
411
+ #
412
+ # get ":controller/:action/:id"
262
413
  #
263
414
  # Two of these symbols are special, +:controller+ maps to the controller
264
415
  # and +:action+ to the controller's action. A pattern can also map
265
416
  # wildcard segments (globs) to params:
266
417
  #
267
- # match 'songs/*category/:title' => 'songs#show'
418
+ # get 'songs/*category/:title', to: 'songs#show'
268
419
  #
269
420
  # # 'songs/rock/classic/stairway-to-heaven' sets
270
421
  # # params[:category] = 'rock/classic'
271
422
  # # params[:title] = 'stairway-to-heaven'
272
423
  #
424
+ # To match a wildcard parameter, it must have a name assigned to it.
425
+ # Without a variable name to attach the glob parameter to, the route
426
+ # can't be parsed.
427
+ #
273
428
  # When a pattern points to an internal route, the route's +:action+ and
274
429
  # +:controller+ should be set in options or hash shorthand. Examples:
275
430
  #
276
- # match 'photos/:id' => 'photos#show'
277
- # match 'photos/:id', :to => 'photos#show'
278
- # match 'photos/:id', :controller => 'photos', :action => 'show'
431
+ # match 'photos/:id' => 'photos#show', via: :get
432
+ # match 'photos/:id', to: 'photos#show', via: :get
433
+ # match 'photos/:id', controller: 'photos', action: 'show', via: :get
279
434
  #
280
435
  # A pattern can also point to a +Rack+ endpoint i.e. anything that
281
436
  # responds to +call+:
282
437
  #
283
- # match 'photos/:id' => lambda {|hash| [200, {}, "Coming soon"] }
284
- # match 'photos/:id' => PhotoRackApp
438
+ # match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] }, via: :get
439
+ # match 'photos/:id', to: PhotoRackApp, via: :get
285
440
  # # Yes, controller actions are just rack endpoints
286
- # match 'photos/:id' => PhotosController.action(:show)
441
+ # match 'photos/:id', to: PhotosController.action(:show), via: :get
442
+ #
443
+ # Because requesting various HTTP verbs with a single action has security
444
+ # implications, you must either specify the actions in
445
+ # the via options or use one of the HttpHelpers[rdoc-ref:HttpHelpers]
446
+ # instead +match+
287
447
  #
288
448
  # === Options
289
449
  #
@@ -295,14 +455,20 @@ module ActionDispatch
295
455
  # [:action]
296
456
  # The route's action.
297
457
  #
458
+ # [:param]
459
+ # Overrides the default resource identifier +:id+ (name of the
460
+ # dynamic segment used to generate the routes).
461
+ # You can access that segment from your controller using
462
+ # <tt>params[<:param>]</tt>.
463
+ #
298
464
  # [:path]
299
465
  # The path prefix for the routes.
300
466
  #
301
467
  # [:module]
302
468
  # The namespace for :controller.
303
469
  #
304
- # match 'path' => 'c#a', :module => 'sekret', :controller => 'posts'
305
- # #=> Sekret::PostsController
470
+ # match 'path', to: 'c#a', module: 'sekret', controller: 'posts', via: :get
471
+ # # => Sekret::PostsController
306
472
  #
307
473
  # See <tt>Scoping#namespace</tt> for its scope equivalent.
308
474
  #
@@ -312,16 +478,17 @@ module ActionDispatch
312
478
  # [:via]
313
479
  # Allowed HTTP verb(s) for route.
314
480
  #
315
- # match 'path' => 'c#a', :via => :get
316
- # match 'path' => 'c#a', :via => [:get, :post]
481
+ # match 'path', to: 'c#a', via: :get
482
+ # match 'path', to: 'c#a', via: [:get, :post]
483
+ # match 'path', to: 'c#a', via: :all
317
484
  #
318
485
  # [:to]
319
486
  # Points to a +Rack+ endpoint. Can be an object that responds to
320
487
  # +call+ or a string representing a controller's action.
321
488
  #
322
- # match 'path', :to => 'controller#action'
323
- # match 'path', :to => lambda { |env| [200, {}, "Success!"] }
324
- # match 'path', :to => RackApp
489
+ # match 'path', to: 'controller#action', via: :get
490
+ # match 'path', to: lambda { |env| [200, {}, ["Success!"]] }, via: :get
491
+ # match 'path', to: RackApp, via: :get
325
492
  #
326
493
  # [:on]
327
494
  # Shorthand for wrapping routes in a specific RESTful context. Valid
@@ -329,27 +496,31 @@ module ActionDispatch
329
496
  # <tt>resource(s)</tt> block. For example:
330
497
  #
331
498
  # resource :bar do
332
- # match 'foo' => 'c#a', :on => :member, :via => [:get, :post]
499
+ # match 'foo', to: 'c#a', on: :member, via: [:get, :post]
333
500
  # end
334
501
  #
335
502
  # Is equivalent to:
336
503
  #
337
504
  # resource :bar do
338
505
  # member do
339
- # match 'foo' => 'c#a', :via => [:get, :post]
506
+ # match 'foo', to: 'c#a', via: [:get, :post]
340
507
  # end
341
508
  # end
342
509
  #
343
510
  # [:constraints]
344
- # Constrains parameters with a hash of regular expressions or an
345
- # object that responds to <tt>matches?</tt>
511
+ # Constrains parameters with a hash of regular expressions
512
+ # or an object that responds to <tt>matches?</tt>. In addition, constraints
513
+ # other than path can also be specified with any object
514
+ # that responds to <tt>===</tt> (eg. String, Array, Range, etc.).
346
515
  #
347
- # match 'path/:id', :constraints => { :id => /[A-Z]\d{5}/ }
516
+ # match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via: :get
348
517
  #
349
- # class Blacklist
518
+ # match 'json_only', constraints: { format: 'json' }, via: :get
519
+ #
520
+ # class Whitelist
350
521
  # def matches?(request) request.remote_ip == '1.2.3.4' end
351
522
  # end
352
- # match 'path' => 'c#a', :constraints => Blacklist.new
523
+ # match 'path', to: 'c#a', constraints: Whitelist.new, via: :get
353
524
  #
354
525
  # See <tt>Scoping#constraints</tt> for more examples with its scope
355
526
  # equivalent.
@@ -358,7 +529,7 @@ module ActionDispatch
358
529
  # Sets defaults for parameters
359
530
  #
360
531
  # # Sets params[:format] to 'jpg' by default
361
- # match 'path' => 'c#a', :defaults => { :format => 'jpg' }
532
+ # match 'path', to: 'c#a', defaults: { format: 'jpg' }, via: :get
362
533
  #
363
534
  # See <tt>Scoping#defaults</tt> for its scope equivalent.
364
535
  #
@@ -367,13 +538,17 @@ module ActionDispatch
367
538
  # false, the pattern matches any request prefixed with the given path.
368
539
  #
369
540
  # # Matches any request starting with 'path'
370
- # match 'path' => 'c#a', :anchor => false
541
+ # match 'path', to: 'c#a', anchor: false, via: :get
542
+ #
543
+ # [:format]
544
+ # Allows you to specify the default value for optional +format+
545
+ # segment or disable it by supplying +false+.
371
546
  def match(path, options=nil)
372
547
  end
373
548
 
374
549
  # Mount a Rack-based application to be used within the application.
375
550
  #
376
- # mount SomeRackApp, :at => "some_route"
551
+ # mount SomeRackApp, at: "some_route"
377
552
  #
378
553
  # Alternatively:
379
554
  #
@@ -386,7 +561,7 @@ module ActionDispatch
386
561
  # the helper is either +some_rack_app_path+ or +some_rack_app_url+.
387
562
  # To customize this helper's name, use the +:as+ option:
388
563
  #
389
- # mount(SomeRackApp => "some_route", :as => "exciting")
564
+ # mount(SomeRackApp => "some_route", as: "exciting")
390
565
  #
391
566
  # This will generate the +exciting_path+ and +exciting_url+ helpers
392
567
  # which can be used to navigate to this mounted app.
@@ -394,18 +569,26 @@ module ActionDispatch
394
569
  if options
395
570
  path = options.delete(:at)
396
571
  else
572
+ unless Hash === app
573
+ raise ArgumentError, "must be called with mount point"
574
+ end
575
+
397
576
  options = app
398
- app, path = options.find { |k, v| k.respond_to?(:call) }
577
+ app, path = options.find { |k, _| k.respond_to?(:call) }
399
578
  options.delete(app) if app
400
579
  end
401
580
 
402
581
  raise "A rack application must be specified" unless path
403
582
 
404
- options[:as] ||= app_name(app)
583
+ rails_app = rails_app? app
584
+ options[:as] ||= app_name(app, rails_app)
585
+
586
+ target_as = name_for_action(options[:as], path)
587
+ options[:via] ||= :all
405
588
 
406
589
  match(path, options.merge(:to => app, :anchor => false, :format => false))
407
590
 
408
- define_generate_prefix(app, options[:as])
591
+ define_generate_prefix(app, target_as) if rails_app
409
592
  self
410
593
  end
411
594
 
@@ -420,84 +603,92 @@ module ActionDispatch
420
603
  end
421
604
  end
422
605
 
606
+ # Query if the following named route was already defined.
607
+ def has_named_route?(name)
608
+ @set.named_routes.routes[name.to_sym]
609
+ end
610
+
423
611
  private
424
- def app_name(app)
425
- return unless app.respond_to?(:routes)
612
+ def rails_app?(app)
613
+ app.is_a?(Class) && app < Rails::Railtie
614
+ end
426
615
 
427
- if app.respond_to?(:railtie_name)
616
+ def app_name(app, rails_app)
617
+ if rails_app
428
618
  app.railtie_name
429
- else
430
- class_name = app.class.is_a?(Class) ? app.name : app.class.name
431
- ActiveSupport::Inflector.underscore(class_name).gsub("/", "_")
619
+ elsif app.is_a?(Class)
620
+ class_name = app.name
621
+ ActiveSupport::Inflector.underscore(class_name).tr("/", "_")
432
622
  end
433
623
  end
434
624
 
435
625
  def define_generate_prefix(app, name)
436
- return unless app.respond_to?(:routes) && app.routes.respond_to?(:define_mounted_helper)
437
-
438
- _route = @set.named_routes.routes[name.to_sym]
626
+ _route = @set.named_routes.get name
439
627
  _routes = @set
440
628
  app.routes.define_mounted_helper(name)
441
- app.routes.class_eval do
442
- define_method :_generate_prefix do |options|
443
- prefix_options = options.slice(*_route.segment_keys)
444
- # we must actually delete prefix segment keys to avoid passing them to next url_for
445
- _route.segment_keys.each { |k| options.delete(k) }
446
- prefix = _routes.url_helpers.send("#{name}_path", prefix_options)
447
- prefix = prefix.gsub(%r{/\z}, '')
448
- prefix
629
+ app.routes.extend Module.new {
630
+ def optimize_routes_generation?; false; end
631
+ define_method :find_script_name do |options|
632
+ if options.key? :script_name
633
+ super(options)
634
+ else
635
+ prefix_options = options.slice(*_route.segment_keys)
636
+ prefix_options[:relative_url_root] = ''.freeze
637
+ # we must actually delete prefix segment keys to avoid passing them to next url_for
638
+ _route.segment_keys.each { |k| options.delete(k) }
639
+ _routes.url_helpers.send("#{name}_path", prefix_options)
640
+ end
449
641
  end
450
- end
642
+ }
451
643
  end
452
644
  end
453
645
 
454
646
  module HttpHelpers
455
647
  # Define a route that only recognizes HTTP GET.
456
- # For supported arguments, see <tt>Base#match</tt>.
457
- #
458
- # Example:
648
+ # For supported arguments, see match[rdoc-ref:Base#match]
459
649
  #
460
- # get 'bacon', :to => 'food#bacon'
650
+ # get 'bacon', to: 'food#bacon'
461
651
  def get(*args, &block)
462
- map_method(:get, *args, &block)
652
+ map_method(:get, args, &block)
463
653
  end
464
654
 
465
655
  # Define a route that only recognizes HTTP POST.
466
- # For supported arguments, see <tt>Base#match</tt>.
656
+ # For supported arguments, see match[rdoc-ref:Base#match]
467
657
  #
468
- # Example:
469
- #
470
- # post 'bacon', :to => 'food#bacon'
658
+ # post 'bacon', to: 'food#bacon'
471
659
  def post(*args, &block)
472
- map_method(:post, *args, &block)
660
+ map_method(:post, args, &block)
473
661
  end
474
662
 
475
- # Define a route that only recognizes HTTP PUT.
476
- # For supported arguments, see <tt>Base#match</tt>.
477
- #
478
- # Example:
663
+ # Define a route that only recognizes HTTP PATCH.
664
+ # For supported arguments, see match[rdoc-ref:Base#match]
479
665
  #
480
- # put 'bacon', :to => 'food#bacon'
481
- def put(*args, &block)
482
- map_method(:put, *args, &block)
666
+ # patch 'bacon', to: 'food#bacon'
667
+ def patch(*args, &block)
668
+ map_method(:patch, args, &block)
483
669
  end
484
670
 
485
671
  # Define a route that only recognizes HTTP PUT.
486
- # For supported arguments, see <tt>Base#match</tt>.
672
+ # For supported arguments, see match[rdoc-ref:Base#match]
487
673
  #
488
- # Example:
674
+ # put 'bacon', to: 'food#bacon'
675
+ def put(*args, &block)
676
+ map_method(:put, args, &block)
677
+ end
678
+
679
+ # Define a route that only recognizes HTTP DELETE.
680
+ # For supported arguments, see match[rdoc-ref:Base#match]
489
681
  #
490
- # delete 'broccoli', :to => 'food#broccoli'
682
+ # delete 'broccoli', to: 'food#broccoli'
491
683
  def delete(*args, &block)
492
- map_method(:delete, *args, &block)
684
+ map_method(:delete, args, &block)
493
685
  end
494
686
 
495
687
  private
496
- def map_method(method, *args, &block)
688
+ def map_method(method, args, &block)
497
689
  options = args.extract_options!
498
690
  options[:via] = method
499
- args.push(options)
500
- match(*args, &block)
691
+ match(*args, options, &block)
501
692
  self
502
693
  end
503
694
  end
@@ -515,27 +706,27 @@ module ActionDispatch
515
706
  # This will create a number of routes for each of the posts and comments
516
707
  # controller. For <tt>Admin::PostsController</tt>, Rails will create:
517
708
  #
518
- # GET /admin/posts
519
- # GET /admin/posts/new
520
- # POST /admin/posts
521
- # GET /admin/posts/1
522
- # GET /admin/posts/1/edit
523
- # PUT /admin/posts/1
524
- # DELETE /admin/posts/1
709
+ # GET /admin/posts
710
+ # GET /admin/posts/new
711
+ # POST /admin/posts
712
+ # GET /admin/posts/1
713
+ # GET /admin/posts/1/edit
714
+ # PATCH/PUT /admin/posts/1
715
+ # DELETE /admin/posts/1
525
716
  #
526
717
  # If you want to route /posts (without the prefix /admin) to
527
718
  # <tt>Admin::PostsController</tt>, you could use
528
719
  #
529
- # scope :module => "admin" do
720
+ # scope module: "admin" do
530
721
  # resources :posts
531
722
  # end
532
723
  #
533
724
  # or, for a single case
534
725
  #
535
- # resources :posts, :module => "admin"
726
+ # resources :posts, module: "admin"
536
727
  #
537
728
  # If you want to route /admin/posts to +PostsController+
538
- # (without the Admin:: module prefix), you could use
729
+ # (without the <tt>Admin::</tt> module prefix), you could use
539
730
  #
540
731
  # scope "/admin" do
541
732
  # resources :posts
@@ -543,25 +734,25 @@ module ActionDispatch
543
734
  #
544
735
  # or, for a single case
545
736
  #
546
- # resources :posts, :path => "/admin/posts"
737
+ # resources :posts, path: "/admin/posts"
547
738
  #
548
739
  # In each of these cases, the named routes remain the same as if you did
549
740
  # not use scope. In the last case, the following paths map to
550
741
  # +PostsController+:
551
742
  #
552
- # GET /admin/posts
553
- # GET /admin/posts/new
554
- # POST /admin/posts
555
- # GET /admin/posts/1
556
- # GET /admin/posts/1/edit
557
- # PUT /admin/posts/1
558
- # DELETE /admin/posts/1
743
+ # GET /admin/posts
744
+ # GET /admin/posts/new
745
+ # POST /admin/posts
746
+ # GET /admin/posts/1
747
+ # GET /admin/posts/1/edit
748
+ # PATCH/PUT /admin/posts/1
749
+ # DELETE /admin/posts/1
559
750
  module Scoping
560
751
  # Scopes a set of routes to the given default options.
561
752
  #
562
753
  # Take the following route definition as an example:
563
754
  #
564
- # scope :path => ":account_id", :as => "account" do
755
+ # scope path: ":account_id", as: "account" do
565
756
  # resources :projects
566
757
  # end
567
758
  #
@@ -573,63 +764,67 @@ module ActionDispatch
573
764
  #
574
765
  # Takes same options as <tt>Base#match</tt> and <tt>Resources#resources</tt>.
575
766
  #
576
- # === Examples
577
- #
578
767
  # # route /posts (without the prefix /admin) to <tt>Admin::PostsController</tt>
579
- # scope :module => "admin" do
768
+ # scope module: "admin" do
580
769
  # resources :posts
581
770
  # end
582
771
  #
583
772
  # # prefix the posts resource's requests with '/admin'
584
- # scope :path => "/admin" do
773
+ # scope path: "/admin" do
585
774
  # resources :posts
586
775
  # end
587
776
  #
588
777
  # # prefix the routing helper name: +sekret_posts_path+ instead of +posts_path+
589
- # scope :as => "sekret" do
778
+ # scope as: "sekret" do
590
779
  # resources :posts
591
780
  # end
592
781
  def scope(*args)
593
- options = args.extract_options!
594
- options = options.dup
595
-
596
- options[:path] = args.first if args.first.is_a?(String)
597
- recover = {}
782
+ options = args.extract_options!.dup
783
+ scope = {}
598
784
 
785
+ options[:path] = args.flatten.join('/') if args.any?
599
786
  options[:constraints] ||= {}
600
- unless options[:constraints].is_a?(Hash)
601
- block, options[:constraints] = options[:constraints], {}
787
+
788
+ unless nested_scope?
789
+ options[:shallow_path] ||= options[:path] if options.key?(:path)
790
+ options[:shallow_prefix] ||= options[:as] if options.key?(:as)
602
791
  end
603
792
 
604
- scope_options.each do |option|
605
- if value = options.delete(option)
606
- recover[option] = @scope[option]
607
- @scope[option] = send("merge_#{option}_scope", @scope[option], value)
793
+ if options[:constraints].is_a?(Hash)
794
+ defaults = options[:constraints].select do |k, v|
795
+ URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Integer))
608
796
  end
797
+
798
+ (options[:defaults] ||= {}).reverse_merge!(defaults)
799
+ else
800
+ block, options[:constraints] = options[:constraints], {}
609
801
  end
610
802
 
611
- recover[:block] = @scope[:blocks]
612
- @scope[:blocks] = merge_blocks_scope(@scope[:blocks], block)
803
+ @scope.options.each do |option|
804
+ if option == :blocks
805
+ value = block
806
+ elsif option == :options
807
+ value = options
808
+ else
809
+ value = options.delete(option)
810
+ end
613
811
 
614
- recover[:options] = @scope[:options]
615
- @scope[:options] = merge_options_scope(@scope[:options], options)
812
+ if value
813
+ scope[option] = send("merge_#{option}_scope", @scope[option], value)
814
+ end
815
+ end
616
816
 
817
+ @scope = @scope.new scope
617
818
  yield
618
819
  self
619
820
  ensure
620
- scope_options.each do |option|
621
- @scope[option] = recover[option] if recover.has_key?(option)
622
- end
623
-
624
- @scope[:options] = recover[:options]
625
- @scope[:blocks] = recover[:block]
821
+ @scope = @scope.parent
626
822
  end
627
823
 
628
824
  # Scopes routes to a specific controller
629
825
  #
630
- # Example:
631
826
  # controller "food" do
632
- # match "bacon", :action => "bacon"
827
+ # match "bacon", action: "bacon"
633
828
  # end
634
829
  def controller(controller, options={})
635
830
  options[:controller] = controller
@@ -644,13 +839,13 @@ module ActionDispatch
644
839
  #
645
840
  # This generates the following routes:
646
841
  #
647
- # admin_posts GET /admin/posts(.:format) admin/posts#index
648
- # admin_posts POST /admin/posts(.:format) admin/posts#create
649
- # new_admin_post GET /admin/posts/new(.:format) admin/posts#new
650
- # edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
651
- # admin_post GET /admin/posts/:id(.:format) admin/posts#show
652
- # admin_post PUT /admin/posts/:id(.:format) admin/posts#update
653
- # admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
842
+ # admin_posts GET /admin/posts(.:format) admin/posts#index
843
+ # admin_posts POST /admin/posts(.:format) admin/posts#create
844
+ # new_admin_post GET /admin/posts/new(.:format) admin/posts#new
845
+ # edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
846
+ # admin_post GET /admin/posts/:id(.:format) admin/posts#show
847
+ # admin_post PATCH/PUT /admin/posts/:id(.:format) admin/posts#update
848
+ # admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
654
849
  #
655
850
  # === Options
656
851
  #
@@ -660,34 +855,39 @@ module ActionDispatch
660
855
  # For options, see <tt>Base#match</tt>. For +:shallow_path+ option, see
661
856
  # <tt>Resources#resources</tt>.
662
857
  #
663
- # === Examples
664
- #
665
858
  # # accessible through /sekret/posts rather than /admin/posts
666
- # namespace :admin, :path => "sekret" do
859
+ # namespace :admin, path: "sekret" do
667
860
  # resources :posts
668
861
  # end
669
862
  #
670
863
  # # maps to <tt>Sekret::PostsController</tt> rather than <tt>Admin::PostsController</tt>
671
- # namespace :admin, :module => "sekret" do
864
+ # namespace :admin, module: "sekret" do
672
865
  # resources :posts
673
866
  # end
674
867
  #
675
868
  # # generates +sekret_posts_path+ rather than +admin_posts_path+
676
- # namespace :admin, :as => "sekret" do
869
+ # namespace :admin, as: "sekret" do
677
870
  # resources :posts
678
871
  # end
679
872
  def namespace(path, options = {})
680
873
  path = path.to_s
681
- options = { :path => path, :as => path, :module => path,
682
- :shallow_path => path, :shallow_prefix => path }.merge!(options)
683
- scope(options) { yield }
874
+
875
+ defaults = {
876
+ module: path,
877
+ path: options.fetch(:path, path),
878
+ as: options.fetch(:as, path),
879
+ shallow_path: options.fetch(:path, path),
880
+ shallow_prefix: options.fetch(:as, path)
881
+ }
882
+
883
+ scope(defaults.merge!(options)) { yield }
684
884
  end
685
885
 
686
886
  # === Parameter Restriction
687
887
  # Allows you to constrain the nested routes based on a set of rules.
688
888
  # For instance, in order to change the routes to allow for a dot character in the +id+ parameter:
689
889
  #
690
- # constraints(:id => /\d+\.\d+/) do
890
+ # constraints(id: /\d+\.\d+/) do
691
891
  # resources :posts
692
892
  # end
693
893
  #
@@ -697,7 +897,7 @@ module ActionDispatch
697
897
  # You may use this to also restrict other parameters:
698
898
  #
699
899
  # resources :posts do
700
- # constraints(:post_id => /\d+\.\d+/) do
900
+ # constraints(post_id: /\d+\.\d+/) do
701
901
  # resources :comments
702
902
  # end
703
903
  # end
@@ -706,7 +906,7 @@ module ActionDispatch
706
906
  #
707
907
  # Routes can also be constrained to an IP or a certain range of IP addresses:
708
908
  #
709
- # constraints(:ip => /192.168.\d+.\d+/) do
909
+ # constraints(ip: /192\.168\.\d+\.\d+/) do
710
910
  # resources :posts
711
911
  # end
712
912
  #
@@ -743,8 +943,8 @@ module ActionDispatch
743
943
  end
744
944
 
745
945
  # Allows you to set default parameters for a route, such as this:
746
- # defaults :id => 'home' do
747
- # match 'scoped_pages/(:id)', :to => 'pages#show'
946
+ # defaults id: 'home' do
947
+ # match 'scoped_pages/(:id)', to: 'pages#show'
748
948
  # end
749
949
  # Using this, the +:id+ parameter here will default to 'home'.
750
950
  def defaults(defaults = {})
@@ -752,10 +952,6 @@ module ActionDispatch
752
952
  end
753
953
 
754
954
  private
755
- def scope_options #:nodoc:
756
- @scope_options ||= private_methods.grep(/^merge_(.+)_scope$/) { $1.to_sym }
757
- end
758
-
759
955
  def merge_path_scope(parent, child) #:nodoc:
760
956
  Mapper.normalize_path("#{parent}/#{child}")
761
957
  end
@@ -803,7 +999,7 @@ module ActionDispatch
803
999
  end
804
1000
 
805
1001
  def merge_options_scope(parent, child) #:nodoc:
806
- (parent || {}).except(*override_keys(child)).merge(child)
1002
+ (parent || {}).except(*override_keys(child)).merge!(child)
807
1003
  end
808
1004
 
809
1005
  def merge_shallow_scope(parent, child) #:nodoc:
@@ -850,7 +1046,7 @@ module ActionDispatch
850
1046
  # use dots as part of the +:id+ parameter add a constraint which
851
1047
  # overrides this restriction, e.g:
852
1048
  #
853
- # resources :articles, :id => /[^\/]+/
1049
+ # resources :articles, id: /[^\/]+/
854
1050
  #
855
1051
  # This allows any character other than a slash as part of your +:id+.
856
1052
  #
@@ -858,18 +1054,20 @@ module ActionDispatch
858
1054
  # CANONICAL_ACTIONS holds all actions that does not need a prefix or
859
1055
  # a path appended since they fit properly in their scope level.
860
1056
  VALID_ON_OPTIONS = [:new, :collection, :member]
861
- RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except]
1057
+ RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns]
862
1058
  CANONICAL_ACTIONS = %w(index create new show update destroy)
863
1059
 
864
1060
  class Resource #:nodoc:
865
- attr_reader :controller, :path, :options
1061
+ attr_reader :controller, :path, :options, :param
866
1062
 
867
1063
  def initialize(entities, options = {})
868
1064
  @name = entities.to_s
869
1065
  @path = (options[:path] || @name).to_s
870
1066
  @controller = (options[:controller] || @name).to_s
871
1067
  @as = options[:as]
1068
+ @param = (options[:param] || :id).to_sym
872
1069
  @options = options
1070
+ @shallow = false
873
1071
  end
874
1072
 
875
1073
  def default_actions
@@ -913,17 +1111,30 @@ module ActionDispatch
913
1111
  alias :collection_scope :path
914
1112
 
915
1113
  def member_scope
916
- "#{path}/:id"
1114
+ "#{path}/:#{param}"
917
1115
  end
918
1116
 
1117
+ alias :shallow_scope :member_scope
1118
+
919
1119
  def new_scope(new_path)
920
1120
  "#{path}/#{new_path}"
921
1121
  end
922
1122
 
1123
+ def nested_param
1124
+ :"#{singular}_#{param}"
1125
+ end
1126
+
923
1127
  def nested_scope
924
- "#{path}/:#{singular}_id"
1128
+ "#{path}/:#{nested_param}"
1129
+ end
1130
+
1131
+ def shallow=(value)
1132
+ @shallow = value
925
1133
  end
926
1134
 
1135
+ def shallow?
1136
+ @shallow
1137
+ end
927
1138
  end
928
1139
 
929
1140
  class SingletonResource < Resource #:nodoc:
@@ -963,18 +1174,18 @@ module ActionDispatch
963
1174
  # a singular resource to map /profile (rather than /profile/:id) to
964
1175
  # the show action:
965
1176
  #
966
- # resource :geocoder
1177
+ # resource :profile
967
1178
  #
968
1179
  # creates six different routes in your application, all mapping to
969
- # the +GeoCoders+ controller (note that the controller is named after
1180
+ # the +Profiles+ controller (note that the controller is named after
970
1181
  # the plural):
971
1182
  #
972
- # GET /geocoder/new
973
- # POST /geocoder
974
- # GET /geocoder
975
- # GET /geocoder/edit
976
- # PUT /geocoder
977
- # DELETE /geocoder
1183
+ # GET /profile/new
1184
+ # POST /profile
1185
+ # GET /profile
1186
+ # GET /profile/edit
1187
+ # PATCH/PUT /profile
1188
+ # DELETE /profile
978
1189
  #
979
1190
  # === Options
980
1191
  # Takes same options as +resources+.
@@ -988,6 +1199,8 @@ module ActionDispatch
988
1199
  resource_scope(:resource, SingletonResource.new(resources.pop, options)) do
989
1200
  yield if block_given?
990
1201
 
1202
+ concerns(options[:concerns]) if options[:concerns]
1203
+
991
1204
  collection do
992
1205
  post :create
993
1206
  end if parent_resource.actions.include?(:create)
@@ -996,12 +1209,7 @@ module ActionDispatch
996
1209
  get :new
997
1210
  end if parent_resource.actions.include?(:new)
998
1211
 
999
- member do
1000
- get :edit if parent_resource.actions.include?(:edit)
1001
- get :show if parent_resource.actions.include?(:show)
1002
- put :update if parent_resource.actions.include?(:update)
1003
- delete :destroy if parent_resource.actions.include?(:destroy)
1004
- end
1212
+ set_member_mappings_for_resource
1005
1213
  end
1006
1214
 
1007
1215
  self
@@ -1017,13 +1225,13 @@ module ActionDispatch
1017
1225
  # creates seven different routes in your application, all mapping to
1018
1226
  # the +Photos+ controller:
1019
1227
  #
1020
- # GET /photos
1021
- # GET /photos/new
1022
- # POST /photos
1023
- # GET /photos/:id
1024
- # GET /photos/:id/edit
1025
- # PUT /photos/:id
1026
- # DELETE /photos/:id
1228
+ # GET /photos
1229
+ # GET /photos/new
1230
+ # POST /photos
1231
+ # GET /photos/:id
1232
+ # GET /photos/:id/edit
1233
+ # PATCH/PUT /photos/:id
1234
+ # DELETE /photos/:id
1027
1235
  #
1028
1236
  # Resources can also be nested infinitely by using this block syntax:
1029
1237
  #
@@ -1033,13 +1241,13 @@ module ActionDispatch
1033
1241
  #
1034
1242
  # This generates the following comments routes:
1035
1243
  #
1036
- # GET /photos/:photo_id/comments
1037
- # GET /photos/:photo_id/comments/new
1038
- # POST /photos/:photo_id/comments
1039
- # GET /photos/:photo_id/comments/:id
1040
- # GET /photos/:photo_id/comments/:id/edit
1041
- # PUT /photos/:photo_id/comments/:id
1042
- # DELETE /photos/:photo_id/comments/:id
1244
+ # GET /photos/:photo_id/comments
1245
+ # GET /photos/:photo_id/comments/new
1246
+ # POST /photos/:photo_id/comments
1247
+ # GET /photos/:photo_id/comments/:id
1248
+ # GET /photos/:photo_id/comments/:id/edit
1249
+ # PATCH/PUT /photos/:photo_id/comments/:id
1250
+ # DELETE /photos/:photo_id/comments/:id
1043
1251
  #
1044
1252
  # === Options
1045
1253
  # Takes same options as <tt>Base#match</tt> as well as:
@@ -1048,43 +1256,43 @@ module ActionDispatch
1048
1256
  # Allows you to change the segment component of the +edit+ and +new+ actions.
1049
1257
  # Actions not specified are not changed.
1050
1258
  #
1051
- # resources :posts, :path_names => { :new => "brand_new" }
1259
+ # resources :posts, path_names: { new: "brand_new" }
1052
1260
  #
1053
1261
  # The above example will now change /posts/new to /posts/brand_new
1054
1262
  #
1055
1263
  # [:path]
1056
1264
  # Allows you to change the path prefix for the resource.
1057
1265
  #
1058
- # resources :posts, :path => 'postings'
1266
+ # resources :posts, path: 'postings'
1059
1267
  #
1060
1268
  # The resource and all segments will now route to /postings instead of /posts
1061
1269
  #
1062
1270
  # [:only]
1063
1271
  # Only generate routes for the given actions.
1064
1272
  #
1065
- # resources :cows, :only => :show
1066
- # resources :cows, :only => [:show, :index]
1273
+ # resources :cows, only: :show
1274
+ # resources :cows, only: [:show, :index]
1067
1275
  #
1068
1276
  # [:except]
1069
1277
  # Generate all routes except for the given actions.
1070
1278
  #
1071
- # resources :cows, :except => :show
1072
- # resources :cows, :except => [:show, :index]
1279
+ # resources :cows, except: :show
1280
+ # resources :cows, except: [:show, :index]
1073
1281
  #
1074
1282
  # [:shallow]
1075
1283
  # Generates shallow routes for nested resource(s). When placed on a parent resource,
1076
1284
  # generates shallow routes for all nested resources.
1077
1285
  #
1078
- # resources :posts, :shallow => true do
1286
+ # resources :posts, shallow: true do
1079
1287
  # resources :comments
1080
1288
  # end
1081
1289
  #
1082
1290
  # Is the same as:
1083
1291
  #
1084
1292
  # resources :posts do
1085
- # resources :comments, :except => [:show, :edit, :update, :destroy]
1293
+ # resources :comments, except: [:show, :edit, :update, :destroy]
1086
1294
  # end
1087
- # resources :comments, :only => [:show, :edit, :update, :destroy]
1295
+ # resources :comments, only: [:show, :edit, :update, :destroy]
1088
1296
  #
1089
1297
  # This allows URLs for resources that otherwise would be deeply nested such
1090
1298
  # as a comment on a blog post like <tt>/posts/a-long-permalink/comments/1234</tt>
@@ -1093,29 +1301,52 @@ module ActionDispatch
1093
1301
  # [:shallow_path]
1094
1302
  # Prefixes nested shallow routes with the specified path.
1095
1303
  #
1096
- # scope :shallow_path => "sekret" do
1304
+ # scope shallow_path: "sekret" do
1097
1305
  # resources :posts do
1098
- # resources :comments, :shallow => true
1306
+ # resources :comments, shallow: true
1099
1307
  # end
1100
1308
  # end
1101
1309
  #
1102
1310
  # The +comments+ resource here will have the following routes generated for it:
1103
1311
  #
1104
- # post_comments GET /posts/:post_id/comments(.:format)
1105
- # post_comments POST /posts/:post_id/comments(.:format)
1106
- # new_post_comment GET /posts/:post_id/comments/new(.:format)
1107
- # edit_comment GET /sekret/comments/:id/edit(.:format)
1108
- # comment GET /sekret/comments/:id(.:format)
1109
- # comment PUT /sekret/comments/:id(.:format)
1110
- # comment DELETE /sekret/comments/:id(.:format)
1312
+ # post_comments GET /posts/:post_id/comments(.:format)
1313
+ # post_comments POST /posts/:post_id/comments(.:format)
1314
+ # new_post_comment GET /posts/:post_id/comments/new(.:format)
1315
+ # edit_comment GET /sekret/comments/:id/edit(.:format)
1316
+ # comment GET /sekret/comments/:id(.:format)
1317
+ # comment PATCH/PUT /sekret/comments/:id(.:format)
1318
+ # comment DELETE /sekret/comments/:id(.:format)
1319
+ #
1320
+ # [:shallow_prefix]
1321
+ # Prefixes nested shallow route names with specified prefix.
1322
+ #
1323
+ # scope shallow_prefix: "sekret" do
1324
+ # resources :posts do
1325
+ # resources :comments, shallow: true
1326
+ # end
1327
+ # end
1328
+ #
1329
+ # The +comments+ resource here will have the following routes generated for it:
1330
+ #
1331
+ # post_comments GET /posts/:post_id/comments(.:format)
1332
+ # post_comments POST /posts/:post_id/comments(.:format)
1333
+ # new_post_comment GET /posts/:post_id/comments/new(.:format)
1334
+ # edit_sekret_comment GET /comments/:id/edit(.:format)
1335
+ # sekret_comment GET /comments/:id(.:format)
1336
+ # sekret_comment PATCH/PUT /comments/:id(.:format)
1337
+ # sekret_comment DELETE /comments/:id(.:format)
1338
+ #
1339
+ # [:format]
1340
+ # Allows you to specify the default value for optional +format+
1341
+ # segment or disable it by supplying +false+.
1111
1342
  #
1112
1343
  # === Examples
1113
1344
  #
1114
1345
  # # routes call <tt>Admin::PostsController</tt>
1115
- # resources :posts, :module => "admin"
1346
+ # resources :posts, module: "admin"
1116
1347
  #
1117
1348
  # # resource actions are at /admin/posts.
1118
- # resources :posts, :path => "admin/posts"
1349
+ # resources :posts, path: "admin/posts"
1119
1350
  def resources(*resources, &block)
1120
1351
  options = resources.extract_options!.dup
1121
1352
 
@@ -1126,6 +1357,8 @@ module ActionDispatch
1126
1357
  resource_scope(:resources, Resource.new(resources.pop, options)) do
1127
1358
  yield if block_given?
1128
1359
 
1360
+ concerns(options[:concerns]) if options[:concerns]
1361
+
1129
1362
  collection do
1130
1363
  get :index if parent_resource.actions.include?(:index)
1131
1364
  post :create if parent_resource.actions.include?(:create)
@@ -1135,12 +1368,7 @@ module ActionDispatch
1135
1368
  get :new
1136
1369
  end if parent_resource.actions.include?(:new)
1137
1370
 
1138
- member do
1139
- get :edit if parent_resource.actions.include?(:edit)
1140
- get :show if parent_resource.actions.include?(:show)
1141
- put :update if parent_resource.actions.include?(:update)
1142
- delete :destroy if parent_resource.actions.include?(:destroy)
1143
- end
1371
+ set_member_mappings_for_resource
1144
1372
  end
1145
1373
 
1146
1374
  self
@@ -1187,8 +1415,10 @@ module ActionDispatch
1187
1415
  end
1188
1416
 
1189
1417
  with_scope_level(:member) do
1190
- scope(parent_resource.member_scope) do
1191
- yield
1418
+ if shallow?
1419
+ shallow_scope(parent_resource.member_scope) { yield }
1420
+ else
1421
+ scope(parent_resource.member_scope) { yield }
1192
1422
  end
1193
1423
  end
1194
1424
  end
@@ -1211,16 +1441,8 @@ module ActionDispatch
1211
1441
  end
1212
1442
 
1213
1443
  with_scope_level(:nested) do
1214
- if shallow?
1215
- with_exclusive_scope do
1216
- if @scope[:shallow_path].blank?
1217
- scope(parent_resource.nested_scope, nested_options) { yield }
1218
- else
1219
- scope(@scope[:shallow_path], :as => @scope[:shallow_prefix]) do
1220
- scope(parent_resource.nested_scope, nested_options) { yield }
1221
- end
1222
- end
1223
- end
1444
+ if shallow? && shallow_nesting_depth >= 1
1445
+ shallow_scope(parent_resource.nested_scope, nested_options) { yield }
1224
1446
  else
1225
1447
  scope(parent_resource.nested_scope, nested_options) { yield }
1226
1448
  end
@@ -1237,7 +1459,7 @@ module ActionDispatch
1237
1459
  end
1238
1460
 
1239
1461
  def shallow
1240
- scope(:shallow => true, :shallow_path => @scope[:path]) do
1462
+ scope(:shallow => true) do
1241
1463
  yield
1242
1464
  end
1243
1465
  end
@@ -1246,11 +1468,27 @@ module ActionDispatch
1246
1468
  parent_resource.instance_of?(Resource) && @scope[:shallow]
1247
1469
  end
1248
1470
 
1471
+ # match 'path' => 'controller#action'
1472
+ # match 'path', to: 'controller#action'
1473
+ # match 'path', 'otherpath', on: :member, via: :get
1249
1474
  def match(path, *rest)
1250
1475
  if rest.empty? && Hash === path
1251
1476
  options = path
1252
- path, to = options.find { |name, value| name.is_a?(String) }
1253
- options[:to] = to
1477
+ path, to = options.find { |name, _value| name.is_a?(String) }
1478
+
1479
+ case to
1480
+ when Symbol
1481
+ options[:action] = to
1482
+ when String
1483
+ if to =~ /#/
1484
+ options[:to] = to
1485
+ else
1486
+ options[:controller] = to
1487
+ end
1488
+ else
1489
+ options[:to] = to
1490
+ end
1491
+
1254
1492
  options.delete(path)
1255
1493
  paths = [path]
1256
1494
  else
@@ -1258,34 +1496,40 @@ module ActionDispatch
1258
1496
  paths = [path] + rest
1259
1497
  end
1260
1498
 
1499
+ options[:anchor] = true unless options.key?(:anchor)
1500
+
1501
+ if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
1502
+ raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
1503
+ end
1504
+
1261
1505
  if @scope[:controller] && @scope[:action]
1262
1506
  options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
1263
1507
  end
1264
1508
 
1265
- path_without_format = path.to_s.sub(/\(\.:format\)$/, '')
1266
- if using_match_shorthand?(path_without_format, options)
1267
- options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
1268
- end
1509
+ paths.each do |_path|
1510
+ route_options = options.dup
1511
+ route_options[:path] ||= _path if _path.is_a?(String)
1269
1512
 
1270
- options[:anchor] = true unless options.key?(:anchor)
1513
+ path_without_format = _path.to_s.sub(/\(\.:format\)$/, '')
1514
+ if using_match_shorthand?(path_without_format, route_options)
1515
+ route_options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
1516
+ route_options[:to].tr!("-", "_")
1517
+ end
1271
1518
 
1272
- if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
1273
- raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
1519
+ decomposed_match(_path, route_options)
1274
1520
  end
1275
-
1276
- paths.each { |_path| decomposed_match(_path, options.dup) }
1277
1521
  self
1278
1522
  end
1279
1523
 
1280
1524
  def using_match_shorthand?(path, options)
1281
- path && (options[:to] || options[:action]).nil? && path =~ %r{/[\w/]+$}
1525
+ path && (options[:to] || options[:action]).nil? && path =~ %r{^/?[-\w]+/[-\w/]+$}
1282
1526
  end
1283
1527
 
1284
1528
  def decomposed_match(path, options) # :nodoc:
1285
1529
  if on = options.delete(:on)
1286
1530
  send(on) { decomposed_match(path, options) }
1287
1531
  else
1288
- case @scope[:scope_level]
1532
+ case @scope.scope_level
1289
1533
  when :resources
1290
1534
  nested { decomposed_match(path, options) }
1291
1535
  when :resource
@@ -1298,27 +1542,37 @@ module ActionDispatch
1298
1542
 
1299
1543
  def add_route(action, options) # :nodoc:
1300
1544
  path = path_for_action(action, options.delete(:path))
1545
+ raise ArgumentError, "path is required" if path.blank?
1546
+
1301
1547
  action = action.to_s.dup
1302
1548
 
1303
- if action =~ /^[\w\/]+$/
1304
- options[:action] ||= action unless action.include?("/")
1549
+ if action =~ /^[\w\-\/]+$/
1550
+ options[:action] ||= action.tr('-', '_') unless action.include?("/")
1305
1551
  else
1306
1552
  action = nil
1307
1553
  end
1308
1554
 
1309
- if !options.fetch(:as, true)
1310
- options.delete(:as)
1311
- else
1312
- options[:as] = name_for_action(options[:as], action)
1313
- end
1555
+ as = if !options.fetch(:as, true) # if it's set to nil or false
1556
+ options.delete(:as)
1557
+ else
1558
+ name_for_action(options.delete(:as), action)
1559
+ end
1314
1560
 
1315
- mapping = Mapping.new(@set, @scope, path, options)
1561
+ mapping = Mapping.build(@scope, @set, URI.parser.escape(path), as, options)
1316
1562
  app, conditions, requirements, defaults, as, anchor = mapping.to_route
1317
1563
  @set.add_route(app, conditions, requirements, defaults, as, anchor)
1318
1564
  end
1319
1565
 
1320
- def root(options={})
1321
- if @scope[:scope_level] == :resources
1566
+ def root(path, options={})
1567
+ if path.is_a?(String)
1568
+ options[:to] = path
1569
+ elsif path.is_a?(Hash) and options.empty?
1570
+ options = path
1571
+ else
1572
+ raise ArgumentError, "must be called with a path and/or options"
1573
+ end
1574
+
1575
+ if @scope.resources?
1322
1576
  with_scope_level(:root) do
1323
1577
  scope(parent_resource.path) do
1324
1578
  super(options)
@@ -1341,6 +1595,13 @@ module ActionDispatch
1341
1595
  return true
1342
1596
  end
1343
1597
 
1598
+ if options.delete(:shallow)
1599
+ shallow do
1600
+ send(method, resources.pop, options, &block)
1601
+ end
1602
+ return true
1603
+ end
1604
+
1344
1605
  if resource_scope?
1345
1606
  nested { send(method, resources.pop, options, &block) }
1346
1607
  return true
@@ -1378,96 +1639,115 @@ module ActionDispatch
1378
1639
  end
1379
1640
 
1380
1641
  def resource_scope? #:nodoc:
1381
- [:resource, :resources].include? @scope[:scope_level]
1642
+ @scope.resource_scope?
1382
1643
  end
1383
1644
 
1384
1645
  def resource_method_scope? #:nodoc:
1385
- [:collection, :member, :new].include? @scope[:scope_level]
1646
+ @scope.resource_method_scope?
1647
+ end
1648
+
1649
+ def nested_scope? #:nodoc:
1650
+ @scope.nested?
1386
1651
  end
1387
1652
 
1388
1653
  def with_exclusive_scope
1389
1654
  begin
1390
- old_name_prefix, old_path = @scope[:as], @scope[:path]
1391
- @scope[:as], @scope[:path] = nil, nil
1655
+ @scope = @scope.new(:as => nil, :path => nil)
1392
1656
 
1393
1657
  with_scope_level(:exclusive) do
1394
1658
  yield
1395
1659
  end
1396
1660
  ensure
1397
- @scope[:as], @scope[:path] = old_name_prefix, old_path
1661
+ @scope = @scope.parent
1398
1662
  end
1399
1663
  end
1400
1664
 
1401
- def with_scope_level(kind, resource = parent_resource)
1402
- old, @scope[:scope_level] = @scope[:scope_level], kind
1403
- old_resource, @scope[:scope_level_resource] = @scope[:scope_level_resource], resource
1665
+ def with_scope_level(kind)
1666
+ @scope = @scope.new_level(kind)
1404
1667
  yield
1405
1668
  ensure
1406
- @scope[:scope_level] = old
1407
- @scope[:scope_level_resource] = old_resource
1669
+ @scope = @scope.parent
1408
1670
  end
1409
1671
 
1410
1672
  def resource_scope(kind, resource) #:nodoc:
1411
- with_scope_level(kind, resource) do
1412
- scope(parent_resource.resource_scope) do
1413
- yield
1414
- end
1673
+ resource.shallow = @scope[:shallow]
1674
+ @scope = @scope.new(:scope_level_resource => resource)
1675
+ @nesting.push(resource)
1676
+
1677
+ with_scope_level(kind) do
1678
+ scope(parent_resource.resource_scope) { yield }
1415
1679
  end
1680
+ ensure
1681
+ @nesting.pop
1682
+ @scope = @scope.parent
1416
1683
  end
1417
1684
 
1418
1685
  def nested_options #:nodoc:
1419
1686
  options = { :as => parent_resource.member_name }
1420
1687
  options[:constraints] = {
1421
- :"#{parent_resource.singular}_id" => id_constraint
1422
- } if id_constraint?
1688
+ parent_resource.nested_param => param_constraint
1689
+ } if param_constraint?
1423
1690
 
1424
1691
  options
1425
1692
  end
1426
1693
 
1427
- def id_constraint? #:nodoc:
1428
- @scope[:constraints] && @scope[:constraints][:id].is_a?(Regexp)
1694
+ def nesting_depth #:nodoc:
1695
+ @nesting.size
1429
1696
  end
1430
1697
 
1431
- def id_constraint #:nodoc:
1432
- @scope[:constraints][:id]
1698
+ def shallow_nesting_depth #:nodoc:
1699
+ @nesting.select(&:shallow?).size
1433
1700
  end
1434
1701
 
1435
- def canonical_action?(action, flag) #:nodoc:
1436
- flag && resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
1702
+ def param_constraint? #:nodoc:
1703
+ @scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp)
1437
1704
  end
1438
1705
 
1439
- def shallow_scoping? #:nodoc:
1440
- shallow? && @scope[:scope_level] == :member
1706
+ def param_constraint #:nodoc:
1707
+ @scope[:constraints][parent_resource.param]
1441
1708
  end
1442
1709
 
1443
- def path_for_action(action, path) #:nodoc:
1444
- prefix = shallow_scoping? ?
1445
- "#{@scope[:shallow_path]}/#{parent_resource.path}/:id" : @scope[:path]
1710
+ def canonical_action?(action) #:nodoc:
1711
+ resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
1712
+ end
1713
+
1714
+ def shallow_scope(path, options = {}) #:nodoc:
1715
+ scope = { :as => @scope[:shallow_prefix],
1716
+ :path => @scope[:shallow_path] }
1717
+ @scope = @scope.new scope
1446
1718
 
1447
- path = if canonical_action?(action, path.blank?)
1448
- prefix.to_s
1719
+ scope(path, options) { yield }
1720
+ ensure
1721
+ @scope = @scope.parent
1722
+ end
1723
+
1724
+ def path_for_action(action, path) #:nodoc:
1725
+ if path.blank? && canonical_action?(action)
1726
+ @scope[:path].to_s
1449
1727
  else
1450
- "#{prefix}/#{action_path(action, path)}"
1728
+ "#{@scope[:path]}/#{action_path(action, path)}"
1451
1729
  end
1452
1730
  end
1453
1731
 
1454
1732
  def action_path(name, path = nil) #:nodoc:
1455
- # Ruby 1.8 can't transform empty strings to symbols
1456
- name = name.to_sym if name.is_a?(String) && !name.empty?
1733
+ name = name.to_sym if name.is_a?(String)
1457
1734
  path || @scope[:path_names][name] || name.to_s
1458
1735
  end
1459
1736
 
1460
1737
  def prefix_name_for_action(as, action) #:nodoc:
1461
1738
  if as
1462
- as.to_s
1463
- elsif !canonical_action?(action, @scope[:scope_level])
1464
- action.to_s
1739
+ prefix = as
1740
+ elsif !canonical_action?(action)
1741
+ prefix = action
1742
+ end
1743
+
1744
+ if prefix && prefix != '/' && !prefix.empty?
1745
+ Mapper.normalize_name prefix.to_s.tr('-', '_')
1465
1746
  end
1466
1747
  end
1467
1748
 
1468
1749
  def name_for_action(as, action) #:nodoc:
1469
1750
  prefix = prefix_name_for_action(as, action)
1470
- prefix = Mapper.normalize_name(prefix) if prefix
1471
1751
  name_prefix = @scope[:as]
1472
1752
 
1473
1753
  if parent_resource
@@ -1477,43 +1757,224 @@ module ActionDispatch
1477
1757
  member_name = parent_resource.member_name
1478
1758
  end
1479
1759
 
1480
- name = case @scope[:scope_level]
1481
- when :nested
1482
- [name_prefix, prefix]
1483
- when :collection
1484
- [prefix, name_prefix, collection_name]
1485
- when :new
1486
- [prefix, :new, name_prefix, member_name]
1487
- when :member
1488
- [prefix, shallow_scoping? ? @scope[:shallow_prefix] : name_prefix, member_name]
1489
- when :root
1490
- [name_prefix, collection_name, prefix]
1491
- else
1492
- [name_prefix, member_name, prefix]
1493
- end
1760
+ name = @scope.action_name(name_prefix, prefix, collection_name, member_name)
1494
1761
 
1495
- if candidate = name.select(&:present?).join("_").presence
1762
+ if candidate = name.compact.join("_").presence
1496
1763
  # If a name was not explicitly given, we check if it is valid
1497
1764
  # and return nil in case it isn't. Otherwise, we pass the invalid name
1498
1765
  # forward so the underlying router engine treats it and raises an exception.
1499
1766
  if as.nil?
1500
- candidate unless @set.routes.find { |r| r.name == candidate } || candidate !~ /\A[_a-z]/i
1767
+ candidate unless candidate !~ /\A[_a-z]/i || @set.named_routes.key?(candidate)
1501
1768
  else
1502
1769
  candidate
1503
1770
  end
1504
1771
  end
1505
1772
  end
1773
+
1774
+ def set_member_mappings_for_resource
1775
+ member do
1776
+ get :edit if parent_resource.actions.include?(:edit)
1777
+ get :show if parent_resource.actions.include?(:show)
1778
+ if parent_resource.actions.include?(:update)
1779
+ patch :update
1780
+ put :update
1781
+ end
1782
+ delete :destroy if parent_resource.actions.include?(:destroy)
1783
+ end
1784
+ end
1785
+ end
1786
+
1787
+ # Routing Concerns allow you to declare common routes that can be reused
1788
+ # inside others resources and routes.
1789
+ #
1790
+ # concern :commentable do
1791
+ # resources :comments
1792
+ # end
1793
+ #
1794
+ # concern :image_attachable do
1795
+ # resources :images, only: :index
1796
+ # end
1797
+ #
1798
+ # These concerns are used in Resources routing:
1799
+ #
1800
+ # resources :messages, concerns: [:commentable, :image_attachable]
1801
+ #
1802
+ # or in a scope or namespace:
1803
+ #
1804
+ # namespace :posts do
1805
+ # concerns :commentable
1806
+ # end
1807
+ module Concerns
1808
+ # Define a routing concern using a name.
1809
+ #
1810
+ # Concerns may be defined inline, using a block, or handled by
1811
+ # another object, by passing that object as the second parameter.
1812
+ #
1813
+ # The concern object, if supplied, should respond to <tt>call</tt>,
1814
+ # which will receive two parameters:
1815
+ #
1816
+ # * The current mapper
1817
+ # * A hash of options which the concern object may use
1818
+ #
1819
+ # Options may also be used by concerns defined in a block by accepting
1820
+ # a block parameter. So, using a block, you might do something as
1821
+ # simple as limit the actions available on certain resources, passing
1822
+ # standard resource options through the concern:
1823
+ #
1824
+ # concern :commentable do |options|
1825
+ # resources :comments, options
1826
+ # end
1827
+ #
1828
+ # resources :posts, concerns: :commentable
1829
+ # resources :archived_posts do
1830
+ # # Don't allow comments on archived posts
1831
+ # concerns :commentable, only: [:index, :show]
1832
+ # end
1833
+ #
1834
+ # Or, using a callable object, you might implement something more
1835
+ # specific to your application, which would be out of place in your
1836
+ # routes file.
1837
+ #
1838
+ # # purchasable.rb
1839
+ # class Purchasable
1840
+ # def initialize(defaults = {})
1841
+ # @defaults = defaults
1842
+ # end
1843
+ #
1844
+ # def call(mapper, options = {})
1845
+ # options = @defaults.merge(options)
1846
+ # mapper.resources :purchases
1847
+ # mapper.resources :receipts
1848
+ # mapper.resources :returns if options[:returnable]
1849
+ # end
1850
+ # end
1851
+ #
1852
+ # # routes.rb
1853
+ # concern :purchasable, Purchasable.new(returnable: true)
1854
+ #
1855
+ # resources :toys, concerns: :purchasable
1856
+ # resources :electronics, concerns: :purchasable
1857
+ # resources :pets do
1858
+ # concerns :purchasable, returnable: false
1859
+ # end
1860
+ #
1861
+ # Any routing helpers can be used inside a concern. If using a
1862
+ # callable, they're accessible from the Mapper that's passed to
1863
+ # <tt>call</tt>.
1864
+ def concern(name, callable = nil, &block)
1865
+ callable ||= lambda { |mapper, options| mapper.instance_exec(options, &block) }
1866
+ @concerns[name] = callable
1867
+ end
1868
+
1869
+ # Use the named concerns
1870
+ #
1871
+ # resources :posts do
1872
+ # concerns :commentable
1873
+ # end
1874
+ #
1875
+ # concerns also work in any routes helper that you want to use:
1876
+ #
1877
+ # namespace :posts do
1878
+ # concerns :commentable
1879
+ # end
1880
+ def concerns(*args)
1881
+ options = args.extract_options!
1882
+ args.flatten.each do |name|
1883
+ if concern = @concerns[name]
1884
+ concern.call(self, options)
1885
+ else
1886
+ raise ArgumentError, "No concern named #{name} was found!"
1887
+ end
1888
+ end
1889
+ end
1890
+ end
1891
+
1892
+ class Scope # :nodoc:
1893
+ OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
1894
+ :controller, :action, :path_names, :constraints,
1895
+ :shallow, :blocks, :defaults, :options]
1896
+
1897
+ RESOURCE_SCOPES = [:resource, :resources]
1898
+ RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
1899
+
1900
+ attr_reader :parent, :scope_level
1901
+
1902
+ def initialize(hash, parent = {}, scope_level = nil)
1903
+ @hash = hash
1904
+ @parent = parent
1905
+ @scope_level = scope_level
1906
+ end
1907
+
1908
+ def nested?
1909
+ scope_level == :nested
1910
+ end
1911
+
1912
+ def resources?
1913
+ scope_level == :resources
1914
+ end
1915
+
1916
+ def resource_method_scope?
1917
+ RESOURCE_METHOD_SCOPES.include? scope_level
1918
+ end
1919
+
1920
+ def action_name(name_prefix, prefix, collection_name, member_name)
1921
+ case scope_level
1922
+ when :nested
1923
+ [name_prefix, prefix]
1924
+ when :collection
1925
+ [prefix, name_prefix, collection_name]
1926
+ when :new
1927
+ [prefix, :new, name_prefix, member_name]
1928
+ when :member
1929
+ [prefix, name_prefix, member_name]
1930
+ when :root
1931
+ [name_prefix, collection_name, prefix]
1932
+ else
1933
+ [name_prefix, member_name, prefix]
1934
+ end
1935
+ end
1936
+
1937
+ def resource_scope?
1938
+ RESOURCE_SCOPES.include? scope_level
1939
+ end
1940
+
1941
+ def options
1942
+ OPTIONS
1943
+ end
1944
+
1945
+ def new(hash)
1946
+ self.class.new hash, self, scope_level
1947
+ end
1948
+
1949
+ def new_level(level)
1950
+ self.class.new(self, self, level)
1951
+ end
1952
+
1953
+ def fetch(key, &block)
1954
+ @hash.fetch(key, &block)
1955
+ end
1956
+
1957
+ def [](key)
1958
+ @hash.fetch(key) { @parent[key] }
1959
+ end
1960
+
1961
+ def []=(k,v)
1962
+ @hash[k] = v
1963
+ end
1506
1964
  end
1507
1965
 
1508
1966
  def initialize(set) #:nodoc:
1509
1967
  @set = set
1510
- @scope = { :path_names => @set.resources_path_names }
1968
+ @scope = Scope.new({ :path_names => @set.resources_path_names })
1969
+ @concerns = {}
1970
+ @nesting = []
1511
1971
  end
1512
1972
 
1513
1973
  include Base
1514
1974
  include HttpHelpers
1515
1975
  include Redirection
1516
1976
  include Scoping
1977
+ include Concerns
1517
1978
  include Resources
1518
1979
  end
1519
1980
  end