actionpack 3.2.22.5 → 4.0.0.beta1

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 (265) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +641 -418
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -288
  5. data/lib/abstract_controller.rb +1 -8
  6. data/lib/abstract_controller/asset_paths.rb +2 -2
  7. data/lib/abstract_controller/base.rb +39 -37
  8. data/lib/abstract_controller/callbacks.rb +101 -82
  9. data/lib/abstract_controller/collector.rb +7 -3
  10. data/lib/abstract_controller/helpers.rb +23 -11
  11. data/lib/abstract_controller/layouts.rb +68 -73
  12. data/lib/abstract_controller/logger.rb +1 -2
  13. data/lib/abstract_controller/rendering.rb +22 -13
  14. data/lib/abstract_controller/translation.rb +16 -1
  15. data/lib/abstract_controller/url_for.rb +6 -6
  16. data/lib/abstract_controller/view_paths.rb +1 -1
  17. data/lib/action_controller.rb +15 -6
  18. data/lib/action_controller/base.rb +46 -22
  19. data/lib/action_controller/caching.rb +46 -33
  20. data/lib/action_controller/caching/fragments.rb +23 -53
  21. data/lib/action_controller/deprecated.rb +5 -1
  22. data/lib/action_controller/deprecated/integration_test.rb +3 -0
  23. data/lib/action_controller/log_subscriber.rb +11 -8
  24. data/lib/action_controller/metal.rb +16 -30
  25. data/lib/action_controller/metal/conditional_get.rb +76 -32
  26. data/lib/action_controller/metal/data_streaming.rb +20 -26
  27. data/lib/action_controller/metal/exceptions.rb +19 -6
  28. data/lib/action_controller/metal/flash.rb +24 -9
  29. data/lib/action_controller/metal/force_ssl.rb +32 -9
  30. data/lib/action_controller/metal/head.rb +25 -4
  31. data/lib/action_controller/metal/helpers.rb +6 -9
  32. data/lib/action_controller/metal/hide_actions.rb +1 -2
  33. data/lib/action_controller/metal/http_authentication.rb +105 -87
  34. data/lib/action_controller/metal/implicit_render.rb +1 -1
  35. data/lib/action_controller/metal/instrumentation.rb +2 -1
  36. data/lib/action_controller/metal/live.rb +141 -0
  37. data/lib/action_controller/metal/mime_responds.rb +161 -47
  38. data/lib/action_controller/metal/params_wrapper.rb +112 -74
  39. data/lib/action_controller/metal/rack_delegation.rb +9 -3
  40. data/lib/action_controller/metal/redirecting.rb +15 -20
  41. data/lib/action_controller/metal/renderers.rb +11 -9
  42. data/lib/action_controller/metal/rendering.rb +8 -0
  43. data/lib/action_controller/metal/request_forgery_protection.rb +112 -19
  44. data/lib/action_controller/metal/responder.rb +20 -19
  45. data/lib/action_controller/metal/streaming.rb +12 -18
  46. data/lib/action_controller/metal/strong_parameters.rb +516 -0
  47. data/lib/action_controller/metal/testing.rb +13 -18
  48. data/lib/action_controller/metal/url_for.rb +27 -25
  49. data/lib/action_controller/model_naming.rb +12 -0
  50. data/lib/action_controller/railtie.rb +33 -17
  51. data/lib/action_controller/railties/helpers.rb +22 -0
  52. data/lib/action_controller/record_identifier.rb +18 -72
  53. data/lib/action_controller/test_case.rb +215 -123
  54. data/lib/action_controller/vendor/html-scanner.rb +4 -19
  55. data/lib/action_dispatch.rb +27 -19
  56. data/lib/action_dispatch/http/cache.rb +63 -11
  57. data/lib/action_dispatch/http/filter_parameters.rb +18 -8
  58. data/lib/action_dispatch/http/filter_redirect.rb +37 -0
  59. data/lib/action_dispatch/http/headers.rb +27 -19
  60. data/lib/action_dispatch/http/mime_negotiation.rb +25 -2
  61. data/lib/action_dispatch/http/mime_type.rb +145 -113
  62. data/lib/action_dispatch/http/mime_types.rb +1 -1
  63. data/lib/action_dispatch/http/parameter_filter.rb +44 -46
  64. data/lib/action_dispatch/http/parameters.rb +12 -5
  65. data/lib/action_dispatch/http/rack_cache.rb +2 -3
  66. data/lib/action_dispatch/http/request.rb +49 -18
  67. data/lib/action_dispatch/http/response.rb +129 -35
  68. data/lib/action_dispatch/http/upload.rb +60 -17
  69. data/lib/action_dispatch/http/url.rb +53 -31
  70. data/lib/action_dispatch/journey.rb +5 -0
  71. data/lib/action_dispatch/journey/backwards.rb +5 -0
  72. data/lib/action_dispatch/journey/formatter.rb +146 -0
  73. data/lib/action_dispatch/journey/gtg/builder.rb +162 -0
  74. data/lib/action_dispatch/journey/gtg/simulator.rb +44 -0
  75. data/lib/action_dispatch/journey/gtg/transition_table.rb +156 -0
  76. data/lib/action_dispatch/journey/nfa/builder.rb +76 -0
  77. data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
  78. data/lib/action_dispatch/journey/nfa/simulator.rb +47 -0
  79. data/lib/action_dispatch/journey/nfa/transition_table.rb +163 -0
  80. data/lib/action_dispatch/journey/nodes/node.rb +124 -0
  81. data/lib/action_dispatch/journey/parser.rb +206 -0
  82. data/lib/action_dispatch/journey/parser.y +47 -0
  83. data/lib/action_dispatch/journey/parser_extras.rb +23 -0
  84. data/lib/action_dispatch/journey/path/pattern.rb +196 -0
  85. data/lib/action_dispatch/journey/route.rb +116 -0
  86. data/lib/action_dispatch/journey/router.rb +164 -0
  87. data/lib/action_dispatch/journey/router/strexp.rb +24 -0
  88. data/lib/action_dispatch/journey/router/utils.rb +54 -0
  89. data/lib/action_dispatch/journey/routes.rb +75 -0
  90. data/lib/action_dispatch/journey/scanner.rb +61 -0
  91. data/lib/action_dispatch/journey/visitors.rb +189 -0
  92. data/lib/action_dispatch/journey/visualizer/fsm.css +34 -0
  93. data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
  94. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  95. data/lib/action_dispatch/middleware/callbacks.rb +9 -4
  96. data/lib/action_dispatch/middleware/cookies.rb +168 -57
  97. data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -17
  98. data/lib/action_dispatch/middleware/exception_wrapper.rb +27 -3
  99. data/lib/action_dispatch/middleware/flash.rb +58 -58
  100. data/lib/action_dispatch/middleware/params_parser.rb +14 -29
  101. data/lib/action_dispatch/middleware/public_exceptions.rb +31 -14
  102. data/lib/action_dispatch/middleware/reloader.rb +6 -6
  103. data/lib/action_dispatch/middleware/remote_ip.rb +145 -39
  104. data/lib/action_dispatch/middleware/request_id.rb +2 -6
  105. data/lib/action_dispatch/middleware/session/abstract_store.rb +22 -20
  106. data/lib/action_dispatch/middleware/session/cache_store.rb +3 -3
  107. data/lib/action_dispatch/middleware/session/cookie_store.rb +81 -7
  108. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -3
  109. data/lib/action_dispatch/middleware/show_exceptions.rb +12 -45
  110. data/lib/action_dispatch/middleware/ssl.rb +70 -0
  111. data/lib/action_dispatch/middleware/stack.rb +6 -1
  112. data/lib/action_dispatch/middleware/static.rb +5 -24
  113. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +14 -11
  114. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +25 -0
  115. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +3 -3
  116. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +15 -9
  117. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +121 -5
  118. data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +7 -2
  119. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +30 -15
  120. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +39 -13
  121. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +6 -2
  122. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
  123. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +144 -0
  124. data/lib/action_dispatch/railtie.rb +16 -6
  125. data/lib/action_dispatch/request/session.rb +181 -0
  126. data/lib/action_dispatch/routing.rb +41 -40
  127. data/lib/action_dispatch/routing/inspector.rb +240 -0
  128. data/lib/action_dispatch/routing/mapper.rb +501 -273
  129. data/lib/action_dispatch/routing/polymorphic_routes.rb +16 -20
  130. data/lib/action_dispatch/routing/redirection.rb +46 -29
  131. data/lib/action_dispatch/routing/route_set.rb +203 -164
  132. data/lib/action_dispatch/routing/routes_proxy.rb +2 -0
  133. data/lib/action_dispatch/routing/url_for.rb +48 -33
  134. data/lib/action_dispatch/testing/assertions/dom.rb +3 -13
  135. data/lib/action_dispatch/testing/assertions/response.rb +32 -40
  136. data/lib/action_dispatch/testing/assertions/routing.rb +40 -39
  137. data/lib/action_dispatch/testing/assertions/selector.rb +15 -20
  138. data/lib/action_dispatch/testing/assertions/tag.rb +20 -23
  139. data/lib/action_dispatch/testing/integration.rb +41 -22
  140. data/lib/action_dispatch/testing/test_process.rb +9 -6
  141. data/lib/action_dispatch/testing/test_request.rb +7 -3
  142. data/lib/action_pack.rb +1 -1
  143. data/lib/action_pack/version.rb +4 -4
  144. data/lib/action_view.rb +17 -8
  145. data/lib/action_view/base.rb +15 -34
  146. data/lib/action_view/buffers.rb +1 -1
  147. data/lib/action_view/context.rb +4 -4
  148. data/lib/action_view/dependency_tracker.rb +91 -0
  149. data/lib/action_view/digestor.rb +85 -0
  150. data/lib/action_view/flows.rb +1 -4
  151. data/lib/action_view/helpers.rb +2 -4
  152. data/lib/action_view/helpers/active_model_helper.rb +3 -4
  153. data/lib/action_view/helpers/asset_tag_helper.rb +211 -353
  154. data/lib/action_view/helpers/asset_url_helper.rb +354 -0
  155. data/lib/action_view/helpers/atom_feed_helper.rb +13 -10
  156. data/lib/action_view/helpers/cache_helper.rb +150 -18
  157. data/lib/action_view/helpers/capture_helper.rb +42 -29
  158. data/lib/action_view/helpers/csrf_helper.rb +0 -2
  159. data/lib/action_view/helpers/date_helper.rb +268 -247
  160. data/lib/action_view/helpers/debug_helper.rb +10 -11
  161. data/lib/action_view/helpers/form_helper.rb +904 -547
  162. data/lib/action_view/helpers/form_options_helper.rb +341 -166
  163. data/lib/action_view/helpers/form_tag_helper.rb +188 -88
  164. data/lib/action_view/helpers/javascript_helper.rb +23 -16
  165. data/lib/action_view/helpers/number_helper.rb +148 -354
  166. data/lib/action_view/helpers/output_safety_helper.rb +3 -3
  167. data/lib/action_view/helpers/record_tag_helper.rb +17 -22
  168. data/lib/action_view/helpers/rendering_helper.rb +2 -4
  169. data/lib/action_view/helpers/sanitize_helper.rb +3 -6
  170. data/lib/action_view/helpers/tag_helper.rb +43 -37
  171. data/lib/action_view/helpers/tags.rb +39 -0
  172. data/lib/action_view/helpers/tags/base.rb +148 -0
  173. data/lib/action_view/helpers/tags/check_box.rb +64 -0
  174. data/lib/action_view/helpers/tags/checkable.rb +16 -0
  175. data/lib/action_view/helpers/tags/collection_check_boxes.rb +43 -0
  176. data/lib/action_view/helpers/tags/collection_helpers.rb +83 -0
  177. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +36 -0
  178. data/lib/action_view/helpers/tags/collection_select.rb +28 -0
  179. data/lib/action_view/helpers/tags/color_field.rb +25 -0
  180. data/lib/action_view/helpers/tags/date_field.rb +13 -0
  181. data/lib/action_view/helpers/tags/date_select.rb +72 -0
  182. data/lib/action_view/helpers/tags/datetime_field.rb +22 -0
  183. data/lib/action_view/helpers/tags/datetime_local_field.rb +19 -0
  184. data/lib/action_view/helpers/tags/datetime_select.rb +8 -0
  185. data/lib/action_view/helpers/tags/email_field.rb +8 -0
  186. data/lib/action_view/helpers/tags/file_field.rb +8 -0
  187. data/lib/action_view/helpers/tags/grouped_collection_select.rb +29 -0
  188. data/lib/action_view/helpers/tags/hidden_field.rb +8 -0
  189. data/lib/action_view/helpers/tags/label.rb +65 -0
  190. data/lib/action_view/helpers/tags/month_field.rb +13 -0
  191. data/lib/action_view/helpers/tags/number_field.rb +18 -0
  192. data/lib/action_view/helpers/tags/password_field.rb +12 -0
  193. data/lib/action_view/helpers/tags/radio_button.rb +31 -0
  194. data/lib/action_view/helpers/tags/range_field.rb +8 -0
  195. data/lib/action_view/helpers/tags/search_field.rb +24 -0
  196. data/lib/action_view/helpers/tags/select.rb +41 -0
  197. data/lib/action_view/helpers/tags/tel_field.rb +8 -0
  198. data/lib/action_view/helpers/tags/text_area.rb +18 -0
  199. data/lib/action_view/helpers/tags/text_field.rb +29 -0
  200. data/lib/action_view/helpers/tags/time_field.rb +13 -0
  201. data/lib/action_view/helpers/tags/time_select.rb +8 -0
  202. data/lib/action_view/helpers/tags/time_zone_select.rb +20 -0
  203. data/lib/action_view/helpers/tags/url_field.rb +8 -0
  204. data/lib/action_view/helpers/tags/week_field.rb +13 -0
  205. data/lib/action_view/helpers/text_helper.rb +126 -113
  206. data/lib/action_view/helpers/translation_helper.rb +32 -16
  207. data/lib/action_view/helpers/url_helper.rb +200 -271
  208. data/lib/action_view/locale/en.yml +1 -105
  209. data/lib/action_view/log_subscriber.rb +6 -4
  210. data/lib/action_view/lookup_context.rb +15 -39
  211. data/lib/action_view/model_naming.rb +12 -0
  212. data/lib/action_view/path_set.rb +9 -39
  213. data/lib/action_view/railtie.rb +6 -22
  214. data/lib/action_view/record_identifier.rb +84 -0
  215. data/lib/action_view/renderer/abstract_renderer.rb +10 -19
  216. data/lib/action_view/renderer/partial_renderer.rb +144 -81
  217. data/lib/action_view/renderer/renderer.rb +2 -19
  218. data/lib/action_view/renderer/streaming_template_renderer.rb +2 -5
  219. data/lib/action_view/renderer/template_renderer.rb +14 -13
  220. data/lib/action_view/routing_url_for.rb +107 -0
  221. data/lib/action_view/template.rb +22 -21
  222. data/lib/action_view/template/error.rb +22 -12
  223. data/lib/action_view/template/handlers.rb +12 -9
  224. data/lib/action_view/template/handlers/builder.rb +1 -1
  225. data/lib/action_view/template/handlers/erb.rb +11 -16
  226. data/lib/action_view/template/handlers/raw.rb +11 -0
  227. data/lib/action_view/template/resolver.rb +111 -83
  228. data/lib/action_view/template/text.rb +12 -8
  229. data/lib/action_view/template/types.rb +57 -0
  230. data/lib/action_view/test_case.rb +66 -43
  231. data/lib/action_view/testing/resolvers.rb +3 -2
  232. data/lib/action_view/vendor/html-scanner.rb +20 -0
  233. data/lib/{action_controller → action_view}/vendor/html-scanner/html/document.rb +0 -0
  234. data/lib/{action_controller → action_view}/vendor/html-scanner/html/node.rb +12 -12
  235. data/lib/{action_controller → action_view}/vendor/html-scanner/html/sanitizer.rb +18 -7
  236. data/lib/{action_controller → action_view}/vendor/html-scanner/html/selector.rb +1 -1
  237. data/lib/{action_controller → action_view}/vendor/html-scanner/html/tokenizer.rb +1 -1
  238. data/lib/{action_controller → action_view}/vendor/html-scanner/html/version.rb +0 -0
  239. metadata +135 -125
  240. data/lib/action_controller/caching/actions.rb +0 -185
  241. data/lib/action_controller/caching/pages.rb +0 -187
  242. data/lib/action_controller/caching/sweeping.rb +0 -97
  243. data/lib/action_controller/deprecated/performance_test.rb +0 -1
  244. data/lib/action_controller/metal/compatibility.rb +0 -65
  245. data/lib/action_controller/metal/session_management.rb +0 -14
  246. data/lib/action_controller/railties/paths.rb +0 -25
  247. data/lib/action_dispatch/middleware/best_standards_support.rb +0 -30
  248. data/lib/action_dispatch/middleware/body_proxy.rb +0 -30
  249. data/lib/action_dispatch/middleware/head.rb +0 -18
  250. data/lib/action_dispatch/middleware/rescue.rb +0 -26
  251. data/lib/action_dispatch/testing/performance_test.rb +0 -10
  252. data/lib/action_view/asset_paths.rb +0 -142
  253. data/lib/action_view/helpers/asset_paths.rb +0 -7
  254. data/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +0 -146
  255. data/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +0 -93
  256. data/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +0 -193
  257. data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +0 -148
  258. data/lib/sprockets/assets.rake +0 -99
  259. data/lib/sprockets/bootstrap.rb +0 -37
  260. data/lib/sprockets/compressors.rb +0 -83
  261. data/lib/sprockets/helpers.rb +0 -6
  262. data/lib/sprockets/helpers/isolated_helper.rb +0 -13
  263. data/lib/sprockets/helpers/rails_helper.rb +0 -182
  264. data/lib/sprockets/railtie.rb +0 -62
  265. data/lib/sprockets/static_compiler.rb +0 -56
@@ -1,3 +1,4 @@
1
+ # encoding: UTF-8
1
2
  require 'active_support/core_ext/object/to_param'
2
3
  require 'active_support/core_ext/regexp'
3
4
 
@@ -60,7 +61,7 @@ module ActionDispatch
60
61
  # directory by using +scope+. +scope+ takes additional options which
61
62
  # apply to all enclosed routes.
62
63
  #
63
- # scope :path => "/cpanel", :as => 'admin' do
64
+ # scope path: "/cpanel", as: 'admin' do
64
65
  # resources :posts, :comments
65
66
  # end
66
67
  #
@@ -77,22 +78,22 @@ module ActionDispatch
77
78
  # Example:
78
79
  #
79
80
  # # In routes.rb
80
- # match '/login' => 'accounts#login', :as => 'login'
81
+ # match '/login' => 'accounts#login', as: 'login'
81
82
  #
82
83
  # # With render, redirect_to, tests, etc.
83
84
  # redirect_to login_url
84
85
  #
85
86
  # Arguments can be passed as well.
86
87
  #
87
- # redirect_to show_item_path(:id => 25)
88
+ # redirect_to show_item_path(id: 25)
88
89
  #
89
90
  # Use <tt>root</tt> as a shorthand to name a route for the root path "/".
90
91
  #
91
92
  # # In routes.rb
92
- # root :to => 'blogs#index'
93
+ # root to: 'blogs#index'
93
94
  #
94
95
  # # would recognize http://www.example.com/ as
95
- # params = { :controller => 'blogs', :action => 'index' }
96
+ # params = { controller: 'blogs', action: 'index' }
96
97
  #
97
98
  # # and provide these named routes
98
99
  # root_url # => 'http://www.example.com/'
@@ -109,43 +110,43 @@ module ActionDispatch
109
110
  # end
110
111
  #
111
112
  # # provides named routes for show, delete, and edit
112
- # link_to @article.title, show_path(:id => @article.id)
113
+ # link_to @article.title, show_path(id: @article.id)
113
114
  #
114
115
  # == Pretty URLs
115
116
  #
116
117
  # Routes can generate pretty URLs. For example:
117
118
  #
118
- # match '/articles/:year/:month/:day' => 'articles#find_by_id', :constraints => {
119
- # :year => /\d{4}/,
120
- # :month => /\d{1,2}/,
121
- # :day => /\d{1,2}/
119
+ # match '/articles/:year/:month/:day' => 'articles#find_by_id', constraints: {
120
+ # year: /\d{4}/,
121
+ # month: /\d{1,2}/,
122
+ # day: /\d{1,2}/
122
123
  # }
123
124
  #
124
125
  # Using the route above, the URL "http://localhost:3000/articles/2005/11/06"
125
126
  # maps to
126
127
  #
127
- # params = {:year => '2005', :month => '11', :day => '06'}
128
+ # params = {year: '2005', month: '11', day: '06'}
128
129
  #
129
130
  # == Regular Expressions and parameters
130
131
  # You can specify a regular expression to define a format for a parameter.
131
132
  #
132
133
  # controller 'geocode' do
133
- # match 'geocode/:postalcode' => :show, :constraints => {
134
- # :postalcode => /\d{5}(-\d{4})?/
134
+ # match 'geocode/:postalcode' => :show, constraints: {
135
+ # postalcode: /\d{5}(-\d{4})?/
135
136
  # }
136
137
  #
137
138
  # Constraints can include the 'ignorecase' and 'extended syntax' regular
138
139
  # expression modifiers:
139
140
  #
140
141
  # controller 'geocode' do
141
- # match 'geocode/:postalcode' => :show, :constraints => {
142
- # :postalcode => /hx\d\d\s\d[a-z]{2}/i
142
+ # match 'geocode/:postalcode' => :show, constraints: {
143
+ # postalcode: /hx\d\d\s\d[a-z]{2}/i
143
144
  # }
144
145
  # end
145
146
  #
146
147
  # controller 'geocode' do
147
- # match 'geocode/:postalcode' => :show, :constraints => {
148
- # :postalcode => /# Postcode format
148
+ # match 'geocode/:postalcode' => :show, constraints: {
149
+ # postalcode: /# Postcode format
149
150
  # \d{5} #Prefix
150
151
  # (-\d{4})? #Suffix
151
152
  # /x
@@ -171,9 +172,9 @@ module ActionDispatch
171
172
  # Suppose you get an incoming request for <tt>/blog/edit/22</tt>, you'll end
172
173
  # up with:
173
174
  #
174
- # params = { :controller => 'blog',
175
- # :action => 'edit',
176
- # :id => '22'
175
+ # params = { controller: 'blog',
176
+ # action: 'edit',
177
+ # id: '22'
177
178
  # }
178
179
  #
179
180
  # By not relying on default routes, you improve the security of your
@@ -182,15 +183,16 @@ module ActionDispatch
182
183
  #
183
184
  # == HTTP Methods
184
185
  #
185
- # Using the <tt>:via</tt> option when specifying a route allows you to restrict it to a specific HTTP method.
186
- # Possible values are <tt>:post</tt>, <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>.
187
- # If your route needs to respond to more than one method you can use an array, e.g. <tt>[ :get, :post ]</tt>.
188
- # The default value is <tt>:any</tt> which means that the route will respond to any of the HTTP methods.
186
+ # Using the <tt>:via</tt> option when specifying a route allows you to
187
+ # restrict it to a specific HTTP method. Possible values are <tt>:post</tt>,
188
+ # <tt>:get</tt>, <tt>:patch</tt>, <tt>:put</tt>, <tt>:delete</tt> and
189
+ # <tt>:any</tt>. If your route needs to respond to more than one method you
190
+ # can use an array, e.g. <tt>[ :get, :post ]</tt>. The default value is
191
+ # <tt>:any</tt> which means that the route will respond to any of the HTTP
192
+ # methods.
189
193
  #
190
- # Examples:
191
- #
192
- # match 'post/:id' => 'posts#show', :via => :get
193
- # match 'post/:id' => "posts#create_comment', :via => :post
194
+ # match 'post/:id' => 'posts#show', via: :get
195
+ # match 'post/:id' => 'posts#create_comment', via: :post
194
196
  #
195
197
  # Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
196
198
  # URL will route to the <tt>show</tt> action.
@@ -198,12 +200,10 @@ module ActionDispatch
198
200
  # === HTTP helper methods
199
201
  #
200
202
  # An alternative method of specifying which HTTP method a route should respond to is to use the helper
201
- # methods <tt>get</tt>, <tt>post</tt>, <tt>put</tt> and <tt>delete</tt>.
202
- #
203
- # Examples:
203
+ # methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>.
204
204
  #
205
205
  # get 'post/:id' => 'posts#show'
206
- # post 'post/:id' => "posts#create_comment'
206
+ # post 'post/:id' => 'posts#create_comment'
207
207
  #
208
208
  # This syntax is less verbose and the intention is more apparent to someone else reading your code,
209
209
  # however if your route needs to respond to more than one HTTP method (or all methods) then using the
@@ -215,6 +215,12 @@ module ActionDispatch
215
215
  #
216
216
  # match "/stories" => redirect("/posts")
217
217
  #
218
+ # == Unicode character routes
219
+ #
220
+ # You can specify unicode character routes in your router:
221
+ #
222
+ # match "こんにちは" => "welcome#index"
223
+ #
218
224
  # == Routing to Rack Applications
219
225
  #
220
226
  # Instead of a String, like <tt>posts#index</tt>, which corresponds to the
@@ -239,7 +245,7 @@ module ActionDispatch
239
245
  # === +assert_routing+
240
246
  #
241
247
  # def test_movie_route_properly_splits
242
- # opts = {:controller => "plugin", :action => "checkout", :id => "2"}
248
+ # opts = {controller: "plugin", action: "checkout", id: "2"}
243
249
  # assert_routing "plugin/checkout/2", opts
244
250
  # end
245
251
  #
@@ -248,7 +254,7 @@ module ActionDispatch
248
254
  # === +assert_recognizes+
249
255
  #
250
256
  # def test_route_has_options
251
- # opts = {:controller => "plugin", :action => "show", :id => "12"}
257
+ # opts = {controller: "plugin", action: "show", id: "12"}
252
258
  # assert_recognizes opts, "/plugins/show/12"
253
259
  # end
254
260
  #
@@ -283,11 +289,6 @@ module ActionDispatch
283
289
  autoload :PolymorphicRoutes, 'action_dispatch/routing/polymorphic_routes'
284
290
 
285
291
  SEPARATORS = %w( / . ? ) #:nodoc:
286
- HTTP_METHODS = [:get, :head, :post, :put, :delete, :options] #:nodoc:
287
-
288
- # A helper module to hold URL related helpers.
289
- module Helpers #:nodoc:
290
- include PolymorphicRoutes
291
- end
292
+ HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc:
292
293
  end
293
294
  end
@@ -0,0 +1,240 @@
1
+ require 'delegate'
2
+ require 'active_support/core_ext/string/strip'
3
+
4
+ module ActionDispatch
5
+ module Routing
6
+ class RouteWrapper < SimpleDelegator
7
+ def endpoint
8
+ rack_app ? rack_app.inspect : "#{controller}##{action}"
9
+ end
10
+
11
+ def constraints
12
+ requirements.except(:controller, :action)
13
+ end
14
+
15
+ def rack_app(app = self.app)
16
+ @rack_app ||= begin
17
+ class_name = app.class.name.to_s
18
+ if class_name == "ActionDispatch::Routing::Mapper::Constraints"
19
+ rack_app(app.app)
20
+ elsif ActionDispatch::Routing::Redirect === app || class_name !~ /^ActionDispatch::Routing/
21
+ app
22
+ end
23
+ end
24
+ end
25
+
26
+ def verb
27
+ super.source.gsub(/[$^]/, '')
28
+ end
29
+
30
+ def path
31
+ super.spec.to_s
32
+ end
33
+
34
+ def name
35
+ super.to_s
36
+ end
37
+
38
+ def regexp
39
+ __getobj__.path.to_regexp
40
+ end
41
+
42
+ def json_regexp
43
+ str = regexp.inspect.
44
+ sub('\\A' , '^').
45
+ sub('\\Z' , '$').
46
+ sub('\\z' , '$').
47
+ sub(/^\// , '').
48
+ sub(/\/[a-z]*$/ , '').
49
+ gsub(/\(\?#.+\)/ , '').
50
+ gsub(/\(\?-\w+:/ , '(').
51
+ gsub(/\s/ , '')
52
+ Regexp.new(str).source
53
+ end
54
+
55
+ def reqs
56
+ @reqs ||= begin
57
+ reqs = endpoint
58
+ reqs += " #{constraints.to_s}" unless constraints.empty?
59
+ reqs
60
+ end
61
+ end
62
+
63
+ def controller
64
+ requirements[:controller] || ':controller'
65
+ end
66
+
67
+ def action
68
+ requirements[:action] || ':action'
69
+ end
70
+
71
+ def internal?
72
+ controller =~ %r{\Arails/(info|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}}
73
+ end
74
+
75
+ def engine?
76
+ rack_app && rack_app.respond_to?(:routes)
77
+ end
78
+ end
79
+
80
+ ##
81
+ # This class is just used for displaying route information when someone
82
+ # executes `rake routes` or looks at the RoutingError page.
83
+ # People should not use this class.
84
+ class RoutesInspector # :nodoc:
85
+ def initialize(routes)
86
+ @engines = {}
87
+ @routes = routes
88
+ end
89
+
90
+ def format(formatter, filter = nil)
91
+ routes_to_display = filter_routes(filter)
92
+
93
+ routes = collect_routes(routes_to_display)
94
+
95
+ if routes.none?
96
+ formatter.no_routes
97
+ return formatter.result
98
+ end
99
+
100
+ formatter.header routes
101
+ formatter.section routes
102
+
103
+ @engines.each do |name, engine_routes|
104
+ formatter.section_title "Routes for #{name}"
105
+ formatter.section engine_routes
106
+ end
107
+
108
+ formatter.result
109
+ end
110
+
111
+ private
112
+
113
+ def filter_routes(filter)
114
+ if filter
115
+ @routes.select { |route| route.defaults[:controller] == filter }
116
+ else
117
+ @routes
118
+ end
119
+ end
120
+
121
+ def collect_routes(routes)
122
+ routes.collect do |route|
123
+ RouteWrapper.new(route)
124
+ end.reject do |route|
125
+ route.internal?
126
+ end.collect do |route|
127
+ collect_engine_routes(route)
128
+
129
+ { name: route.name,
130
+ verb: route.verb,
131
+ path: route.path,
132
+ reqs: route.reqs,
133
+ regexp: route.json_regexp }
134
+ end
135
+ end
136
+
137
+ def collect_engine_routes(route)
138
+ name = route.endpoint
139
+ return unless route.engine?
140
+ return if @engines[name]
141
+
142
+ routes = route.rack_app.routes
143
+ if routes.is_a?(ActionDispatch::Routing::RouteSet)
144
+ @engines[name] = collect_routes(routes.routes)
145
+ end
146
+ end
147
+ end
148
+
149
+ class ConsoleFormatter
150
+ def initialize
151
+ @buffer = []
152
+ end
153
+
154
+ def result
155
+ @buffer.join("\n")
156
+ end
157
+
158
+ def section_title(title)
159
+ @buffer << "\n#{title}:"
160
+ end
161
+
162
+ def section(routes)
163
+ @buffer << draw_section(routes)
164
+ end
165
+
166
+ def header(routes)
167
+ @buffer << draw_header(routes)
168
+ end
169
+
170
+ def no_routes
171
+ @buffer << <<-MESSAGE.strip_heredoc
172
+ You don't have any routes defined!
173
+
174
+ Please add some routes in config/routes.rb.
175
+
176
+ For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html.
177
+ MESSAGE
178
+ end
179
+
180
+ private
181
+ def draw_section(routes)
182
+ name_width, verb_width, path_width = widths(routes)
183
+
184
+ routes.map do |r|
185
+ "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}"
186
+ end
187
+ end
188
+
189
+ def draw_header(routes)
190
+ name_width, verb_width, path_width = widths(routes)
191
+
192
+ "#{"Prefix".rjust(name_width)} #{"Verb".ljust(verb_width)} #{"URI Pattern".ljust(path_width)} Controller#Action"
193
+ end
194
+
195
+ def widths(routes)
196
+ [routes.map { |r| r[:name].length }.max,
197
+ routes.map { |r| r[:verb].length }.max,
198
+ routes.map { |r| r[:path].length }.max]
199
+ end
200
+ end
201
+
202
+ class HtmlTableFormatter
203
+ def initialize(view)
204
+ @view = view
205
+ @buffer = []
206
+ end
207
+
208
+ def section_title(title)
209
+ @buffer << %(<tr><th colspan="4">#{title}</th></tr>)
210
+ end
211
+
212
+ def section(routes)
213
+ @buffer << @view.render(partial: "routes/route", collection: routes)
214
+ end
215
+
216
+ # the header is part of the HTML page, so we don't construct it here.
217
+ def header(routes)
218
+ end
219
+
220
+ def no_routes
221
+ @buffer << <<-MESSAGE.strip_heredoc
222
+ <p>You don't have any routes defined!</p>
223
+ <ul>
224
+ <li>Please add some routes in <tt>config/routes.rb</tt>.</li>
225
+ <li>
226
+ For more information about routes, please see the Rails guide
227
+ <a href="http://guides.rubyonrails.org/routing.html">Rails Routing from the Outside In</a>.
228
+ </li>
229
+ </ul>
230
+ MESSAGE
231
+ end
232
+
233
+ def result
234
+ @view.raw @view.render(layout: "routes/table") {
235
+ @view.raw @buffer.join("\n")
236
+ }
237
+ end
238
+ end
239
+ end
240
+ end
@@ -1,13 +1,16 @@
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'
5
6
  require 'active_support/inflector'
6
7
  require 'action_dispatch/routing/redirection'
7
8
 
8
9
  module ActionDispatch
9
10
  module Routing
10
11
  class Mapper
12
+ URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
13
+
11
14
  class Constraints #:nodoc:
12
15
  def self.new(app, constraints, request = Rack::Request)
13
16
  if constraints.any?
@@ -26,15 +29,10 @@ module ActionDispatch
26
29
  def matches?(env)
27
30
  req = @request.new(env)
28
31
 
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
- }
36
-
37
- return true
32
+ @constraints.all? do |constraint|
33
+ (constraint.respond_to?(:matches?) && constraint.matches?(req)) ||
34
+ (constraint.respond_to?(:call) && constraint.call(*constraint_args(constraint, req)))
35
+ end
38
36
  ensure
39
37
  req.reset_parameters
40
38
  end
@@ -50,100 +48,159 @@ module ActionDispatch
50
48
  end
51
49
 
52
50
  class Mapping #:nodoc:
53
- IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix]
51
+ IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix, :format]
54
52
  ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
55
53
  WILDCARD_PATH = %r{\*([^/\)]+)\)?$}
56
54
 
55
+ attr_reader :scope, :path, :options, :requirements, :conditions, :defaults
56
+
57
57
  def initialize(set, scope, path, options)
58
- @set, @scope = set, scope
59
- @options = (@scope[:options] || {}).merge(options)
60
- @path = normalize_path(path)
58
+ @set, @scope, @path, @options = set, scope, path, options
59
+ @requirements, @conditions, @defaults = {}, {}, {}
60
+
61
+ normalize_path!
61
62
  normalize_options!
63
+ normalize_requirements!
64
+ normalize_conditions!
65
+ normalize_defaults!
62
66
  end
63
67
 
64
68
  def to_route
65
- [ app, conditions, requirements, defaults, @options[:as], @options[:anchor] ]
69
+ [ app, conditions, requirements, defaults, options[:as], options[:anchor] ]
66
70
  end
67
71
 
68
72
  private
69
73
 
74
+ def normalize_path!
75
+ raise ArgumentError, "path is required" if @path.blank?
76
+ @path = Mapper.normalize_path(@path)
77
+
78
+ if required_format?
79
+ @path = "#{@path}.:format"
80
+ elsif optional_format?
81
+ @path = "#{@path}(.:format)"
82
+ end
83
+ end
84
+
85
+ def required_format?
86
+ options[:format] == true
87
+ end
88
+
89
+ def optional_format?
90
+ options[:format] != false && !path.include?(':format') && !path.end_with?('/')
91
+ end
92
+
70
93
  def normalize_options!
94
+ @options.reverse_merge!(scope[:options]) if scope[:options]
95
+ path_without_format = path.sub(/\(\.:format\)$/, '')
96
+
97
+ # Add a constraint for wildcard route to make it non-greedy and match the
98
+ # optional format part of the route by default
99
+ if path_without_format.match(WILDCARD_PATH) && @options[:format] != false
100
+ @options[$1.to_sym] ||= /.+?/
101
+ end
102
+
103
+ if path_without_format.match(':controller')
104
+ raise ArgumentError, ":controller segment is not allowed within a namespace block" if scope[:module]
105
+
106
+ # Add a default constraint for :controller path segments that matches namespaced
107
+ # controllers with default routes like :controller/:action/:id(.:format), e.g:
108
+ # GET /admin/products/show/1
109
+ # => { controller: 'admin/products', action: 'show', id: '1' }
110
+ @options[:controller] ||= /.+?/
111
+ end
112
+
71
113
  @options.merge!(default_controller_and_action)
114
+ end
72
115
 
73
- requirements.each do |name, requirement|
74
- # segment_keys.include?(k.to_s) || k == :controller
75
- next unless Regexp === requirement && !constraints[name]
116
+ def normalize_format!
117
+ if options[:format] == true
118
+ options[:format] = /.+/
119
+ elsif options[:format] == false
120
+ options.delete(:format)
121
+ end
122
+ end
123
+
124
+ def normalize_requirements!
125
+ constraints.each do |key, requirement|
126
+ next unless segment_keys.include?(key) || key == :controller
76
127
 
77
128
  if requirement.source =~ ANCHOR_CHARACTERS_REGEX
78
129
  raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
79
130
  end
131
+
80
132
  if requirement.multiline?
81
- raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
133
+ raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
82
134
  end
135
+
136
+ @requirements[key] = requirement
83
137
  end
84
- end
85
138
 
86
- # match "account/overview"
87
- def using_match_shorthand?(path, options)
88
- path && (options[:to] || options[:action]).nil? && path =~ SHORTHAND_REGEX
139
+ if options[:format] == true
140
+ @requirements[:format] = /.+/
141
+ elsif Regexp === options[:format]
142
+ @requirements[:format] = options[:format]
143
+ elsif String === options[:format]
144
+ @requirements[:format] = Regexp.compile(options[:format])
145
+ end
89
146
  end
90
147
 
91
- def normalize_path(path)
92
- raise ArgumentError, "path is required" if path.blank?
93
- path = Mapper.normalize_path(path)
94
-
95
- if path.match(':controller')
96
- raise ArgumentError, ":controller segment is not allowed within a namespace block" if @scope[:module]
148
+ def normalize_defaults!
149
+ @defaults.merge!(scope[:defaults]) if scope[:defaults]
150
+ @defaults.merge!(options[:defaults]) if options[:defaults]
97
151
 
98
- # Add a default constraint for :controller path segments that matches namespaced
99
- # controllers with default routes like :controller/:action/:id(.:format), e.g:
100
- # GET /admin/products/show/1
101
- # => { :controller => 'admin/products', :action => 'show', :id => '1' }
102
- @options[:controller] ||= /.+?/
152
+ options.each do |key, default|
153
+ next if Regexp === default || IGNORE_OPTIONS.include?(key)
154
+ @defaults[key] = default
103
155
  end
104
156
 
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] ||= /.+?/
157
+ if options[:constraints].is_a?(Hash)
158
+ options[:constraints].each do |key, default|
159
+ next unless URL_OPTIONS.include?(key) && (String === default || Fixnum === default)
160
+ @defaults[key] ||= default
161
+ end
109
162
  end
110
163
 
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)"
164
+ if Regexp === options[:format]
165
+ @defaults[:format] = nil
166
+ elsif String === options[:format]
167
+ @defaults[:format] = options[:format]
120
168
  end
121
169
  end
122
170
 
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
- )
129
- end
171
+ def normalize_conditions!
172
+ @conditions.merge!(:path_info => path)
130
173
 
131
- def conditions
132
- { :path_info => @path }.merge(constraints).merge(request_method_condition)
133
- end
174
+ constraints.each do |key, condition|
175
+ next if segment_keys.include?(key) || key == :controller
176
+ @conditions[key] = condition
177
+ end
134
178
 
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) }
179
+ @conditions[:required_defaults] = []
180
+ options.each do |key, required_default|
181
+ next if segment_keys.include?(key) || IGNORE_OPTIONS.include?(key)
182
+ next if Regexp === required_default
183
+ @conditions[:required_defaults] << key
139
184
  end
140
- end
141
185
 
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) }
186
+ via_all = options.delete(:via) if options[:via] == :all
187
+
188
+ if !via_all && options[:via].blank?
189
+ msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
190
+ "If you want to expose your action to GET, use `get` in the router:\n\n" \
191
+ " Instead of: match \"controller#action\"\n" \
192
+ " Do: get \"controller#action\""
193
+ raise msg
146
194
  end
195
+
196
+ if via = options[:via]
197
+ list = Array(via).map { |m| m.to_s.dasherize.upcase }
198
+ @conditions.merge!(:request_method => list)
199
+ end
200
+ end
201
+
202
+ def app
203
+ Constraints.new(endpoint, blocks, @set.request_class)
147
204
  end
148
205
 
149
206
  def default_controller_and_action
@@ -170,14 +227,20 @@ module ActionDispatch
170
227
  controller = controller.to_s unless controller.is_a?(Regexp)
171
228
  action = action.to_s unless action.is_a?(Regexp)
172
229
 
173
- if controller.blank? && segment_keys.exclude?("controller")
230
+ if controller.blank? && segment_keys.exclude?(:controller)
174
231
  raise ArgumentError, "missing :controller"
175
232
  end
176
233
 
177
- if action.blank? && segment_keys.exclude?("action")
234
+ if action.blank? && segment_keys.exclude?(:action)
178
235
  raise ArgumentError, "missing :action"
179
236
  end
180
237
 
238
+ if controller.is_a?(String) && controller !~ /\A[a-z_0-9\/]*\z/
239
+ message = "'#{controller}' is not a supported controller name. This can lead to potential routing problems."
240
+ message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
241
+ raise ArgumentError, message
242
+ end
243
+
181
244
  hash = {}
182
245
  hash[:controller] = controller unless controller.blank?
183
246
  hash[:action] = action unless action.blank?
@@ -186,43 +249,55 @@ module ActionDispatch
186
249
  end
187
250
 
188
251
  def blocks
189
- constraints = @options[:constraints]
190
- if constraints.present? && !constraints.is_a?(Hash)
191
- [constraints]
252
+ if options[:constraints].present? && !options[:constraints].is_a?(Hash)
253
+ [options[:constraints]]
192
254
  else
193
- @scope[:blocks] || []
255
+ scope[:blocks] || []
194
256
  end
195
257
  end
196
258
 
197
259
  def constraints
198
- @constraints ||= requirements.reject { |k, v| segment_keys.include?(k.to_s) || k == :controller }
199
- end
260
+ @constraints ||= {}.tap do |constraints|
261
+ constraints.merge!(scope[:constraints]) if scope[:constraints]
200
262
 
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 }
205
- else
206
- { }
263
+ options.except(*IGNORE_OPTIONS).each do |key, option|
264
+ constraints[key] = option if Regexp === option
265
+ end
266
+
267
+ constraints.merge!(options[:constraints]) if options[:constraints].is_a?(Hash)
207
268
  end
208
269
  end
209
270
 
210
271
  def segment_keys
211
- @segment_keys ||= Journey::Path::Pattern.new(
212
- Journey::Router::Strexp.compile(@path, requirements, SEPARATORS)
213
- ).names
272
+ @segment_keys ||= path_pattern.names.map{ |s| s.to_sym }
273
+ end
274
+
275
+ def path_pattern
276
+ Journey::Path::Pattern.new(strexp)
277
+ end
278
+
279
+ def strexp
280
+ Journey::Router::Strexp.compile(path, requirements, SEPARATORS)
281
+ end
282
+
283
+ def endpoint
284
+ to.respond_to?(:call) ? to : dispatcher
285
+ end
286
+
287
+ def dispatcher
288
+ Routing::RouteSet::Dispatcher.new(:defaults => defaults)
214
289
  end
215
290
 
216
291
  def to
217
- @options[:to]
292
+ options[:to]
218
293
  end
219
294
 
220
295
  def default_controller
221
- @options[:controller] || @scope[:controller]
296
+ options[:controller] || scope[:controller]
222
297
  end
223
298
 
224
299
  def default_action
225
- @options[:action] || @scope[:action]
300
+ options[:action] || scope[:action]
226
301
  end
227
302
  end
228
303
 
@@ -236,21 +311,26 @@ module ActionDispatch
236
311
  end
237
312
 
238
313
  def self.normalize_name(name)
239
- normalize_path(name)[1..-1].gsub("/", "_")
314
+ normalize_path(name)[1..-1].tr("/", "_")
240
315
  end
241
316
 
242
317
  module Base
243
318
  # You can specify what Rails should route "/" to with the root method:
244
319
  #
245
- # root :to => 'pages#main'
320
+ # root to: 'pages#main'
246
321
  #
247
322
  # For options, see +match+, as +root+ uses it internally.
248
323
  #
324
+ # You can also pass a string which will expand
325
+ #
326
+ # root 'pages#main'
327
+ #
249
328
  # You should put the root route at the top of <tt>config/routes.rb</tt>,
250
329
  # because this means it will be matched first. As this is the most popular route
251
330
  # of most Rails applications, this is beneficial.
252
331
  def root(options = {})
253
- match '/', { :as => :root }.merge(options)
332
+ options = { :to => options } if options.is_a?(String)
333
+ match '/', { :as => :root, :via => :get }.merge!(options)
254
334
  end
255
335
 
256
336
  # Matches a url pattern to one or more routes. Any symbols in a pattern
@@ -264,7 +344,7 @@ module ActionDispatch
264
344
  # and +:action+ to the controller's action. A pattern can also map
265
345
  # wildcard segments (globs) to params:
266
346
  #
267
- # match 'songs/*category/:title' => 'songs#show'
347
+ # match 'songs/*category/:title', to: 'songs#show'
268
348
  #
269
349
  # # 'songs/rock/classic/stairway-to-heaven' sets
270
350
  # # params[:category] = 'rock/classic'
@@ -274,16 +354,20 @@ module ActionDispatch
274
354
  # +:controller+ should be set in options or hash shorthand. Examples:
275
355
  #
276
356
  # match 'photos/:id' => 'photos#show'
277
- # match 'photos/:id', :to => 'photos#show'
278
- # match 'photos/:id', :controller => 'photos', :action => 'show'
357
+ # match 'photos/:id', to: 'photos#show'
358
+ # match 'photos/:id', controller: 'photos', action: 'show'
279
359
  #
280
360
  # A pattern can also point to a +Rack+ endpoint i.e. anything that
281
361
  # responds to +call+:
282
362
  #
283
- # match 'photos/:id' => lambda {|hash| [200, {}, "Coming soon"] }
284
- # match 'photos/:id' => PhotoRackApp
363
+ # match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] }
364
+ # match 'photos/:id', to: PhotoRackApp
285
365
  # # Yes, controller actions are just rack endpoints
286
- # match 'photos/:id' => PhotosController.action(:show)
366
+ # match 'photos/:id', to: PhotosController.action(:show)
367
+ #
368
+ # Because request various HTTP verbs with a single action has security
369
+ # implications, is recommendable use HttpHelpers[rdoc-ref:HttpHelpers]
370
+ # instead +match+
287
371
  #
288
372
  # === Options
289
373
  #
@@ -301,7 +385,7 @@ module ActionDispatch
301
385
  # [:module]
302
386
  # The namespace for :controller.
303
387
  #
304
- # match 'path' => 'c#a', :module => 'sekret', :controller => 'posts'
388
+ # match 'path', to: 'c#a', module: 'sekret', controller: 'posts'
305
389
  # #=> Sekret::PostsController
306
390
  #
307
391
  # See <tt>Scoping#namespace</tt> for its scope equivalent.
@@ -312,16 +396,17 @@ module ActionDispatch
312
396
  # [:via]
313
397
  # Allowed HTTP verb(s) for route.
314
398
  #
315
- # match 'path' => 'c#a', :via => :get
316
- # match 'path' => 'c#a', :via => [:get, :post]
399
+ # match 'path', to: 'c#a', via: :get
400
+ # match 'path', to: 'c#a', via: [:get, :post]
401
+ # match 'path', to: 'c#a', via: :all
317
402
  #
318
403
  # [:to]
319
404
  # Points to a +Rack+ endpoint. Can be an object that responds to
320
405
  # +call+ or a string representing a controller's action.
321
406
  #
322
- # match 'path', :to => 'controller#action'
323
- # match 'path', :to => lambda { |env| [200, {}, "Success!"] }
324
- # match 'path', :to => RackApp
407
+ # match 'path', to: 'controller#action'
408
+ # match 'path', to: lambda { |env| [200, {}, ["Success!"]] }
409
+ # match 'path', to: RackApp
325
410
  #
326
411
  # [:on]
327
412
  # Shorthand for wrapping routes in a specific RESTful context. Valid
@@ -329,14 +414,14 @@ module ActionDispatch
329
414
  # <tt>resource(s)</tt> block. For example:
330
415
  #
331
416
  # resource :bar do
332
- # match 'foo' => 'c#a', :on => :member, :via => [:get, :post]
417
+ # match 'foo', to: 'c#a', on: :member, via: [:get, :post]
333
418
  # end
334
419
  #
335
420
  # Is equivalent to:
336
421
  #
337
422
  # resource :bar do
338
423
  # member do
339
- # match 'foo' => 'c#a', :via => [:get, :post]
424
+ # match 'foo', to: 'c#a', via: [:get, :post]
340
425
  # end
341
426
  # end
342
427
  #
@@ -344,12 +429,12 @@ module ActionDispatch
344
429
  # Constrains parameters with a hash of regular expressions or an
345
430
  # object that responds to <tt>matches?</tt>
346
431
  #
347
- # match 'path/:id', :constraints => { :id => /[A-Z]\d{5}/ }
432
+ # match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }
348
433
  #
349
434
  # class Blacklist
350
435
  # def matches?(request) request.remote_ip == '1.2.3.4' end
351
436
  # end
352
- # match 'path' => 'c#a', :constraints => Blacklist.new
437
+ # match 'path', to: 'c#a', constraints: Blacklist.new
353
438
  #
354
439
  # See <tt>Scoping#constraints</tt> for more examples with its scope
355
440
  # equivalent.
@@ -358,7 +443,7 @@ module ActionDispatch
358
443
  # Sets defaults for parameters
359
444
  #
360
445
  # # Sets params[:format] to 'jpg' by default
361
- # match 'path' => 'c#a', :defaults => { :format => 'jpg' }
446
+ # match 'path', to: 'c#a', defaults: { format: 'jpg' }
362
447
  #
363
448
  # See <tt>Scoping#defaults</tt> for its scope equivalent.
364
449
  #
@@ -367,13 +452,17 @@ module ActionDispatch
367
452
  # false, the pattern matches any request prefixed with the given path.
368
453
  #
369
454
  # # Matches any request starting with 'path'
370
- # match 'path' => 'c#a', :anchor => false
455
+ # match 'path', to: 'c#a', anchor: false
456
+ #
457
+ # [:format]
458
+ # Allows you to specify the default value for optional +format+
459
+ # segment or disable it by supplying +false+.
371
460
  def match(path, options=nil)
372
461
  end
373
462
 
374
463
  # Mount a Rack-based application to be used within the application.
375
464
  #
376
- # mount SomeRackApp, :at => "some_route"
465
+ # mount SomeRackApp, at: "some_route"
377
466
  #
378
467
  # Alternatively:
379
468
  #
@@ -386,7 +475,7 @@ module ActionDispatch
386
475
  # the helper is either +some_rack_app_path+ or +some_rack_app_url+.
387
476
  # To customize this helper's name, use the +:as+ option:
388
477
  #
389
- # mount(SomeRackApp => "some_route", :as => "exciting")
478
+ # mount(SomeRackApp => "some_route", as: "exciting")
390
479
  #
391
480
  # This will generate the +exciting_path+ and +exciting_url+ helpers
392
481
  # which can be used to navigate to this mounted app.
@@ -394,6 +483,10 @@ module ActionDispatch
394
483
  if options
395
484
  path = options.delete(:at)
396
485
  else
486
+ unless Hash === app
487
+ raise ArgumentError, "must be called with mount point"
488
+ end
489
+
397
490
  options = app
398
491
  app, path = options.find { |k, v| k.respond_to?(:call) }
399
492
  options.delete(app) if app
@@ -401,7 +494,8 @@ module ActionDispatch
401
494
 
402
495
  raise "A rack application must be specified" unless path
403
496
 
404
- options[:as] ||= app_name(app)
497
+ options[:as] ||= app_name(app)
498
+ options[:via] ||= :all
405
499
 
406
500
  match(path, options.merge(:to => app, :anchor => false, :format => false))
407
501
 
@@ -428,7 +522,7 @@ module ActionDispatch
428
522
  app.railtie_name
429
523
  else
430
524
  class_name = app.class.is_a?(Class) ? app.name : app.class.name
431
- ActiveSupport::Inflector.underscore(class_name).gsub("/", "_")
525
+ ActiveSupport::Inflector.underscore(class_name).tr("/", "_")
432
526
  end
433
527
  end
434
528
 
@@ -438,14 +532,16 @@ module ActionDispatch
438
532
  _route = @set.named_routes.routes[name.to_sym]
439
533
  _routes = @set
440
534
  app.routes.define_mounted_helper(name)
441
- app.routes.class_eval do
535
+ app.routes.singleton_class.class_eval do
536
+ define_method :mounted? do
537
+ true
538
+ end
539
+
442
540
  define_method :_generate_prefix do |options|
443
541
  prefix_options = options.slice(*_route.segment_keys)
444
542
  # we must actually delete prefix segment keys to avoid passing them to next url_for
445
543
  _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
544
+ _routes.url_helpers.send("#{name}_path", prefix_options)
449
545
  end
450
546
  end
451
547
  end
@@ -453,51 +549,51 @@ module ActionDispatch
453
549
 
454
550
  module HttpHelpers
455
551
  # Define a route that only recognizes HTTP GET.
456
- # For supported arguments, see <tt>Base#match</tt>.
457
- #
458
- # Example:
552
+ # For supported arguments, see match[rdoc-ref:Base#match]
459
553
  #
460
- # get 'bacon', :to => 'food#bacon'
554
+ # get 'bacon', to: 'food#bacon'
461
555
  def get(*args, &block)
462
- map_method(:get, *args, &block)
556
+ map_method(:get, args, &block)
463
557
  end
464
558
 
465
559
  # Define a route that only recognizes HTTP POST.
466
- # For supported arguments, see <tt>Base#match</tt>.
467
- #
468
- # Example:
560
+ # For supported arguments, see match[rdoc-ref:Base#match]
469
561
  #
470
- # post 'bacon', :to => 'food#bacon'
562
+ # post 'bacon', to: 'food#bacon'
471
563
  def post(*args, &block)
472
- map_method(:post, *args, &block)
564
+ map_method(:post, args, &block)
473
565
  end
474
566
 
475
- # Define a route that only recognizes HTTP PUT.
476
- # For supported arguments, see <tt>Base#match</tt>.
477
- #
478
- # Example:
567
+ # Define a route that only recognizes HTTP PATCH.
568
+ # For supported arguments, see match[rdoc-ref:Base#match]
479
569
  #
480
- # put 'bacon', :to => 'food#bacon'
481
- def put(*args, &block)
482
- map_method(:put, *args, &block)
570
+ # patch 'bacon', to: 'food#bacon'
571
+ def patch(*args, &block)
572
+ map_method(:patch, args, &block)
483
573
  end
484
574
 
485
575
  # Define a route that only recognizes HTTP PUT.
486
- # For supported arguments, see <tt>Base#match</tt>.
576
+ # For supported arguments, see match[rdoc-ref:Base#match]
487
577
  #
488
- # Example:
578
+ # put 'bacon', to: 'food#bacon'
579
+ def put(*args, &block)
580
+ map_method(:put, args, &block)
581
+ end
582
+
583
+ # Define a route that only recognizes HTTP DELETE.
584
+ # For supported arguments, see match[rdoc-ref:Base#match]
489
585
  #
490
- # delete 'broccoli', :to => 'food#broccoli'
586
+ # delete 'broccoli', to: 'food#broccoli'
491
587
  def delete(*args, &block)
492
- map_method(:delete, *args, &block)
588
+ map_method(:delete, args, &block)
493
589
  end
494
590
 
495
591
  private
496
- def map_method(method, *args, &block)
592
+ def map_method(method, args, &block)
497
593
  options = args.extract_options!
498
- options[:via] = method
499
- args.push(options)
500
- match(*args, &block)
594
+ options[:via] = method
595
+ options[:path] ||= args.first if args.first.is_a?(String)
596
+ match(*args, options, &block)
501
597
  self
502
598
  end
503
599
  end
@@ -515,24 +611,24 @@ module ActionDispatch
515
611
  # This will create a number of routes for each of the posts and comments
516
612
  # controller. For <tt>Admin::PostsController</tt>, Rails will create:
517
613
  #
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
614
+ # GET /admin/posts
615
+ # GET /admin/posts/new
616
+ # POST /admin/posts
617
+ # GET /admin/posts/1
618
+ # GET /admin/posts/1/edit
619
+ # PATCH/PUT /admin/posts/1
620
+ # DELETE /admin/posts/1
525
621
  #
526
622
  # If you want to route /posts (without the prefix /admin) to
527
623
  # <tt>Admin::PostsController</tt>, you could use
528
624
  #
529
- # scope :module => "admin" do
625
+ # scope module: "admin" do
530
626
  # resources :posts
531
627
  # end
532
628
  #
533
629
  # or, for a single case
534
630
  #
535
- # resources :posts, :module => "admin"
631
+ # resources :posts, module: "admin"
536
632
  #
537
633
  # If you want to route /admin/posts to +PostsController+
538
634
  # (without the Admin:: module prefix), you could use
@@ -543,25 +639,25 @@ module ActionDispatch
543
639
  #
544
640
  # or, for a single case
545
641
  #
546
- # resources :posts, :path => "/admin/posts"
642
+ # resources :posts, path: "/admin/posts"
547
643
  #
548
644
  # In each of these cases, the named routes remain the same as if you did
549
645
  # not use scope. In the last case, the following paths map to
550
646
  # +PostsController+:
551
647
  #
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
648
+ # GET /admin/posts
649
+ # GET /admin/posts/new
650
+ # POST /admin/posts
651
+ # GET /admin/posts/1
652
+ # GET /admin/posts/1/edit
653
+ # PATCH/PUT /admin/posts/1
654
+ # DELETE /admin/posts/1
559
655
  module Scoping
560
656
  # Scopes a set of routes to the given default options.
561
657
  #
562
658
  # Take the following route definition as an example:
563
659
  #
564
- # scope :path => ":account_id", :as => "account" do
660
+ # scope path: ":account_id", as: "account" do
565
661
  # resources :projects
566
662
  # end
567
663
  #
@@ -573,31 +669,34 @@ module ActionDispatch
573
669
  #
574
670
  # Takes same options as <tt>Base#match</tt> and <tt>Resources#resources</tt>.
575
671
  #
576
- # === Examples
577
- #
578
672
  # # route /posts (without the prefix /admin) to <tt>Admin::PostsController</tt>
579
- # scope :module => "admin" do
673
+ # scope module: "admin" do
580
674
  # resources :posts
581
675
  # end
582
676
  #
583
677
  # # prefix the posts resource's requests with '/admin'
584
- # scope :path => "/admin" do
678
+ # scope path: "/admin" do
585
679
  # resources :posts
586
680
  # end
587
681
  #
588
682
  # # prefix the routing helper name: +sekret_posts_path+ instead of +posts_path+
589
- # scope :as => "sekret" do
683
+ # scope as: "sekret" do
590
684
  # resources :posts
591
685
  # end
592
686
  def scope(*args)
593
- options = args.extract_options!
594
- options = options.dup
595
-
596
- options[:path] = args.first if args.first.is_a?(String)
687
+ options = args.extract_options!.dup
597
688
  recover = {}
598
689
 
690
+ options[:path] = args.flatten.join('/') if args.any?
599
691
  options[:constraints] ||= {}
600
- unless options[:constraints].is_a?(Hash)
692
+
693
+ if options[:constraints].is_a?(Hash)
694
+ defaults = options[:constraints].select do
695
+ |k, v| URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum))
696
+ end
697
+
698
+ (options[:defaults] ||= {}).reverse_merge!(defaults)
699
+ else
601
700
  block, options[:constraints] = options[:constraints], {}
602
701
  end
603
702
 
@@ -608,8 +707,8 @@ module ActionDispatch
608
707
  end
609
708
  end
610
709
 
611
- recover[:block] = @scope[:blocks]
612
- @scope[:blocks] = merge_blocks_scope(@scope[:blocks], block)
710
+ recover[:blocks] = @scope[:blocks]
711
+ @scope[:blocks] = merge_blocks_scope(@scope[:blocks], block)
613
712
 
614
713
  recover[:options] = @scope[:options]
615
714
  @scope[:options] = merge_options_scope(@scope[:options], options)
@@ -617,19 +716,13 @@ module ActionDispatch
617
716
  yield
618
717
  self
619
718
  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]
719
+ @scope.merge!(recover)
626
720
  end
627
721
 
628
722
  # Scopes routes to a specific controller
629
723
  #
630
- # Example:
631
724
  # controller "food" do
632
- # match "bacon", :action => "bacon"
725
+ # match "bacon", action: "bacon"
633
726
  # end
634
727
  def controller(controller, options={})
635
728
  options[:controller] = controller
@@ -644,13 +737,13 @@ module ActionDispatch
644
737
  #
645
738
  # This generates the following routes:
646
739
  #
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
740
+ # admin_posts GET /admin/posts(.:format) admin/posts#index
741
+ # admin_posts POST /admin/posts(.:format) admin/posts#create
742
+ # new_admin_post GET /admin/posts/new(.:format) admin/posts#new
743
+ # edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
744
+ # admin_post GET /admin/posts/:id(.:format) admin/posts#show
745
+ # admin_post PATCH/PUT /admin/posts/:id(.:format) admin/posts#update
746
+ # admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
654
747
  #
655
748
  # === Options
656
749
  #
@@ -660,20 +753,18 @@ module ActionDispatch
660
753
  # For options, see <tt>Base#match</tt>. For +:shallow_path+ option, see
661
754
  # <tt>Resources#resources</tt>.
662
755
  #
663
- # === Examples
664
- #
665
756
  # # accessible through /sekret/posts rather than /admin/posts
666
- # namespace :admin, :path => "sekret" do
757
+ # namespace :admin, path: "sekret" do
667
758
  # resources :posts
668
759
  # end
669
760
  #
670
761
  # # maps to <tt>Sekret::PostsController</tt> rather than <tt>Admin::PostsController</tt>
671
- # namespace :admin, :module => "sekret" do
762
+ # namespace :admin, module: "sekret" do
672
763
  # resources :posts
673
764
  # end
674
765
  #
675
766
  # # generates +sekret_posts_path+ rather than +admin_posts_path+
676
- # namespace :admin, :as => "sekret" do
767
+ # namespace :admin, as: "sekret" do
677
768
  # resources :posts
678
769
  # end
679
770
  def namespace(path, options = {})
@@ -687,7 +778,7 @@ module ActionDispatch
687
778
  # Allows you to constrain the nested routes based on a set of rules.
688
779
  # For instance, in order to change the routes to allow for a dot character in the +id+ parameter:
689
780
  #
690
- # constraints(:id => /\d+\.\d+/) do
781
+ # constraints(id: /\d+\.\d+/) do
691
782
  # resources :posts
692
783
  # end
693
784
  #
@@ -697,7 +788,7 @@ module ActionDispatch
697
788
  # You may use this to also restrict other parameters:
698
789
  #
699
790
  # resources :posts do
700
- # constraints(:post_id => /\d+\.\d+/) do
791
+ # constraints(post_id: /\d+\.\d+/) do
701
792
  # resources :comments
702
793
  # end
703
794
  # end
@@ -706,7 +797,7 @@ module ActionDispatch
706
797
  #
707
798
  # Routes can also be constrained to an IP or a certain range of IP addresses:
708
799
  #
709
- # constraints(:ip => /192.168.\d+.\d+/) do
800
+ # constraints(ip: /192\.168\.\d+\.\d+/) do
710
801
  # resources :posts
711
802
  # end
712
803
  #
@@ -743,8 +834,8 @@ module ActionDispatch
743
834
  end
744
835
 
745
836
  # 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'
837
+ # defaults id: 'home' do
838
+ # match 'scoped_pages/(:id)', to: 'pages#show'
748
839
  # end
749
840
  # Using this, the +:id+ parameter here will default to 'home'.
750
841
  def defaults(defaults = {})
@@ -780,10 +871,6 @@ module ActionDispatch
780
871
  child
781
872
  end
782
873
 
783
- def merge_action_scope(parent, child) #:nodoc:
784
- child
785
- end
786
-
787
874
  def merge_path_names_scope(parent, child) #:nodoc:
788
875
  merge_options_scope(parent, child)
789
876
  end
@@ -803,7 +890,7 @@ module ActionDispatch
803
890
  end
804
891
 
805
892
  def merge_options_scope(parent, child) #:nodoc:
806
- (parent || {}).except(*override_keys(child)).merge(child)
893
+ (parent || {}).except(*override_keys(child)).merge!(child)
807
894
  end
808
895
 
809
896
  def merge_shallow_scope(parent, child) #:nodoc:
@@ -850,7 +937,7 @@ module ActionDispatch
850
937
  # use dots as part of the +:id+ parameter add a constraint which
851
938
  # overrides this restriction, e.g:
852
939
  #
853
- # resources :articles, :id => /[^\/]+/
940
+ # resources :articles, id: /[^\/]+/
854
941
  #
855
942
  # This allows any character other than a slash as part of your +:id+.
856
943
  #
@@ -858,17 +945,18 @@ module ActionDispatch
858
945
  # CANONICAL_ACTIONS holds all actions that does not need a prefix or
859
946
  # a path appended since they fit properly in their scope level.
860
947
  VALID_ON_OPTIONS = [:new, :collection, :member]
861
- RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except]
948
+ RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns]
862
949
  CANONICAL_ACTIONS = %w(index create new show update destroy)
863
950
 
864
951
  class Resource #:nodoc:
865
- attr_reader :controller, :path, :options
952
+ attr_reader :controller, :path, :options, :param
866
953
 
867
954
  def initialize(entities, options = {})
868
955
  @name = entities.to_s
869
956
  @path = (options[:path] || @name).to_s
870
957
  @controller = (options[:controller] || @name).to_s
871
958
  @as = options[:as]
959
+ @param = (options[:param] || :id).to_sym
872
960
  @options = options
873
961
  end
874
962
 
@@ -913,15 +1001,21 @@ module ActionDispatch
913
1001
  alias :collection_scope :path
914
1002
 
915
1003
  def member_scope
916
- "#{path}/:id"
1004
+ "#{path}/:#{param}"
917
1005
  end
918
1006
 
1007
+ alias :shallow_scope :member_scope
1008
+
919
1009
  def new_scope(new_path)
920
1010
  "#{path}/#{new_path}"
921
1011
  end
922
1012
 
1013
+ def nested_param
1014
+ :"#{singular}_#{param}"
1015
+ end
1016
+
923
1017
  def nested_scope
924
- "#{path}/:#{singular}_id"
1018
+ "#{path}/:#{nested_param}"
925
1019
  end
926
1020
 
927
1021
  end
@@ -969,12 +1063,12 @@ module ActionDispatch
969
1063
  # the +GeoCoders+ controller (note that the controller is named after
970
1064
  # the plural):
971
1065
  #
972
- # GET /geocoder/new
973
- # POST /geocoder
974
- # GET /geocoder
975
- # GET /geocoder/edit
976
- # PUT /geocoder
977
- # DELETE /geocoder
1066
+ # GET /geocoder/new
1067
+ # POST /geocoder
1068
+ # GET /geocoder
1069
+ # GET /geocoder/edit
1070
+ # PATCH/PUT /geocoder
1071
+ # DELETE /geocoder
978
1072
  #
979
1073
  # === Options
980
1074
  # Takes same options as +resources+.
@@ -988,6 +1082,8 @@ module ActionDispatch
988
1082
  resource_scope(:resource, SingletonResource.new(resources.pop, options)) do
989
1083
  yield if block_given?
990
1084
 
1085
+ concerns(options[:concerns]) if options[:concerns]
1086
+
991
1087
  collection do
992
1088
  post :create
993
1089
  end if parent_resource.actions.include?(:create)
@@ -996,12 +1092,7 @@ module ActionDispatch
996
1092
  get :new
997
1093
  end if parent_resource.actions.include?(:new)
998
1094
 
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
1095
+ set_member_mappings_for_resource
1005
1096
  end
1006
1097
 
1007
1098
  self
@@ -1017,13 +1108,13 @@ module ActionDispatch
1017
1108
  # creates seven different routes in your application, all mapping to
1018
1109
  # the +Photos+ controller:
1019
1110
  #
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
1111
+ # GET /photos
1112
+ # GET /photos/new
1113
+ # POST /photos
1114
+ # GET /photos/:id
1115
+ # GET /photos/:id/edit
1116
+ # PATCH/PUT /photos/:id
1117
+ # DELETE /photos/:id
1027
1118
  #
1028
1119
  # Resources can also be nested infinitely by using this block syntax:
1029
1120
  #
@@ -1033,13 +1124,13 @@ module ActionDispatch
1033
1124
  #
1034
1125
  # This generates the following comments routes:
1035
1126
  #
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
1127
+ # GET /photos/:photo_id/comments
1128
+ # GET /photos/:photo_id/comments/new
1129
+ # POST /photos/:photo_id/comments
1130
+ # GET /photos/:photo_id/comments/:id
1131
+ # GET /photos/:photo_id/comments/:id/edit
1132
+ # PATCH/PUT /photos/:photo_id/comments/:id
1133
+ # DELETE /photos/:photo_id/comments/:id
1043
1134
  #
1044
1135
  # === Options
1045
1136
  # Takes same options as <tt>Base#match</tt> as well as:
@@ -1048,43 +1139,43 @@ module ActionDispatch
1048
1139
  # Allows you to change the segment component of the +edit+ and +new+ actions.
1049
1140
  # Actions not specified are not changed.
1050
1141
  #
1051
- # resources :posts, :path_names => { :new => "brand_new" }
1142
+ # resources :posts, path_names: { new: "brand_new" }
1052
1143
  #
1053
1144
  # The above example will now change /posts/new to /posts/brand_new
1054
1145
  #
1055
1146
  # [:path]
1056
1147
  # Allows you to change the path prefix for the resource.
1057
1148
  #
1058
- # resources :posts, :path => 'postings'
1149
+ # resources :posts, path: 'postings'
1059
1150
  #
1060
1151
  # The resource and all segments will now route to /postings instead of /posts
1061
1152
  #
1062
1153
  # [:only]
1063
1154
  # Only generate routes for the given actions.
1064
1155
  #
1065
- # resources :cows, :only => :show
1066
- # resources :cows, :only => [:show, :index]
1156
+ # resources :cows, only: :show
1157
+ # resources :cows, only: [:show, :index]
1067
1158
  #
1068
1159
  # [:except]
1069
1160
  # Generate all routes except for the given actions.
1070
1161
  #
1071
- # resources :cows, :except => :show
1072
- # resources :cows, :except => [:show, :index]
1162
+ # resources :cows, except: :show
1163
+ # resources :cows, except: [:show, :index]
1073
1164
  #
1074
1165
  # [:shallow]
1075
1166
  # Generates shallow routes for nested resource(s). When placed on a parent resource,
1076
1167
  # generates shallow routes for all nested resources.
1077
1168
  #
1078
- # resources :posts, :shallow => true do
1169
+ # resources :posts, shallow: true do
1079
1170
  # resources :comments
1080
1171
  # end
1081
1172
  #
1082
1173
  # Is the same as:
1083
1174
  #
1084
1175
  # resources :posts do
1085
- # resources :comments, :except => [:show, :edit, :update, :destroy]
1176
+ # resources :comments, except: [:show, :edit, :update, :destroy]
1086
1177
  # end
1087
- # resources :comments, :only => [:show, :edit, :update, :destroy]
1178
+ # resources :comments, only: [:show, :edit, :update, :destroy]
1088
1179
  #
1089
1180
  # This allows URLs for resources that otherwise would be deeply nested such
1090
1181
  # as a comment on a blog post like <tt>/posts/a-long-permalink/comments/1234</tt>
@@ -1093,29 +1184,52 @@ module ActionDispatch
1093
1184
  # [:shallow_path]
1094
1185
  # Prefixes nested shallow routes with the specified path.
1095
1186
  #
1096
- # scope :shallow_path => "sekret" do
1187
+ # scope shallow_path: "sekret" do
1097
1188
  # resources :posts do
1098
- # resources :comments, :shallow => true
1189
+ # resources :comments, shallow: true
1099
1190
  # end
1100
1191
  # end
1101
1192
  #
1102
1193
  # The +comments+ resource here will have the following routes generated for it:
1103
1194
  #
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)
1195
+ # post_comments GET /posts/:post_id/comments(.:format)
1196
+ # post_comments POST /posts/:post_id/comments(.:format)
1197
+ # new_post_comment GET /posts/:post_id/comments/new(.:format)
1198
+ # edit_comment GET /sekret/comments/:id/edit(.:format)
1199
+ # comment GET /sekret/comments/:id(.:format)
1200
+ # comment PATCH/PUT /sekret/comments/:id(.:format)
1201
+ # comment DELETE /sekret/comments/:id(.:format)
1202
+ #
1203
+ # [:shallow_prefix]
1204
+ # Prefixes nested shallow route names with specified prefix.
1205
+ #
1206
+ # scope shallow_prefix: "sekret" do
1207
+ # resources :posts do
1208
+ # resources :comments, shallow: true
1209
+ # end
1210
+ # end
1211
+ #
1212
+ # The +comments+ resource here will have the following routes generated for it:
1213
+ #
1214
+ # post_comments GET /posts/:post_id/comments(.:format)
1215
+ # post_comments POST /posts/:post_id/comments(.:format)
1216
+ # new_post_comment GET /posts/:post_id/comments/new(.:format)
1217
+ # edit_sekret_comment GET /comments/:id/edit(.:format)
1218
+ # sekret_comment GET /comments/:id(.:format)
1219
+ # sekret_comment PATCH/PUT /comments/:id(.:format)
1220
+ # sekret_comment DELETE /comments/:id(.:format)
1221
+ #
1222
+ # [:format]
1223
+ # Allows you to specify the default value for optional +format+
1224
+ # segment or disable it by supplying +false+.
1111
1225
  #
1112
1226
  # === Examples
1113
1227
  #
1114
1228
  # # routes call <tt>Admin::PostsController</tt>
1115
- # resources :posts, :module => "admin"
1229
+ # resources :posts, module: "admin"
1116
1230
  #
1117
1231
  # # resource actions are at /admin/posts.
1118
- # resources :posts, :path => "admin/posts"
1232
+ # resources :posts, path: "admin/posts"
1119
1233
  def resources(*resources, &block)
1120
1234
  options = resources.extract_options!.dup
1121
1235
 
@@ -1126,6 +1240,8 @@ module ActionDispatch
1126
1240
  resource_scope(:resources, Resource.new(resources.pop, options)) do
1127
1241
  yield if block_given?
1128
1242
 
1243
+ concerns(options[:concerns]) if options[:concerns]
1244
+
1129
1245
  collection do
1130
1246
  get :index if parent_resource.actions.include?(:index)
1131
1247
  post :create if parent_resource.actions.include?(:create)
@@ -1135,12 +1251,7 @@ module ActionDispatch
1135
1251
  get :new
1136
1252
  end if parent_resource.actions.include?(:new)
1137
1253
 
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
1254
+ set_member_mappings_for_resource
1144
1255
  end
1145
1256
 
1146
1257
  self
@@ -1246,6 +1357,9 @@ module ActionDispatch
1246
1357
  parent_resource.instance_of?(Resource) && @scope[:shallow]
1247
1358
  end
1248
1359
 
1360
+ # match 'path' => 'controller#action'
1361
+ # match 'path', to: 'controller#action'
1362
+ # match 'path', 'otherpath', on: :member, via: :get
1249
1363
  def match(path, *rest)
1250
1364
  if rest.empty? && Hash === path
1251
1365
  options = path
@@ -1258,10 +1372,6 @@ module ActionDispatch
1258
1372
  paths = [path] + rest
1259
1373
  end
1260
1374
 
1261
- if @scope[:controller] && @scope[:action]
1262
- options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
1263
- end
1264
-
1265
1375
  path_without_format = path.to_s.sub(/\(\.:format\)$/, '')
1266
1376
  if using_match_shorthand?(path_without_format, options)
1267
1377
  options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
@@ -1312,7 +1422,7 @@ module ActionDispatch
1312
1422
  options[:as] = name_for_action(options[:as], action)
1313
1423
  end
1314
1424
 
1315
- mapping = Mapping.new(@set, @scope, path, options)
1425
+ mapping = Mapping.new(@set, @scope, URI.parser.escape(path), options)
1316
1426
  app, conditions, requirements, defaults, as, anchor = mapping.to_route
1317
1427
  @set.add_route(app, conditions, requirements, defaults, as, anchor)
1318
1428
  end
@@ -1418,18 +1528,18 @@ module ActionDispatch
1418
1528
  def nested_options #:nodoc:
1419
1529
  options = { :as => parent_resource.member_name }
1420
1530
  options[:constraints] = {
1421
- :"#{parent_resource.singular}_id" => id_constraint
1422
- } if id_constraint?
1531
+ parent_resource.nested_param => param_constraint
1532
+ } if param_constraint?
1423
1533
 
1424
1534
  options
1425
1535
  end
1426
1536
 
1427
- def id_constraint? #:nodoc:
1428
- @scope[:constraints] && @scope[:constraints][:id].is_a?(Regexp)
1537
+ def param_constraint? #:nodoc:
1538
+ @scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp)
1429
1539
  end
1430
1540
 
1431
- def id_constraint #:nodoc:
1432
- @scope[:constraints][:id]
1541
+ def param_constraint #:nodoc:
1542
+ @scope[:constraints][parent_resource.param]
1433
1543
  end
1434
1544
 
1435
1545
  def canonical_action?(action, flag) #:nodoc:
@@ -1442,9 +1552,9 @@ module ActionDispatch
1442
1552
 
1443
1553
  def path_for_action(action, path) #:nodoc:
1444
1554
  prefix = shallow_scoping? ?
1445
- "#{@scope[:shallow_path]}/#{parent_resource.path}/:id" : @scope[:path]
1555
+ "#{@scope[:shallow_path]}/#{parent_resource.shallow_scope}" : @scope[:path]
1446
1556
 
1447
- path = if canonical_action?(action, path.blank?)
1557
+ if canonical_action?(action, path.blank?)
1448
1558
  prefix.to_s
1449
1559
  else
1450
1560
  "#{prefix}/#{action_path(action, path)}"
@@ -1452,8 +1562,7 @@ module ActionDispatch
1452
1562
  end
1453
1563
 
1454
1564
  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?
1565
+ name = name.to_sym if name.is_a?(String)
1457
1566
  path || @scope[:path_names][name] || name.to_s
1458
1567
  end
1459
1568
 
@@ -1503,17 +1612,136 @@ module ActionDispatch
1503
1612
  end
1504
1613
  end
1505
1614
  end
1615
+
1616
+ def set_member_mappings_for_resource
1617
+ member do
1618
+ get :edit if parent_resource.actions.include?(:edit)
1619
+ get :show if parent_resource.actions.include?(:show)
1620
+ if parent_resource.actions.include?(:update)
1621
+ patch :update
1622
+ put :update
1623
+ end
1624
+ delete :destroy if parent_resource.actions.include?(:destroy)
1625
+ end
1626
+ end
1627
+ end
1628
+
1629
+ # Routing Concerns allow you to declare common routes that can be reused
1630
+ # inside others resources and routes.
1631
+ #
1632
+ # concern :commentable do
1633
+ # resources :comments
1634
+ # end
1635
+ #
1636
+ # concern :image_attachable do
1637
+ # resources :images, only: :index
1638
+ # end
1639
+ #
1640
+ # These concerns are used in Resources routing:
1641
+ #
1642
+ # resources :messages, concerns: [:commentable, :image_attachable]
1643
+ #
1644
+ # or in a scope or namespace:
1645
+ #
1646
+ # namespace :posts do
1647
+ # concerns :commentable
1648
+ # end
1649
+ module Concerns
1650
+ # Define a routing concern using a name.
1651
+ #
1652
+ # Concerns may be defined inline, using a block, or handled by
1653
+ # another object, by passing that object as the second parameter.
1654
+ #
1655
+ # The concern object, if supplied, should respond to <tt>call</tt>,
1656
+ # which will receive two parameters:
1657
+ #
1658
+ # * The current mapper
1659
+ # * A hash of options which the concern object may use
1660
+ #
1661
+ # Options may also be used by concerns defined in a block by accepting
1662
+ # a block parameter. So, using a block, you might do something as
1663
+ # simple as limit the actions available on certain resources, passing
1664
+ # standard resource options through the concern:
1665
+ #
1666
+ # concern :commentable do |options|
1667
+ # resources :comments, options
1668
+ # end
1669
+ #
1670
+ # resources :posts, concerns: :commentable
1671
+ # resources :archived_posts do
1672
+ # # Don't allow comments on archived posts
1673
+ # concerns :commentable, only: [:index, :show]
1674
+ # end
1675
+ #
1676
+ # Or, using a callable object, you might implement something more
1677
+ # specific to your application, which would be out of place in your
1678
+ # routes file.
1679
+ #
1680
+ # # purchasable.rb
1681
+ # class Purchasable
1682
+ # def initialize(defaults = {})
1683
+ # @defaults = defaults
1684
+ # end
1685
+ #
1686
+ # def call(mapper, options = {})
1687
+ # options = @defaults.merge(options)
1688
+ # mapper.resources :purchases
1689
+ # mapper.resources :receipts
1690
+ # mapper.resources :returns if options[:returnable]
1691
+ # end
1692
+ # end
1693
+ #
1694
+ # # routes.rb
1695
+ # concern :purchasable, Purchasable.new(returnable: true)
1696
+ #
1697
+ # resources :toys, concerns: :purchasable
1698
+ # resources :electronics, concerns: :purchasable
1699
+ # resources :pets do
1700
+ # concerns :purchasable, returnable: false
1701
+ # end
1702
+ #
1703
+ # Any routing helpers can be used inside a concern. If using a
1704
+ # callable, they're accessible from the Mapper that's passed to
1705
+ # <tt>call</tt>.
1706
+ def concern(name, callable = nil, &block)
1707
+ callable ||= lambda { |mapper, options| mapper.instance_exec(options, &block) }
1708
+ @concerns[name] = callable
1709
+ end
1710
+
1711
+ # Use the named concerns
1712
+ #
1713
+ # resources :posts do
1714
+ # concerns :commentable
1715
+ # end
1716
+ #
1717
+ # concerns also work in any routes helper that you want to use:
1718
+ #
1719
+ # namespace :posts do
1720
+ # concerns :commentable
1721
+ # end
1722
+ def concerns(*args)
1723
+ options = args.extract_options!
1724
+ args.flatten.each do |name|
1725
+ if concern = @concerns[name]
1726
+ concern.call(self, options)
1727
+ else
1728
+ raise ArgumentError, "No concern named #{name} was found!"
1729
+ end
1730
+ end
1731
+ end
1506
1732
  end
1507
1733
 
1508
1734
  def initialize(set) #:nodoc:
1509
1735
  @set = set
1510
1736
  @scope = { :path_names => @set.resources_path_names }
1737
+ @concerns = {}
1511
1738
  end
1512
1739
 
1513
1740
  include Base
1514
1741
  include HttpHelpers
1515
1742
  include Redirection
1516
1743
  include Scoping
1744
+ include Concerns
1517
1745
  include Resources
1518
1746
  end
1519
1747
  end