actionpack 1.13.6 → 2.0.0

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

Potentially problematic release.


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

Files changed (317) hide show
  1. data/CHANGELOG +1400 -20
  2. data/MIT-LICENSE +1 -1
  3. data/README +5 -5
  4. data/RUNNING_UNIT_TESTS +4 -5
  5. data/Rakefile +5 -6
  6. data/install.rb +2 -2
  7. data/lib/action_controller.rb +11 -15
  8. data/lib/action_controller/assertions.rb +12 -25
  9. data/lib/action_controller/assertions/dom_assertions.rb +18 -4
  10. data/lib/action_controller/assertions/model_assertions.rb +8 -1
  11. data/lib/action_controller/assertions/response_assertions.rb +35 -12
  12. data/lib/action_controller/assertions/routing_assertions.rb +56 -12
  13. data/lib/action_controller/assertions/selector_assertions.rb +105 -38
  14. data/lib/action_controller/assertions/tag_assertions.rb +28 -15
  15. data/lib/action_controller/base.rb +318 -250
  16. data/lib/action_controller/benchmarking.rb +33 -29
  17. data/lib/action_controller/caching.rb +130 -64
  18. data/lib/action_controller/cgi_ext.rb +16 -0
  19. data/lib/action_controller/cgi_ext/{cookie_performance_fix.rb → cookie.rb} +25 -40
  20. data/lib/action_controller/cgi_ext/query_extension.rb +22 -0
  21. data/lib/action_controller/cgi_ext/session.rb +73 -0
  22. data/lib/action_controller/cgi_ext/stdinput.rb +23 -0
  23. data/lib/action_controller/cgi_process.rb +34 -57
  24. data/lib/action_controller/components.rb +19 -36
  25. data/lib/action_controller/cookies.rb +10 -9
  26. data/lib/action_controller/dispatcher.rb +195 -0
  27. data/lib/action_controller/filters.rb +35 -34
  28. data/lib/action_controller/flash.rb +30 -35
  29. data/lib/action_controller/helpers.rb +121 -47
  30. data/lib/action_controller/http_authentication.rb +126 -0
  31. data/lib/action_controller/integration.rb +105 -101
  32. data/lib/action_controller/layout.rb +59 -47
  33. data/lib/action_controller/mime_responds.rb +57 -68
  34. data/lib/action_controller/mime_type.rb +43 -80
  35. data/lib/action_controller/mime_types.rb +20 -0
  36. data/lib/action_controller/polymorphic_routes.rb +88 -0
  37. data/lib/action_controller/record_identifier.rb +91 -0
  38. data/lib/action_controller/request.rb +553 -88
  39. data/lib/action_controller/request_forgery_protection.rb +126 -0
  40. data/lib/action_controller/request_profiler.rb +138 -0
  41. data/lib/action_controller/rescue.rb +185 -69
  42. data/lib/action_controller/resources.rb +211 -172
  43. data/lib/action_controller/response.rb +49 -8
  44. data/lib/action_controller/routing.rb +359 -236
  45. data/lib/action_controller/routing_optimisation.rb +119 -0
  46. data/lib/action_controller/session/active_record_store.rb +3 -2
  47. data/lib/action_controller/session/cookie_store.rb +161 -0
  48. data/lib/action_controller/session/mem_cache_store.rb +9 -16
  49. data/lib/action_controller/session_management.rb +17 -8
  50. data/lib/action_controller/streaming.rb +6 -3
  51. data/lib/action_controller/templates/rescues/_request_and_response.erb +24 -0
  52. data/lib/action_controller/templates/rescues/{_trace.rhtml → _trace.erb} +0 -0
  53. data/lib/action_controller/templates/rescues/{diagnostics.rhtml → diagnostics.erb} +2 -2
  54. data/lib/action_controller/templates/rescues/{layout.rhtml → layout.erb} +0 -0
  55. data/lib/action_controller/templates/rescues/{missing_template.rhtml → missing_template.erb} +0 -0
  56. data/lib/action_controller/templates/rescues/{routing_error.rhtml → routing_error.erb} +0 -0
  57. data/lib/action_controller/templates/rescues/{template_error.rhtml → template_error.erb} +2 -2
  58. data/lib/action_controller/templates/rescues/{unknown_action.rhtml → unknown_action.erb} +0 -0
  59. data/lib/action_controller/test_case.rb +53 -0
  60. data/lib/action_controller/test_process.rb +59 -46
  61. data/lib/action_controller/url_rewriter.rb +48 -24
  62. data/lib/action_controller/vendor/html-scanner/html/document.rb +7 -4
  63. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +173 -0
  64. data/lib/action_controller/vendor/html-scanner/html/selector.rb +11 -6
  65. data/lib/action_controller/verification.rb +27 -21
  66. data/lib/action_pack.rb +1 -1
  67. data/lib/action_pack/version.rb +4 -4
  68. data/lib/action_view.rb +2 -3
  69. data/lib/action_view/base.rb +218 -63
  70. data/lib/action_view/compiled_templates.rb +1 -2
  71. data/lib/action_view/helpers/active_record_helper.rb +35 -17
  72. data/lib/action_view/helpers/asset_tag_helper.rb +395 -87
  73. data/lib/action_view/helpers/atom_feed_helper.rb +111 -0
  74. data/lib/action_view/helpers/benchmark_helper.rb +12 -5
  75. data/lib/action_view/helpers/cache_helper.rb +29 -0
  76. data/lib/action_view/helpers/capture_helper.rb +97 -63
  77. data/lib/action_view/helpers/date_helper.rb +295 -35
  78. data/lib/action_view/helpers/debug_helper.rb +6 -2
  79. data/lib/action_view/helpers/form_helper.rb +354 -111
  80. data/lib/action_view/helpers/form_options_helper.rb +171 -109
  81. data/lib/action_view/helpers/form_tag_helper.rb +332 -76
  82. data/lib/action_view/helpers/javascript_helper.rb +35 -11
  83. data/lib/action_view/helpers/javascripts/controls.js +484 -354
  84. data/lib/action_view/helpers/javascripts/dragdrop.js +88 -58
  85. data/lib/action_view/helpers/javascripts/effects.js +396 -364
  86. data/lib/action_view/helpers/javascripts/prototype.js +2817 -1107
  87. data/lib/action_view/helpers/number_helper.rb +84 -60
  88. data/lib/action_view/helpers/prototype_helper.rb +419 -43
  89. data/lib/action_view/helpers/record_identification_helper.rb +20 -0
  90. data/lib/action_view/helpers/record_tag_helper.rb +59 -0
  91. data/lib/action_view/helpers/sanitize_helper.rb +223 -0
  92. data/lib/action_view/helpers/scriptaculous_helper.rb +63 -4
  93. data/lib/action_view/helpers/tag_helper.rb +69 -39
  94. data/lib/action_view/helpers/text_helper.rb +221 -148
  95. data/lib/action_view/helpers/url_helper.rb +283 -165
  96. data/lib/action_view/partials.rb +134 -62
  97. data/lib/action_view/template_error.rb +4 -12
  98. data/lib/actionpack.rb +1 -0
  99. data/test/abstract_unit.rb +21 -1
  100. data/test/action_view_test.rb +26 -0
  101. data/test/active_record_unit.rb +12 -20
  102. data/test/activerecord/active_record_store_test.rb +2 -2
  103. data/test/activerecord/render_partial_with_record_identification_test.rb +74 -0
  104. data/test/controller/action_pack_assertions_test.rb +21 -152
  105. data/test/controller/addresses_render_test.rb +2 -7
  106. data/test/controller/assert_select_test.rb +120 -14
  107. data/test/controller/base_test.rb +11 -13
  108. data/test/controller/caching_test.rb +125 -5
  109. data/test/controller/capture_test.rb +23 -16
  110. data/test/controller/cgi_test.rb +66 -391
  111. data/test/controller/components_test.rb +31 -42
  112. data/test/controller/content_type_test.rb +1 -1
  113. data/test/controller/cookie_test.rb +42 -14
  114. data/test/controller/deprecation/deprecated_base_methods_test.rb +1 -42
  115. data/test/controller/dispatcher_test.rb +123 -0
  116. data/test/controller/fake_models.rb +5 -0
  117. data/test/controller/filters_test.rb +44 -7
  118. data/test/controller/flash_test.rb +46 -2
  119. data/test/controller/fragment_store_setting_test.rb +10 -8
  120. data/test/controller/helper_test.rb +19 -2
  121. data/test/controller/html-scanner/document_test.rb +124 -0
  122. data/test/controller/html-scanner/node_test.rb +69 -0
  123. data/test/controller/html-scanner/sanitizer_test.rb +250 -0
  124. data/test/controller/html-scanner/tag_node_test.rb +239 -0
  125. data/test/controller/html-scanner/text_node_test.rb +51 -0
  126. data/test/controller/html-scanner/tokenizer_test.rb +125 -0
  127. data/test/controller/http_authentication_test.rb +54 -0
  128. data/test/controller/integration_test.rb +12 -26
  129. data/test/controller/layout_test.rb +64 -12
  130. data/test/controller/mime_responds_test.rb +193 -38
  131. data/test/controller/mime_type_test.rb +30 -8
  132. data/test/controller/new_render_test.rb +104 -22
  133. data/test/controller/polymorphic_routes_test.rb +98 -0
  134. data/test/controller/record_identifier_test.rb +103 -0
  135. data/test/controller/redirect_test.rb +120 -18
  136. data/test/controller/render_test.rb +195 -45
  137. data/test/controller/request_forgery_protection_test.rb +217 -0
  138. data/test/controller/request_test.rb +545 -27
  139. data/test/controller/rescue_test.rb +501 -0
  140. data/test/controller/resources_test.rb +258 -132
  141. data/test/controller/routing_test.rb +502 -106
  142. data/test/controller/selector_test.rb +5 -5
  143. data/test/controller/send_file_test.rb +17 -7
  144. data/test/controller/session/cookie_store_test.rb +246 -0
  145. data/test/controller/session/mem_cache_store_test.rb +182 -0
  146. data/test/controller/session_fixation_test.rb +8 -11
  147. data/test/controller/session_management_test.rb +7 -7
  148. data/test/controller/test_test.rb +150 -38
  149. data/test/controller/url_rewriter_test.rb +87 -12
  150. data/test/controller/verification_test.rb +11 -0
  151. data/test/controller/view_paths_test.rb +137 -0
  152. data/test/controller/webservice_test.rb +11 -75
  153. data/test/fixtures/addresses/{list.rhtml → list.erb} +0 -0
  154. data/test/fixtures/db_definitions/sqlite.sql +2 -1
  155. data/test/fixtures/developer.rb +2 -0
  156. data/test/fixtures/fun/games/{hello_world.rhtml → hello_world.erb} +0 -0
  157. data/test/fixtures/helpers/fun/pdf_helper.rb +1 -1
  158. data/test/fixtures/layout_tests/alt/hello.rhtml +1 -0
  159. data/test/fixtures/layout_tests/layouts/multiple_extensions.html.erb +1 -0
  160. data/test/fixtures/layouts/{builder.rxml → builder.builder} +0 -0
  161. data/test/fixtures/layouts/{standard.rhtml → standard.erb} +0 -0
  162. data/test/fixtures/layouts/{talk_from_action.rhtml → talk_from_action.erb} +0 -0
  163. data/test/fixtures/layouts/{yield.rhtml → yield.erb} +0 -0
  164. data/test/fixtures/multipart/binary_file +0 -0
  165. data/test/fixtures/multipart/bracketed_param +5 -0
  166. data/test/fixtures/override/test/hello_world.erb +1 -0
  167. data/test/fixtures/override2/layouts/test/sub.erb +1 -0
  168. data/test/fixtures/post_test/layouts/post.html.erb +1 -0
  169. data/test/fixtures/post_test/layouts/super_post.iphone.erb +1 -0
  170. data/test/fixtures/post_test/post/index.html.erb +1 -0
  171. data/test/fixtures/post_test/post/index.iphone.erb +1 -0
  172. data/test/fixtures/post_test/super_post/index.html.erb +1 -0
  173. data/test/fixtures/post_test/super_post/index.iphone.erb +1 -0
  174. data/test/fixtures/public/404.html +1 -0
  175. data/test/fixtures/public/500.html +1 -0
  176. data/test/fixtures/public/javascripts/application.js +0 -1
  177. data/test/fixtures/public/javascripts/bank.js +1 -0
  178. data/test/fixtures/public/javascripts/robber.js +1 -0
  179. data/test/fixtures/public/stylesheets/bank.css +1 -0
  180. data/test/fixtures/public/stylesheets/robber.css +1 -0
  181. data/test/fixtures/replies.yml +2 -0
  182. data/test/fixtures/reply.rb +2 -1
  183. data/test/fixtures/respond_to/{all_types_with_layout.rhtml → all_types_with_layout.html.erb} +0 -0
  184. data/test/fixtures/respond_to/{all_types_with_layout.rjs → all_types_with_layout.js.rjs} +0 -0
  185. data/test/fixtures/respond_to/custom_constant_handling_without_block.mobile.erb +1 -0
  186. data/test/fixtures/respond_to/iphone_with_html_response_type.html.erb +1 -0
  187. data/test/fixtures/respond_to/iphone_with_html_response_type.iphone.erb +1 -0
  188. data/test/fixtures/respond_to/layouts/missing.html.erb +1 -0
  189. data/test/fixtures/respond_to/layouts/standard.html.erb +1 -0
  190. data/test/fixtures/respond_to/layouts/standard.iphone.erb +1 -0
  191. data/test/fixtures/respond_to/{using_defaults.rhtml → using_defaults.html.erb} +0 -0
  192. data/test/fixtures/respond_to/{using_defaults.rjs → using_defaults.js.rjs} +0 -0
  193. data/test/fixtures/respond_to/{using_defaults.rxml → using_defaults.xml.builder} +0 -0
  194. data/test/fixtures/respond_to/{using_defaults_with_type_list.rhtml → using_defaults_with_type_list.html.erb} +0 -0
  195. data/test/fixtures/respond_to/{using_defaults_with_type_list.rjs → using_defaults_with_type_list.js.rjs} +0 -0
  196. data/test/fixtures/respond_to/{using_defaults_with_type_list.rxml → using_defaults_with_type_list.xml.builder} +0 -0
  197. data/test/fixtures/scope/test/{modgreet.rhtml → modgreet.erb} +0 -0
  198. data/test/fixtures/test/{_customer.rhtml → _customer.erb} +0 -0
  199. data/test/fixtures/test/{_customer_greeting.rhtml → _customer_greeting.erb} +0 -0
  200. data/test/fixtures/test/_hash_greeting.erb +1 -0
  201. data/test/fixtures/test/_hash_object.erb +2 -0
  202. data/test/fixtures/test/{_hello.rxml → _hello.builder} +0 -0
  203. data/test/fixtures/test/_layout_for_partial.html.erb +3 -0
  204. data/test/fixtures/test/_partial.erb +1 -0
  205. data/test/fixtures/test/_partial.html.erb +1 -0
  206. data/test/fixtures/test/_partial.js.erb +1 -0
  207. data/test/fixtures/test/_partial_for_use_in_layout.html.erb +1 -0
  208. data/test/fixtures/test/{_partial_only.rhtml → _partial_only.erb} +0 -0
  209. data/test/fixtures/test/{_person.rhtml → _person.erb} +0 -0
  210. data/test/fixtures/test/{action_talk_to_layout.rhtml → action_talk_to_layout.erb} +0 -0
  211. data/test/fixtures/test/{block_content_for.rhtml → block_content_for.erb} +0 -0
  212. data/test/fixtures/test/calling_partial_with_layout.html.erb +1 -0
  213. data/test/fixtures/test/{capturing.rhtml → capturing.erb} +0 -0
  214. data/test/fixtures/test/{content_for.rhtml → content_for.erb} +0 -0
  215. data/test/fixtures/test/content_for_concatenated.erb +3 -0
  216. data/test/fixtures/test/content_for_with_parameter.erb +2 -0
  217. data/test/fixtures/test/dot.directory/{render_file_with_ivar.rhtml → render_file_with_ivar.erb} +0 -0
  218. data/test/fixtures/test/{erb_content_for.rhtml → erb_content_for.erb} +0 -0
  219. data/test/fixtures/test/formatted_html_erb.html.erb +1 -0
  220. data/test/fixtures/test/formatted_xml_erb.builder +1 -0
  221. data/test/fixtures/test/formatted_xml_erb.html.erb +1 -0
  222. data/test/fixtures/test/formatted_xml_erb.xml.erb +1 -0
  223. data/test/fixtures/test/{greeting.rhtml → greeting.erb} +0 -0
  224. data/test/fixtures/test/{hello.rxml → hello.builder} +0 -0
  225. data/test/fixtures/test/{hello_world.rxml → hello_world.builder} +0 -0
  226. data/test/fixtures/test/{hello_world.rhtml → hello_world.erb} +0 -0
  227. data/test/fixtures/test/{hello_world_container.rxml → hello_world_container.builder} +0 -0
  228. data/test/fixtures/test/{hello_world_with_layout_false.rhtml → hello_world_with_layout_false.erb} +0 -0
  229. data/test/fixtures/test/{hello_xml_world.rxml → hello_xml_world.builder} +0 -0
  230. data/test/fixtures/test/list.erb +1 -0
  231. data/test/fixtures/test/{non_erb_block_content_for.rxml → non_erb_block_content_for.builder} +0 -0
  232. data/test/fixtures/test/{potential_conflicts.rhtml → potential_conflicts.erb} +0 -0
  233. data/test/fixtures/test/{render_file_with_ivar.rhtml → render_file_with_ivar.erb} +0 -0
  234. data/test/fixtures/test/{render_file_with_locals.rhtml → render_file_with_locals.erb} +0 -0
  235. data/test/fixtures/test/{render_to_string_test.rhtml → render_to_string_test.erb} +0 -0
  236. data/test/fixtures/test/{update_element_with_capture.rhtml → update_element_with_capture.erb} +0 -0
  237. data/test/fixtures/test/using_layout_around_block.html.erb +1 -0
  238. data/test/fixtures/topic.rb +1 -1
  239. data/test/template/active_record_helper_test.rb +67 -20
  240. data/test/template/asset_tag_helper_test.rb +222 -54
  241. data/test/template/atom_feed_helper_test.rb +101 -0
  242. data/test/template/benchmark_helper_test.rb +2 -2
  243. data/test/template/compiled_templates_test.rb +76 -32
  244. data/test/template/date_helper_test.rb +125 -9
  245. data/test/template/form_helper_test.rb +326 -33
  246. data/test/template/form_options_helper_test.rb +822 -15
  247. data/test/template/form_tag_helper_test.rb +96 -30
  248. data/test/template/javascript_helper_test.rb +61 -13
  249. data/test/template/number_helper_test.rb +12 -11
  250. data/test/template/prototype_helper_test.rb +185 -24
  251. data/test/template/sanitize_helper_test.rb +49 -0
  252. data/test/template/scriptaculous_helper_test.rb +9 -3
  253. data/test/template/tag_helper_test.rb +13 -2
  254. data/test/template/text_helper_test.rb +38 -52
  255. data/test/template/url_helper_test.rb +216 -46
  256. metadata +144 -116
  257. data/examples/.htaccess +0 -24
  258. data/examples/address_book/index.rhtml +0 -33
  259. data/examples/address_book/layout.rhtml +0 -8
  260. data/examples/address_book_controller.cgi +0 -9
  261. data/examples/address_book_controller.fcgi +0 -6
  262. data/examples/address_book_controller.rb +0 -52
  263. data/examples/address_book_controller.rbx +0 -4
  264. data/examples/benchmark.rb +0 -52
  265. data/examples/benchmark_with_ar.fcgi +0 -89
  266. data/examples/blog_controller.cgi +0 -53
  267. data/examples/debate/index.rhtml +0 -14
  268. data/examples/debate/new_topic.rhtml +0 -22
  269. data/examples/debate/topic.rhtml +0 -32
  270. data/examples/debate_controller.cgi +0 -57
  271. data/lib/action_controller/assertions/deprecated_assertions.rb +0 -228
  272. data/lib/action_controller/cgi_ext/cgi_ext.rb +0 -36
  273. data/lib/action_controller/cgi_ext/cgi_methods.rb +0 -211
  274. data/lib/action_controller/cgi_ext/pstore_performance_fix.rb +0 -30
  275. data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +0 -95
  276. data/lib/action_controller/cgi_ext/session_performance_fix.rb +0 -30
  277. data/lib/action_controller/deprecated_dependencies.rb +0 -65
  278. data/lib/action_controller/deprecated_redirects.rb +0 -17
  279. data/lib/action_controller/deprecated_request_methods.rb +0 -34
  280. data/lib/action_controller/macros/auto_complete.rb +0 -53
  281. data/lib/action_controller/macros/in_place_editing.rb +0 -33
  282. data/lib/action_controller/pagination.rb +0 -408
  283. data/lib/action_controller/scaffolding.rb +0 -189
  284. data/lib/action_controller/templates/rescues/_request_and_response.rhtml +0 -44
  285. data/lib/action_controller/templates/scaffolds/edit.rhtml +0 -7
  286. data/lib/action_controller/templates/scaffolds/layout.rhtml +0 -69
  287. data/lib/action_controller/templates/scaffolds/list.rhtml +0 -27
  288. data/lib/action_controller/templates/scaffolds/new.rhtml +0 -6
  289. data/lib/action_controller/templates/scaffolds/show.rhtml +0 -9
  290. data/lib/action_controller/vendor/xml_node.rb +0 -97
  291. data/lib/action_view/helpers/deprecated_helper.rb +0 -37
  292. data/lib/action_view/helpers/java_script_macros_helper.rb +0 -233
  293. data/lib/action_view/helpers/pagination_helper.rb +0 -86
  294. data/test/activerecord/active_record_assertions_test.rb +0 -92
  295. data/test/activerecord/pagination_test.rb +0 -165
  296. data/test/controller/deprecated_instance_variables_test.rb +0 -48
  297. data/test/controller/raw_post_test.rb +0 -68
  298. data/test/fixtures/deprecated_instance_variables/_cookies_ivar.rhtml +0 -1
  299. data/test/fixtures/deprecated_instance_variables/_cookies_method.rhtml +0 -1
  300. data/test/fixtures/deprecated_instance_variables/_flash_ivar.rhtml +0 -1
  301. data/test/fixtures/deprecated_instance_variables/_flash_method.rhtml +0 -1
  302. data/test/fixtures/deprecated_instance_variables/_headers_ivar.rhtml +0 -1
  303. data/test/fixtures/deprecated_instance_variables/_headers_method.rhtml +0 -1
  304. data/test/fixtures/deprecated_instance_variables/_params_ivar.rhtml +0 -1
  305. data/test/fixtures/deprecated_instance_variables/_params_method.rhtml +0 -1
  306. data/test/fixtures/deprecated_instance_variables/_request_ivar.rhtml +0 -1
  307. data/test/fixtures/deprecated_instance_variables/_request_method.rhtml +0 -1
  308. data/test/fixtures/deprecated_instance_variables/_response_ivar.rhtml +0 -1
  309. data/test/fixtures/deprecated_instance_variables/_response_method.rhtml +0 -1
  310. data/test/fixtures/deprecated_instance_variables/_session_ivar.rhtml +0 -1
  311. data/test/fixtures/deprecated_instance_variables/_session_method.rhtml +0 -1
  312. data/test/fixtures/respond_to/layouts/standard.rhtml +0 -1
  313. data/test/fixtures/test/_hash_object.rhtml +0 -1
  314. data/test/fixtures/test/list.rhtml +0 -1
  315. data/test/template/deprecated_helper_test.rb +0 -36
  316. data/test/template/deprecated_instance_variables_test.rb +0 -43
  317. data/test/template/java_script_macros_helper_test.rb +0 -109
@@ -1,6 +1,9 @@
1
+ require 'digest/md5'
2
+
1
3
  module ActionController
2
4
  class AbstractResponse #:nodoc:
3
5
  DEFAULT_HEADERS = { "Cache-Control" => "no-cache" }
6
+ attr_accessor :request
4
7
  attr_accessor :body, :headers, :session, :cookies, :assigns, :template, :redirected_to, :redirected_to_method_params, :layout
5
8
 
6
9
  def initialize
@@ -8,28 +11,66 @@ module ActionController
8
11
  end
9
12
 
10
13
  def content_type=(mime_type)
11
- @headers["Content-Type"] = charset ? "#{mime_type}; charset=#{charset}" : mime_type
14
+ self.headers["Content-Type"] = charset ? "#{mime_type}; charset=#{charset}" : mime_type
12
15
  end
13
16
 
14
17
  def content_type
15
- content_type = String(@headers["Content-Type"]).split(";")[0]
18
+ content_type = String(headers["Content-Type"] || headers["type"]).split(";")[0]
16
19
  content_type.blank? ? nil : content_type
17
20
  end
18
21
 
19
22
  def charset=(encoding)
20
- @headers["Content-Type"] = "#{content_type || "text/html"}; charset=#{encoding}"
23
+ self.headers["Content-Type"] = "#{content_type || Mime::HTML}; charset=#{encoding}"
21
24
  end
22
25
 
23
26
  def charset
24
- charset = String(@headers["Content-Type"]).split(";")[1]
27
+ charset = String(headers["Content-Type"] || headers["type"]).split(";")[1]
25
28
  charset.blank? ? nil : charset.strip.split("=")[1]
26
29
  end
27
30
 
28
- def redirect(to_url, permanently = false)
29
- @headers["Status"] = "302 Found" unless @headers["Status"] == "301 Moved Permanently"
30
- @headers["Location"] = to_url
31
+ def redirect(to_url, response_status)
32
+ self.headers["Status"] = response_status
33
+ self.headers["Location"] = to_url
34
+
35
+ self.body = "<html><body>You are being <a href=\"#{to_url}\">redirected</a>.</body></html>"
36
+ end
31
37
 
32
- @body = "<html><body>You are being <a href=\"#{to_url}\">redirected</a>.</body></html>"
38
+ def prepare!
39
+ handle_conditional_get!
40
+ convert_content_type!
41
+ set_content_length!
33
42
  end
43
+
44
+
45
+ private
46
+ def handle_conditional_get!
47
+ if body.is_a?(String) && (headers['Status'] ? headers['Status'][0..2] == '200' : true) && !body.empty?
48
+ self.headers['ETag'] ||= %("#{Digest::MD5.hexdigest(body)}")
49
+ self.headers['Cache-Control'] = 'private, max-age=0, must-revalidate' if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control']
50
+
51
+ if request.headers['HTTP_IF_NONE_MATCH'] == headers['ETag']
52
+ self.headers['Status'] = '304 Not Modified'
53
+ self.body = ''
54
+ end
55
+ end
56
+ end
57
+
58
+ def convert_content_type!
59
+ if content_type = headers.delete("Content-Type")
60
+ self.headers["type"] = content_type
61
+ end
62
+ if content_type = headers.delete("Content-type")
63
+ self.headers["type"] = content_type
64
+ end
65
+ if content_type = headers.delete("content-type")
66
+ self.headers["type"] = content_type
67
+ end
68
+ end
69
+
70
+ # Don't set the Content-Length for block-based bodies as that would mean reading it all into memory. Not nice
71
+ # for, say, a 2GB streaming file.
72
+ def set_content_length!
73
+ self.headers["Content-Length"] = body.size unless body.respond_to?(:call)
74
+ end
34
75
  end
35
76
  end
@@ -1,4 +1,7 @@
1
1
  require 'cgi'
2
+ require 'uri'
3
+ require 'action_controller/polymorphic_routes'
4
+ require 'action_controller/routing_optimisation'
2
5
 
3
6
  class Object
4
7
  def to_param
@@ -28,7 +31,7 @@ class Regexp #:nodoc:
28
31
  def number_of_captures
29
32
  Regexp.new("|#{source}").match('').captures.length
30
33
  end
31
-
34
+
32
35
  class << self
33
36
  def optionalize(pattern)
34
37
  case unoptionalize(pattern)
@@ -36,7 +39,7 @@ class Regexp #:nodoc:
36
39
  else "(?:#{pattern})?"
37
40
  end
38
41
  end
39
-
42
+
40
43
  def unoptionalize(pattern)
41
44
  [/\A\(\?:(.*)\)\?\Z/, /\A(.|\(.*\))\?\Z/].each do |regexp|
42
45
  return $1 if regexp =~ pattern
@@ -47,30 +50,30 @@ class Regexp #:nodoc:
47
50
  end
48
51
 
49
52
  module ActionController
50
- # == Routing
53
+ # == Routing
51
54
  #
52
55
  # The routing module provides URL rewriting in native Ruby. It's a way to
53
56
  # redirect incoming requests to controllers and actions. This replaces
54
- # mod_rewrite rules. Best of all Rails' Routing works with any web server.
57
+ # mod_rewrite rules. Best of all, Rails' Routing works with any web server.
55
58
  # Routes are defined in routes.rb in your RAILS_ROOT/config directory.
56
59
  #
57
- # Consider the following route, installed by Rails when you generate your
60
+ # Consider the following route, installed by Rails when you generate your
58
61
  # application:
59
62
  #
60
63
  # map.connect ':controller/:action/:id'
61
64
  #
62
- # This route states that it expects requests to consist of a
63
- # :controller followed by an :action that in turns is fed by some :id
65
+ # This route states that it expects requests to consist of a
66
+ # :controller followed by an :action that in turn is fed some :id.
64
67
  #
65
- # Suppose you get an incoming request for <tt>/blog/edit/22</tt>, you'll end up
68
+ # Suppose you get an incoming request for <tt>/blog/edit/22</tt>, you'll end up
66
69
  # with:
67
70
  #
68
71
  # params = { :controller => 'blog',
69
- # :action => 'edit'
72
+ # :action => 'edit',
70
73
  # :id => '22'
71
74
  # }
72
75
  #
73
- # Think of creating routes as drawing a map for your requests. The map tells
76
+ # Think of creating routes as drawing a map for your requests. The map tells
74
77
  # them where to go based on some predefined pattern:
75
78
  #
76
79
  # ActionController::Routing::Routes.draw do |map|
@@ -83,45 +86,45 @@ module ActionController
83
86
  #
84
87
  # :controller maps to your controller name
85
88
  # :action maps to an action with your controllers
86
- #
89
+ #
87
90
  # Other names simply map to a parameter as in the case of +:id+.
88
- #
91
+ #
89
92
  # == Route priority
90
93
  #
91
- # Not all routes are created equally. Routes have priority defined by the
94
+ # Not all routes are created equally. Routes have priority defined by the
92
95
  # order of appearance of the routes in the routes.rb file. The priority goes
93
96
  # from top to bottom. The last route in that file is at the lowest priority
94
- # will be applied last. If no route matches, 404 is returned.
97
+ # and will be applied last. If no route matches, 404 is returned.
95
98
  #
96
- # Within blocks, the empty pattern goes first i.e. is at the highest priority.
99
+ # Within blocks, the empty pattern is at the highest priority.
97
100
  # In practice this works out nicely:
98
101
  #
99
- # ActionController::Routing::Routes.draw do |map|
102
+ # ActionController::Routing::Routes.draw do |map|
100
103
  # map.with_options :controller => 'blog' do |blog|
101
- # blog.show '', :action => 'list'
104
+ # blog.show '', :action => 'list'
102
105
  # end
103
- # map.connect ':controller/:action/:view
106
+ # map.connect ':controller/:action/:view'
104
107
  # end
105
108
  #
106
- # In this case, invoking blog controller (with an URL like '/blog/')
109
+ # In this case, invoking blog controller (with an URL like '/blog/')
107
110
  # without parameters will activate the 'list' action by default.
108
111
  #
109
112
  # == Defaults routes and default parameters
110
113
  #
111
- # Setting a default route is straightforward in Rails because by appending a
112
- # Hash to the end of your mapping you can set default parameters.
114
+ # Setting a default route is straightforward in Rails - you simply append a
115
+ # Hash at the end of your mapping to set any default parameters.
113
116
  #
114
117
  # Example:
115
118
  # ActionController::Routing:Routes.draw do |map|
116
119
  # map.connect ':controller/:action/:id', :controller => 'blog'
117
120
  # end
118
121
  #
119
- # This sets up +blog+ as the default controller if no other is specified.
122
+ # This sets up +blog+ as the default controller if no other is specified.
120
123
  # This means visiting '/' would invoke the blog controller.
121
124
  #
122
125
  # More formally, you can define defaults in a route with the +:defaults+ key.
123
- #
124
- # map.connect ':controller/:id/:action', :action => 'show', :defaults => { :page => 'Dashboard' }
126
+ #
127
+ # map.connect ':controller/:action/:id', :action => 'show', :defaults => { :page => 'Dashboard' }
125
128
  #
126
129
  # == Named routes
127
130
  #
@@ -140,7 +143,7 @@ module ActionController
140
143
  #
141
144
  # redirect_to show_item_path(:id => 25)
142
145
  #
143
- # Use <tt>map.root</tt> as a shorthand to name a route for the root path ""
146
+ # Use <tt>map.root</tt> as a shorthand to name a route for the root path "".
144
147
  #
145
148
  # # In routes.rb
146
149
  # map.root :controller => 'blogs'
@@ -163,73 +166,93 @@ module ActionController
163
166
  # end
164
167
  #
165
168
  # # provides named routes for show, delete, and edit
166
- # link_to @article.title, show_path(:id => @article.id)
169
+ # link_to @article.title, show_path(:id => @article.id)
167
170
  #
168
171
  # == Pretty URLs
169
172
  #
170
173
  # Routes can generate pretty URLs. For example:
171
174
  #
172
175
  # map.connect 'articles/:year/:month/:day',
173
- # :controller => 'articles',
176
+ # :controller => 'articles',
174
177
  # :action => 'find_by_date',
175
178
  # :year => /\d{4}/,
176
- # :month => /\d{1,2}/,
177
- # :day => /\d{1,2}/
178
- #
179
+ # :month => /\d{1,2}/,
180
+ # :day => /\d{1,2}/
181
+ #
179
182
  # # Using the route above, the url below maps to:
180
183
  # # params = {:year => '2005', :month => '11', :day => '06'}
181
184
  # # http://localhost:3000/articles/2005/11/06
182
185
  #
183
186
  # == Regular Expressions and parameters
184
- # You can specify a reqular expression to define a format for a parameter.
187
+ # You can specify a regular expression to define a format for a parameter.
185
188
  #
186
189
  # map.geocode 'geocode/:postalcode', :controller => 'geocode',
187
190
  # :action => 'show', :postalcode => /\d{5}(-\d{4})?/
188
191
  #
189
- # or more formally:
192
+ # or, more formally:
190
193
  #
191
- # map.geocode 'geocode/:postalcode', :controller => 'geocode',
192
- # :action => 'show',
193
- # :requirements { :postalcode => /\d{5}(-\d{4})?/ }
194
+ # map.geocode 'geocode/:postalcode', :controller => 'geocode',
195
+ # :action => 'show', :requirements => { :postalcode => /\d{5}(-\d{4})?/ }
194
196
  #
195
197
  # == Route globbing
196
198
  #
197
- # Specifying <tt>*[string]</tt> as part of a rule like :
199
+ # Specifying <tt>*[string]</tt> as part of a rule like:
198
200
  #
199
201
  # map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?'
200
202
  #
201
- # will glob all remaining parts of the route that were not recognized earlier. This idiom must appear at the end of the path. The globbed values are in <tt>params[:path]</tt> in this case.
203
+ # will glob all remaining parts of the route that were not recognized earlier. This idiom
204
+ # must appear at the end of the path. The globbed values are in <tt>params[:path]</tt> in
205
+ # this case.
206
+ #
207
+ # == Route conditions
208
+ #
209
+ # With conditions you can define restrictions on routes. Currently the only valid condition is <tt>:method</tt>.
210
+ #
211
+ # * <tt>:method</tt> - Allows you to specify which method can access the route. Possible values are <tt>:post</tt>,
212
+ # <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>. The default value is <tt>:any</tt>,
213
+ # <tt>:any</tt> means that any method can access the route.
214
+ #
215
+ # Example:
216
+ #
217
+ # map.connect 'post/:id', :controller => 'posts', :action => 'show',
218
+ # :conditions => { :method => :get }
219
+ # map.connect 'post/:id', :controller => 'posts', :action => 'create_comment',
220
+ # :conditions => { :method => :post }
202
221
  #
222
+ # Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
223
+ # URL will route to the <tt>show</tt> action.
224
+ #
203
225
  # == Reloading routes
204
226
  #
205
227
  # You can reload routes if you feel you must:
206
228
  #
207
- # Action::Controller::Routes.reload
229
+ # ActionController::Routing::Routes.reload
208
230
  #
209
- # This will clear all named routes and reload routes.rb
231
+ # This will clear all named routes and reload routes.rb if the file has been modified from
232
+ # last load. To absolutely force reloading, use +reload!+.
210
233
  #
211
234
  # == Testing Routes
212
235
  #
213
236
  # The two main methods for testing your routes:
214
237
  #
215
238
  # === +assert_routing+
216
- #
239
+ #
217
240
  # def test_movie_route_properly_splits
218
241
  # opts = {:controller => "plugin", :action => "checkout", :id => "2"}
219
242
  # assert_routing "plugin/checkout/2", opts
220
243
  # end
221
- #
244
+ #
222
245
  # +assert_routing+ lets you test whether or not the route properly resolves into options.
223
246
  #
224
247
  # === +assert_recognizes+
225
248
  #
226
249
  # def test_route_has_options
227
250
  # opts = {:controller => "plugin", :action => "show", :id => "12"}
228
- # assert_recognizes opts, "/plugins/show/12"
251
+ # assert_recognizes opts, "/plugins/show/12"
229
252
  # end
230
- #
253
+ #
231
254
  # Note the subtle difference between the two: +assert_routing+ tests that
232
- # an URL fits options while +assert_recognizes+ tests that an URL
255
+ # a URL fits options while +assert_recognizes+ tests that a URL
233
256
  # breaks into parameters properly.
234
257
  #
235
258
  # In tests you can simply pass the URL or named route to +get+ or +post+.
@@ -246,12 +269,21 @@ module ActionController
246
269
  # end
247
270
  #
248
271
  module Routing
249
- SEPARATORS = %w( / ; . , ? )
272
+ SEPARATORS = %w( / . ? )
273
+
274
+ HTTP_METHODS = [:get, :head, :post, :put, :delete]
275
+
276
+ ALLOWED_REQUIREMENTS_FOR_OPTIMISATION = [:controller, :action].to_set
250
277
 
251
278
  # The root paths which may contain controller files
252
279
  mattr_accessor :controller_paths
253
280
  self.controller_paths = []
254
281
 
282
+ # A helper module to hold URL related helpers.
283
+ module Helpers
284
+ include PolymorphicRoutes
285
+ end
286
+
255
287
  class << self
256
288
  def with_controllers(names)
257
289
  prior_controllers = @possible_controllers
@@ -282,16 +314,16 @@ module ActionController
282
314
  def possible_controllers
283
315
  unless @possible_controllers
284
316
  @possible_controllers = []
285
-
317
+
286
318
  paths = controller_paths.select { |path| File.directory?(path) && path != "." }
287
319
 
288
320
  seen_paths = Hash.new {|h, k| h[k] = true; false}
289
321
  normalize_paths(paths).each do |load_path|
290
322
  Dir["#{load_path}/**/*_controller.rb"].collect do |path|
291
323
  next if seen_paths[path.gsub(%r{^\.[/\\]}, "")]
292
-
324
+
293
325
  controller_name = path[(load_path.length + 1)..-1]
294
-
326
+
295
327
  controller_name.gsub!(/_controller\.rb\Z/, '')
296
328
  @possible_controllers << controller_name
297
329
  end
@@ -312,24 +344,37 @@ module ActionController
312
344
  elsif controller[0] == ?/ then controller[1..-1]
313
345
  elsif %r{^(.*)/} =~ previous then "#{$1}/#{controller}"
314
346
  else controller
315
- end
316
- end
347
+ end
348
+ end
317
349
  end
318
-
350
+
319
351
  class Route #:nodoc:
320
- attr_accessor :segments, :requirements, :conditions
321
-
352
+ attr_accessor :segments, :requirements, :conditions, :optimise
353
+
322
354
  def initialize
323
355
  @segments = []
324
356
  @requirements = {}
325
357
  @conditions = {}
358
+ @optimise = true
326
359
  end
327
-
360
+
361
+ # Indicates whether the routes should be optimised with the string interpolation
362
+ # version of the named routes methods.
363
+ def optimise?
364
+ @optimise && ActionController::Base::optimise_named_routes
365
+ end
366
+
367
+ def segment_keys
368
+ segments.collect do |segment|
369
+ segment.key if segment.respond_to? :key
370
+ end.compact
371
+ end
372
+
328
373
  # Write and compile a +generate+ method for this Route.
329
374
  def write_generation
330
375
  # Build the main body of the generation
331
376
  body = "expired = false\n#{generation_extraction}\n#{generation_structure}"
332
-
377
+
333
378
  # If we have conditions that must be tested first, nest the body inside an if
334
379
  body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements
335
380
  args = "options, hash, expire_on = {}"
@@ -351,7 +396,7 @@ module ActionController
351
396
  instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
352
397
  raw_method
353
398
  end
354
-
399
+
355
400
  # Build several lines of code that extract values from the options hash. If any
356
401
  # of the values are missing or rejected then a return will be executed.
357
402
  def generation_extraction
@@ -359,7 +404,7 @@ module ActionController
359
404
  segment.extraction_code
360
405
  end.compact * "\n"
361
406
  end
362
-
407
+
363
408
  # Produce a condition expression that will check the requirements of this route
364
409
  # upon generation.
365
410
  def generation_requirements
@@ -373,16 +418,17 @@ module ActionController
373
418
  end
374
419
  requirement_conditions * ' && ' unless requirement_conditions.empty?
375
420
  end
421
+
376
422
  def generation_structure
377
423
  segments.last.string_structure segments[0..-2]
378
424
  end
379
-
425
+
380
426
  # Write and compile a +recognize+ method for this Route.
381
427
  def write_recognition
382
428
  # Create an if structure to extract the params from a match if it occurs.
383
429
  body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams"
384
430
  body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend"
385
-
431
+
386
432
  # Build the method declaration and compile it
387
433
  method_decl = "def recognize(path, env={})\n#{body}\nend"
388
434
  instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
@@ -406,18 +452,18 @@ module ActionController
406
452
  end
407
453
  wrap ? ("\\A" + pattern + "\\Z") : pattern
408
454
  end
409
-
455
+
410
456
  # Write the code to extract the parameters from a matched route.
411
457
  def recognition_extraction
412
458
  next_capture = 1
413
459
  extraction = segments.collect do |segment|
414
- x = segment.match_extraction next_capture
460
+ x = segment.match_extraction(next_capture)
415
461
  next_capture += Regexp.new(segment.regexp_chunk).number_of_captures
416
462
  x
417
463
  end
418
464
  extraction.compact
419
465
  end
420
-
466
+
421
467
  # Write the real generation implementation and then resend the message.
422
468
  def generate(options, hash, expire_on = {})
423
469
  write_generation
@@ -453,12 +499,13 @@ module ActionController
453
499
  # values.
454
500
  def build_query_string(hash, only_keys = nil)
455
501
  elements = []
456
-
502
+
457
503
  (only_keys || hash.keys).each do |key|
458
504
  if value = hash[key]
459
505
  elements << value.to_query(key)
460
506
  end
461
507
  end
508
+
462
509
  elements.empty? ? '' : "?#{elements.sort * '&'}"
463
510
  end
464
511
 
@@ -467,14 +514,14 @@ module ActionController
467
514
  write_recognition
468
515
  recognize path, environment
469
516
  end
470
-
517
+
471
518
  # A route's parameter shell contains parameter values that are not in the
472
519
  # route's path, but should be placed in the recognized hash.
473
- #
520
+ #
474
521
  # For example, +{:controller => 'pages', :action => 'show'} is the shell for the route:
475
- #
522
+ #
476
523
  # map.connect '/page/:id', :controller => 'pages', :action => 'show', :id => /\d+/
477
- #
524
+ #
478
525
  def parameter_shell
479
526
  @parameter_shell ||= returning({}) do |shell|
480
527
  requirements.each do |key, requirement|
@@ -482,7 +529,7 @@ module ActionController
482
529
  end
483
530
  end
484
531
  end
485
-
532
+
486
533
  # Return an array containing all the keys that are used in this route. This
487
534
  # includes keys that appear inside the path, and keys that have requirements
488
535
  # placed upon them.
@@ -508,9 +555,9 @@ module ActionController
508
555
  end
509
556
  end
510
557
  end
511
-
558
+
512
559
  def matches_controller_and_action?(controller, action)
513
- unless @matching_prepared
560
+ unless defined? @matching_prepared
514
561
  @controller_requirement = requirement_for(:controller)
515
562
  @action_requirement = requirement_for(:action)
516
563
  @matching_prepared = true
@@ -526,7 +573,7 @@ module ActionController
526
573
  "%-6s %-40s %s" % [(conditions[:method] || :any).to_s.upcase, segs, requirements.inspect]
527
574
  end
528
575
  end
529
-
576
+
530
577
  protected
531
578
  def requirement_for(key)
532
579
  return requirements[key] if requirements.key? key
@@ -535,10 +582,13 @@ module ActionController
535
582
  end
536
583
  nil
537
584
  end
538
-
585
+
539
586
  end
540
587
 
541
588
  class Segment #:nodoc:
589
+ RESERVED_PCHAR = ':@&=+$,;'
590
+ UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze
591
+
542
592
  attr_accessor :is_optional
543
593
  alias_method :optional?, :is_optional
544
594
 
@@ -549,7 +599,7 @@ module ActionController
549
599
  def extraction_code
550
600
  nil
551
601
  end
552
-
602
+
553
603
  # Continue generating string for the prior segments.
554
604
  def continue_string_structure(prior_segments)
555
605
  if prior_segments.empty?
@@ -559,33 +609,37 @@ module ActionController
559
609
  prior_segments.last.string_structure(new_priors)
560
610
  end
561
611
  end
562
-
612
+
613
+ def interpolation_chunk
614
+ URI.escape(value, UNSAFE_PCHAR)
615
+ end
616
+
563
617
  # Return a string interpolation statement for this segment and those before it.
564
618
  def interpolation_statement(prior_segments)
565
619
  chunks = prior_segments.collect { |s| s.interpolation_chunk }
566
620
  chunks << interpolation_chunk
567
621
  "\"#{chunks * ''}\"#{all_optionals_available_condition(prior_segments)}"
568
622
  end
569
-
623
+
570
624
  def string_structure(prior_segments)
571
625
  optional? ? continue_string_structure(prior_segments) : interpolation_statement(prior_segments)
572
626
  end
573
-
627
+
574
628
  # Return an if condition that is true if all the prior segments can be generated.
575
629
  # If there are no optional segments before this one, then nil is returned.
576
630
  def all_optionals_available_condition(prior_segments)
577
631
  optional_locals = prior_segments.collect { |s| s.local_name if s.optional? && s.respond_to?(:local_name) }.compact
578
632
  optional_locals.empty? ? nil : " if #{optional_locals * ' && '}"
579
633
  end
580
-
634
+
581
635
  # Recognition
582
-
636
+
583
637
  def match_extraction(next_capture)
584
638
  nil
585
639
  end
586
-
640
+
587
641
  # Warning
588
-
642
+
589
643
  # Returns true if this segment is optional? because of a default. If so, then
590
644
  # no warning will be emitted regarding this segment.
591
645
  def optionality_implied?
@@ -596,21 +650,21 @@ module ActionController
596
650
  class StaticSegment < Segment #:nodoc:
597
651
  attr_accessor :value, :raw
598
652
  alias_method :raw?, :raw
599
-
653
+
600
654
  def initialize(value = nil)
601
655
  super()
602
656
  self.value = value
603
657
  end
604
-
658
+
605
659
  def interpolation_chunk
606
- raw? ? value : CGI.escape(value)
660
+ raw? ? value : super
607
661
  end
608
-
662
+
609
663
  def regexp_chunk
610
- chunk = Regexp.escape value
664
+ chunk = Regexp.escape(value)
611
665
  optional? ? Regexp.optionalize(chunk) : chunk
612
666
  end
613
-
667
+
614
668
  def build_pattern(pattern)
615
669
  escaped = Regexp.escape(value)
616
670
  if optional? && ! pattern.empty?
@@ -621,7 +675,7 @@ module ActionController
621
675
  escaped + pattern
622
676
  end
623
677
  end
624
-
678
+
625
679
  def to_s
626
680
  value
627
681
  end
@@ -633,7 +687,7 @@ module ActionController
633
687
  self.raw = true
634
688
  self.is_optional = true
635
689
  end
636
-
690
+
637
691
  def optionality_implied?
638
692
  true
639
693
  end
@@ -641,23 +695,23 @@ module ActionController
641
695
 
642
696
  class DynamicSegment < Segment #:nodoc:
643
697
  attr_accessor :key, :default, :regexp
644
-
698
+
645
699
  def initialize(key = nil, options = {})
646
700
  super()
647
701
  self.key = key
648
702
  self.default = options[:default] if options.key? :default
649
703
  self.is_optional = true if options[:optional] || options.key?(:default)
650
704
  end
651
-
705
+
652
706
  def to_s
653
707
  ":#{key}"
654
708
  end
655
-
709
+
656
710
  # The local variable name that the value of this segment will be extracted to.
657
711
  def local_name
658
712
  "#{key}_value"
659
713
  end
660
-
714
+
661
715
  def extract_value
662
716
  "#{local_name} = hash[:#{key}] && hash[:#{key}].to_param #{"|| #{default.inspect}" if default}"
663
717
  end
@@ -675,39 +729,39 @@ module ActionController
675
729
  def expiry_statement
676
730
  "expired, hash = true, options if !expired && expire_on[:#{key}]"
677
731
  end
678
-
732
+
679
733
  def extraction_code
680
734
  s = extract_value
681
735
  vc = value_check
682
736
  s << "\nreturn [nil,nil] unless #{vc}" if vc
683
737
  s << "\n#{expiry_statement}"
684
738
  end
685
-
686
- def interpolation_chunk
687
- "\#{CGI.escape(#{local_name}.to_s)}"
739
+
740
+ def interpolation_chunk(value_code = "#{local_name}")
741
+ "\#{URI.escape(#{value_code}.to_s, ActionController::Routing::Segment::UNSAFE_PCHAR)}"
688
742
  end
689
-
743
+
690
744
  def string_structure(prior_segments)
691
745
  if optional? # We have a conditional to do...
692
746
  # If we should not appear in the url, just write the code for the prior
693
747
  # segments. This occurs if our value is the default value, or, if we are
694
748
  # optional, if we have nil as our value.
695
- "if #{local_name} == #{default.inspect}\n" +
696
- continue_string_structure(prior_segments) +
749
+ "if #{local_name} == #{default.inspect}\n" +
750
+ continue_string_structure(prior_segments) +
697
751
  "\nelse\n" + # Otherwise, write the code up to here
698
752
  "#{interpolation_statement(prior_segments)}\nend"
699
753
  else
700
754
  interpolation_statement(prior_segments)
701
755
  end
702
756
  end
703
-
757
+
704
758
  def value_regexp
705
759
  Regexp.new "\\A#{regexp.source}\\Z" if regexp
706
760
  end
707
761
  def regexp_chunk
708
762
  regexp ? "(#{regexp.source})" : "([^#{Routing::SEPARATORS.join}]+)"
709
763
  end
710
-
764
+
711
765
  def build_pattern(pattern)
712
766
  chunk = regexp_chunk
713
767
  chunk = "(#{chunk})" if Regexp.new(chunk).number_of_captures == 0
@@ -715,16 +769,23 @@ module ActionController
715
769
  optional? ? Regexp.optionalize(pattern) : pattern
716
770
  end
717
771
  def match_extraction(next_capture)
718
- hangon = (default ? "|| #{default.inspect}" : "if match[#{next_capture}]")
719
-
720
- # All non code-related keys (such as :id, :slug) have to be unescaped as other CGI params
721
- "params[:#{key}] = match[#{next_capture}] #{hangon}"
772
+ # All non code-related keys (such as :id, :slug) are URI-unescaped as
773
+ # path parameters.
774
+ default_value = default ? default.inspect : nil
775
+ %[
776
+ value = if (m = match[#{next_capture}])
777
+ URI.unescape(m)
778
+ else
779
+ #{default_value}
780
+ end
781
+ params[:#{key}] = value if value
782
+ ]
722
783
  end
723
-
784
+
724
785
  def optionality_implied?
725
786
  [:action, :id].include? key
726
787
  end
727
-
788
+
728
789
  end
729
790
 
730
791
  class ControllerSegment < DynamicSegment #:nodoc:
@@ -733,10 +794,9 @@ module ActionController
733
794
  "(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))"
734
795
  end
735
796
 
736
- # Don't CGI.escape the controller name, since it may have slashes in it,
737
- # like admin/foo.
738
- def interpolation_chunk
739
- "\#{#{local_name}.to_s}"
797
+ # Don't URI.escape the controller name since it may contain slashes.
798
+ def interpolation_chunk(value_code = "#{local_name}")
799
+ "\#{#{value_code}.to_s}"
740
800
  end
741
801
 
742
802
  # Make sure controller names like Admin/Content are correctly normalized to
@@ -755,9 +815,11 @@ module ActionController
755
815
  end
756
816
 
757
817
  class PathSegment < DynamicSegment #:nodoc:
758
- EscapedSlash = CGI.escape("/")
759
- def interpolation_chunk
760
- "\#{CGI.escape(#{local_name}.to_s).gsub(#{EscapedSlash.inspect}, '/')}"
818
+ RESERVED_PCHAR = "#{Segment::RESERVED_PCHAR}/"
819
+ UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze
820
+
821
+ def interpolation_chunk(value_code = "#{local_name}")
822
+ "\#{URI.escape(#{value_code}.to_s, ActionController::Routing::PathSegment::UNSAFE_PCHAR)}"
761
823
  end
762
824
 
763
825
  def default
@@ -776,30 +838,34 @@ module ActionController
776
838
  regexp || "(.*)"
777
839
  end
778
840
 
841
+ def optionality_implied?
842
+ true
843
+ end
844
+
779
845
  class Result < ::Array #:nodoc:
780
- def to_s() join '/' end
846
+ def to_s() join '/' end
781
847
  def self.new_escaped(strings)
782
- new strings.collect {|str| CGI.unescape str}
783
- end
784
- end
848
+ new strings.collect {|str| URI.unescape str}
849
+ end
850
+ end
785
851
  end
786
852
 
787
853
  class RouteBuilder #:nodoc:
788
854
  attr_accessor :separators, :optional_separators
789
-
855
+
790
856
  def initialize
791
857
  self.separators = Routing::SEPARATORS
792
858
  self.optional_separators = %w( / )
793
859
  end
794
-
860
+
795
861
  def separator_pattern(inverted = false)
796
862
  "[#{'^' if inverted}#{Regexp.escape(separators.join)}]"
797
863
  end
798
-
864
+
799
865
  def interval_regexp
800
866
  Regexp.new "(.*?)(#{separators.source}|$)"
801
867
  end
802
-
868
+
803
869
  # Accepts a "route path" (a string defining a route), and returns the array
804
870
  # of segments that corresponds to it. Note that the segment array is only
805
871
  # partially initialized--the defaults and requirements, for instance, need
@@ -808,7 +874,7 @@ module ActionController
808
874
  # #assign_route_options is called, as well.
809
875
  def segments_for_route_path(path)
810
876
  rest, segments = path, []
811
-
877
+
812
878
  until rest.empty?
813
879
  segment, rest = segment_for rest
814
880
  segments << segment
@@ -839,12 +905,20 @@ module ActionController
839
905
  end
840
906
  [segment, $~.post_match]
841
907
  end
842
-
908
+
843
909
  # Split the given hash of options into requirement and default hashes. The
844
910
  # segments are passed alongside in order to distinguish between default values
845
911
  # and requirements.
846
912
  def divide_route_options(segments, options)
847
913
  options = options.dup
914
+
915
+ if options[:namespace]
916
+ options[:controller] = "#{options[:path_prefix]}/#{options[:controller]}"
917
+ options.delete(:path_prefix)
918
+ options.delete(:name_prefix)
919
+ options.delete(:namespace)
920
+ end
921
+
848
922
  requirements = (options.delete(:requirements) || {}).dup
849
923
  defaults = (options.delete(:defaults) || {}).dup
850
924
  conditions = (options.delete(:conditions) || {}).dup
@@ -854,20 +928,20 @@ module ActionController
854
928
  hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements
855
929
  hash[key] = value
856
930
  end
857
-
931
+
858
932
  [defaults, requirements, conditions]
859
933
  end
860
-
934
+
861
935
  # Takes a hash of defaults and a hash of requirements, and assigns them to
862
936
  # the segments. Any unused requirements (which do not correspond to a segment)
863
937
  # are returned as a hash.
864
938
  def assign_route_options(segments, defaults, requirements)
865
939
  route_requirements = {} # Requirements that do not belong to a segment
866
-
940
+
867
941
  segment_named = Proc.new do |key|
868
942
  segments.detect { |segment| segment.key == key if segment.respond_to?(:key) }
869
943
  end
870
-
944
+
871
945
  requirements.each do |key, requirement|
872
946
  segment = segment_named[key]
873
947
  if segment
@@ -880,19 +954,19 @@ module ActionController
880
954
  route_requirements[key] = requirement
881
955
  end
882
956
  end
883
-
957
+
884
958
  defaults.each do |key, default|
885
959
  segment = segment_named[key]
886
960
  raise ArgumentError, "#{key}: No matching segment exists; cannot assign default" unless segment
887
961
  segment.is_optional = true
888
962
  segment.default = default.to_param if default
889
963
  end
890
-
964
+
891
965
  assign_default_route_options(segments)
892
966
  ensure_required_segments(segments)
893
967
  route_requirements
894
968
  end
895
-
969
+
896
970
  # Assign default options, such as 'index' as a default for :action. This
897
971
  # method must be run *after* user supplied requirements and defaults have
898
972
  # been applied to the segments.
@@ -912,7 +986,7 @@ module ActionController
912
986
  end
913
987
  end
914
988
  end
915
-
989
+
916
990
  # Makes sure that there are no optional segments that precede a required
917
991
  # segment. If any are found that precede a required segment, they are
918
992
  # made required.
@@ -925,24 +999,27 @@ module ActionController
925
999
  warn "Route segment \"#{segment.to_s}\" cannot be optional because it precedes a required segment. This segment will be required."
926
1000
  end
927
1001
  segment.is_optional = false
928
- elsif allow_optional & segment.respond_to?(:default) && segment.default
1002
+ elsif allow_optional && segment.respond_to?(:default) && segment.default
929
1003
  # if a segment has a default, then it is optional
930
1004
  segment.is_optional = true
931
1005
  end
932
1006
  end
933
1007
  end
934
-
1008
+
935
1009
  # Construct and return a route with the given path and options.
936
1010
  def build(path, options)
937
1011
  # Wrap the path with slashes
938
1012
  path = "/#{path}" unless path[0] == ?/
939
1013
  path = "#{path}/" unless path[-1] == ?/
940
-
1014
+
1015
+ path = "/#{options[:path_prefix].to_s.gsub(/^\//,'')}#{path}" if options[:path_prefix]
1016
+
941
1017
  segments = segments_for_route_path(path)
942
1018
  defaults, requirements, conditions = divide_route_options(segments, options)
943
1019
  requirements = assign_route_options(segments, defaults, requirements)
944
1020
 
945
1021
  route = Route.new
1022
+
946
1023
  route.segments = segments
947
1024
  route.requirements = requirements
948
1025
  route.conditions = conditions
@@ -952,6 +1029,13 @@ module ActionController
952
1029
  route.significant_keys << :action
953
1030
  end
954
1031
 
1032
+ # Routes cannot use the current string interpolation method
1033
+ # if there are user-supplied :requirements as the interpolation
1034
+ # code won't raise RoutingErrors when generating
1035
+ if options.key?(:requirements) || route.requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION
1036
+ route.optimise = false
1037
+ end
1038
+
955
1039
  if !route.significant_keys.include?(:controller)
956
1040
  raise ArgumentError, "Illegal route: the :controller must be specified!"
957
1041
  end
@@ -963,35 +1047,48 @@ module ActionController
963
1047
  class RouteSet #:nodoc:
964
1048
  # Mapper instances are used to build routes. The object passed to the draw
965
1049
  # block in config/routes.rb is a Mapper instance.
966
- #
1050
+ #
967
1051
  # Mapper instances have relatively few instance methods, in order to avoid
968
1052
  # clashes with named routes.
969
1053
  class Mapper #:nodoc:
970
1054
  def initialize(set)
971
1055
  @set = set
972
1056
  end
973
-
974
- # Create an unnamed route with the provided +path+ and +options+. See
1057
+
1058
+ # Create an unnamed route with the provided +path+ and +options+. See
975
1059
  # SomeHelpfulUrl for an introduction to routes.
976
1060
  def connect(path, options = {})
977
1061
  @set.add_route(path, options)
978
1062
  end
979
1063
 
1064
+ # Creates a named route called "root" for matching the root level request.
1065
+ def root(options = {})
1066
+ named_route("root", '', options)
1067
+ end
1068
+
980
1069
  def named_route(name, path, options = {})
981
1070
  @set.add_named_route(name, path, options)
982
1071
  end
983
-
984
- def deprecated_named_route(name, deprecated_name, options = {})
985
- @set.add_deprecated_named_route(name, deprecated_name)
986
- end
987
1072
 
988
- # Added deprecation notice for anyone who already added a named route called "root".
989
- # It'll be used as a shortcut for map.connect '' in Rails 2.0.
990
- def root(*args, &proc)
991
- super unless args.length >= 1 && proc.nil?
992
- @set.add_named_route("root", *args)
1073
+ # Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model.
1074
+ # Example:
1075
+ #
1076
+ # map.namespace(:admin) do |admin|
1077
+ # admin.resources :products,
1078
+ # :has_many => [ :tags, :images, :variants ]
1079
+ # end
1080
+ #
1081
+ # This will create admin_products_url pointing to "admin/products", which will look for an Admin::ProductsController.
1082
+ # It'll also create admin_product_tags_url pointing to "admin/products/#{product_id}/tags", which will look for
1083
+ # Admin::TagsController.
1084
+ def namespace(name, options = {}, &block)
1085
+ if options[:namespace]
1086
+ with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
1087
+ else
1088
+ with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block)
1089
+ end
993
1090
  end
994
- deprecate :root => "(as the the label for a named route) will become a shortcut for map.connect '', so find another name"
1091
+
995
1092
 
996
1093
  def method_missing(route_name, *args, &proc)
997
1094
  super unless args.length >= 1 && proc.nil?
@@ -1004,7 +1101,7 @@ module ActionController
1004
1101
  # named routes.
1005
1102
  class NamedRouteCollection #:nodoc:
1006
1103
  include Enumerable
1007
-
1104
+ include ActionController::Routing::Optimisation
1008
1105
  attr_reader :routes, :helpers
1009
1106
 
1010
1107
  def initialize
@@ -1017,7 +1114,7 @@ module ActionController
1017
1114
 
1018
1115
  @module ||= Module.new
1019
1116
  @module.instance_methods.each do |selector|
1020
- @module.send :remove_method, selector
1117
+ @module.class_eval { remove_method selector }
1021
1118
  end
1022
1119
  end
1023
1120
 
@@ -1047,40 +1144,19 @@ module ActionController
1047
1144
  routes.length
1048
1145
  end
1049
1146
 
1050
- def install(destinations = [ActionController::Base, ActionView::Base])
1051
- Array(destinations).each { |dest| dest.send :include, @module }
1147
+ def reset!
1148
+ old_routes = routes.dup
1149
+ clear!
1150
+ old_routes.each do |name, route|
1151
+ add(name, route)
1152
+ end
1052
1153
  end
1053
-
1054
- def define_deprecated_named_route_methods(name, deprecated_name)
1055
-
1056
- [:url, :path].each do |kind|
1057
- @module.send :module_eval, <<-end_eval # We use module_eval to avoid leaks
1058
-
1059
- def #{url_helper_name(deprecated_name, kind)}(*args)
1060
-
1061
- ActiveSupport::Deprecation.warn(
1062
- 'The named route "#{url_helper_name(deprecated_name, kind)}" uses a format that has been deprecated. ' +
1063
- 'You should use "#{url_helper_name(name, kind)}" instead.', caller
1064
- )
1065
-
1066
- send :#{url_helper_name(name, kind)}, *args
1067
-
1068
- end
1069
-
1070
- def #{hash_access_name(deprecated_name, kind)}(*args)
1071
-
1072
- ActiveSupport::Deprecation.warn(
1073
- 'The named route "#{hash_access_name(deprecated_name, kind)}" uses a format that has been deprecated. ' +
1074
- 'You should use "#{hash_access_name(name, kind)}" instead.', caller
1075
- )
1076
-
1077
- send :#{hash_access_name(name, kind)}, *args
1078
-
1079
- end
1080
1154
 
1081
- end_eval
1155
+ def install(destinations = [ActionController::Base, ActionView::Base], regenerate = false)
1156
+ reset! if regenerate
1157
+ Array(destinations).each do |dest|
1158
+ dest.send! :include, @module
1082
1159
  end
1083
-
1084
1160
  end
1085
1161
 
1086
1162
  private
@@ -1099,57 +1175,62 @@ module ActionController
1099
1175
  define_url_helper route, name, kind, hash
1100
1176
  end
1101
1177
  end
1102
-
1178
+
1103
1179
  def define_hash_access(route, name, kind, options)
1104
1180
  selector = hash_access_name(name, kind)
1105
- @module.send :module_eval, <<-end_eval # We use module_eval to avoid leaks
1181
+ @module.module_eval <<-end_eval # We use module_eval to avoid leaks
1106
1182
  def #{selector}(options = nil)
1107
1183
  options ? #{options.inspect}.merge(options) : #{options.inspect}
1108
1184
  end
1185
+ protected :#{selector}
1109
1186
  end_eval
1110
- @module.send(:protected, selector)
1111
1187
  helpers << selector
1112
1188
  end
1113
-
1189
+
1114
1190
  def define_url_helper(route, name, kind, options)
1115
1191
  selector = url_helper_name(name, kind)
1116
-
1117
1192
  # The segment keys used for positional paramters
1118
- segment_keys = route.segments.collect do |segment|
1119
- segment.key if segment.respond_to? :key
1120
- end.compact
1193
+
1121
1194
  hash_access_method = hash_access_name(name, kind)
1122
-
1123
- @module.send :module_eval, <<-end_eval # We use module_eval to avoid leaks
1195
+
1196
+ # allow ordered parameters to be associated with corresponding
1197
+ # dynamic segments, so you can do
1198
+ #
1199
+ # foo_url(bar, baz, bang)
1200
+ #
1201
+ # instead of
1202
+ #
1203
+ # foo_url(:bar => bar, :baz => baz, :bang => bang)
1204
+ #
1205
+ # Also allow options hash, so you can do
1206
+ #
1207
+ # foo_url(bar, baz, bang, :sort_by => 'baz')
1208
+ #
1209
+ @module.module_eval <<-end_eval # We use module_eval to avoid leaks
1124
1210
  def #{selector}(*args)
1211
+ #{generate_optimisation_block(route, kind)}
1212
+
1125
1213
  opts = if args.empty? || Hash === args.first
1126
1214
  args.first || {}
1127
1215
  else
1128
- # allow ordered parameters to be associated with corresponding
1129
- # dynamic segments, so you can do
1130
- #
1131
- # foo_url(bar, baz, bang)
1132
- #
1133
- # instead of
1134
- #
1135
- # foo_url(:bar => bar, :baz => baz, :bang => bang)
1136
- args.zip(#{segment_keys.inspect}).inject({}) do |h, (v, k)|
1216
+ options = args.last.is_a?(Hash) ? args.pop : {}
1217
+ args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)|
1137
1218
  h[k] = v
1138
1219
  h
1139
1220
  end
1221
+ options.merge(args)
1140
1222
  end
1141
-
1223
+
1142
1224
  url_for(#{hash_access_method}(opts))
1143
1225
  end
1226
+ protected :#{selector}
1144
1227
  end_eval
1145
- @module.send(:protected, selector)
1146
1228
  helpers << selector
1147
1229
  end
1148
-
1149
1230
  end
1150
-
1231
+
1151
1232
  attr_accessor :routes, :named_routes
1152
-
1233
+
1153
1234
  def initialize
1154
1235
  self.routes = []
1155
1236
  self.named_routes = NamedRouteCollection.new
@@ -1164,9 +1245,9 @@ module ActionController
1164
1245
  def draw
1165
1246
  clear!
1166
1247
  yield Mapper.new(self)
1167
- named_routes.install
1248
+ install_helpers
1168
1249
  end
1169
-
1250
+
1170
1251
  def clear!
1171
1252
  routes.clear
1172
1253
  named_routes.clear
@@ -1174,41 +1255,57 @@ module ActionController
1174
1255
  @routes_by_controller = nil
1175
1256
  end
1176
1257
 
1258
+ def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
1259
+ Array(destinations).each { |d| d.module_eval { include Helpers } }
1260
+ named_routes.install(destinations, regenerate_code)
1261
+ end
1262
+
1177
1263
  def empty?
1178
1264
  routes.empty?
1179
1265
  end
1180
-
1266
+
1181
1267
  def load!
1182
1268
  Routing.use_controllers! nil # Clear the controller cache so we may discover new ones
1183
1269
  clear!
1184
1270
  load_routes!
1185
- named_routes.install
1271
+ install_helpers
1186
1272
  end
1187
1273
 
1188
- alias reload load!
1274
+ # reload! will always force a reload whereas load checks the timestamp first
1275
+ alias reload! load!
1276
+
1277
+ def reload
1278
+ if @routes_last_modified && defined?(RAILS_ROOT)
1279
+ mtime = File.stat("#{RAILS_ROOT}/config/routes.rb").mtime
1280
+ # if it hasn't been changed, then just return
1281
+ return if mtime == @routes_last_modified
1282
+ # if it has changed then record the new time and fall to the load! below
1283
+ @routes_last_modified = mtime
1284
+ end
1285
+ load!
1286
+ end
1189
1287
 
1190
1288
  def load_routes!
1191
1289
  if defined?(RAILS_ROOT) && defined?(::ActionController::Routing::Routes) && self == ::ActionController::Routing::Routes
1192
1290
  load File.join("#{RAILS_ROOT}/config/routes.rb")
1291
+ @routes_last_modified = File.stat("#{RAILS_ROOT}/config/routes.rb").mtime
1193
1292
  else
1194
1293
  add_route ":controller/:action/:id"
1195
1294
  end
1196
1295
  end
1197
-
1296
+
1198
1297
  def add_route(path, options = {})
1199
1298
  route = builder.build(path, options)
1200
1299
  routes << route
1201
1300
  route
1202
1301
  end
1203
-
1302
+
1204
1303
  def add_named_route(name, path, options = {})
1205
- named_routes[name] = add_route(path, options)
1206
- end
1207
-
1208
- def add_deprecated_named_route(name, deprecated_name)
1209
- named_routes.define_deprecated_named_route_methods(name, deprecated_name)
1304
+ # TODO - is options EVER used?
1305
+ name = options[:name_prefix] + name.to_s if options[:name_prefix]
1306
+ named_routes[name.to_sym] = add_route(path, options)
1210
1307
  end
1211
-
1308
+
1212
1309
  def options_as_params(options)
1213
1310
  # If an explicit :controller was given, always make :action explicit
1214
1311
  # too, so that action expiry works as expected for things like
@@ -1226,10 +1323,10 @@ module ActionController
1226
1323
  options_as_params[:action] = options_as_params[:action].to_s if options_as_params[:action]
1227
1324
  options_as_params
1228
1325
  end
1229
-
1326
+
1230
1327
  def build_expiry(options, recall)
1231
1328
  recall.inject({}) do |expiry, (key, recalled_value)|
1232
- expiry[key] = (options.key?(key) && options[key] != recalled_value)
1329
+ expiry[key] = (options.key?(key) && options[key].to_param != recalled_value.to_param)
1233
1330
  expiry
1234
1331
  end
1235
1332
  end
@@ -1246,6 +1343,7 @@ module ActionController
1246
1343
 
1247
1344
  def generate(options, recall = {}, method=:generate)
1248
1345
  named_route_name = options.delete(:use_route)
1346
+ generate_all = options.delete(:generate_all)
1249
1347
  if named_route_name
1250
1348
  named_route = named_routes[named_route_name]
1251
1349
  options = named_route.parameter_shell.merge(options)
@@ -1274,7 +1372,7 @@ module ActionController
1274
1372
 
1275
1373
  if named_route
1276
1374
  path = named_route.generate(options, merged, expire_on)
1277
- if path.nil?
1375
+ if path.nil?
1278
1376
  raise_named_route_error(options, named_route, named_route_name)
1279
1377
  else
1280
1378
  return path
@@ -1282,23 +1380,31 @@ module ActionController
1282
1380
  else
1283
1381
  merged[:action] ||= 'index'
1284
1382
  options[:action] ||= 'index'
1285
-
1383
+
1286
1384
  controller = merged[:controller]
1287
1385
  action = merged[:action]
1288
1386
 
1289
1387
  raise RoutingError, "Need controller and action!" unless controller && action
1388
+
1389
+ if generate_all
1390
+ # Used by caching to expire all paths for a resource
1391
+ return routes.collect do |route|
1392
+ route.send!(method, options, merged, expire_on)
1393
+ end.compact
1394
+ end
1395
+
1290
1396
  # don't use the recalled keys when determining which routes to check
1291
1397
  routes = routes_by_controller[controller][action][options.keys.sort_by { |x| x.object_id }]
1292
1398
 
1293
1399
  routes.each do |route|
1294
- results = route.send(method, options, merged, expire_on)
1400
+ results = route.send!(method, options, merged, expire_on)
1295
1401
  return results if results && (!results.is_a?(Array) || results.first)
1296
1402
  end
1297
1403
  end
1298
-
1404
+
1299
1405
  raise RoutingError, "No route matches #{options.inspect}"
1300
1406
  end
1301
-
1407
+
1302
1408
  # try to give a helpful error message when named route generation fails
1303
1409
  def raise_named_route_error(options, named_route, named_route_name)
1304
1410
  diff = named_route.requirements.diff(options)
@@ -1307,24 +1413,32 @@ module ActionController
1307
1413
  else
1308
1414
  required_segments = named_route.segments.select {|seg| (!seg.optional?) && (!seg.is_a?(DividerSegment)) }
1309
1415
  required_keys_or_values = required_segments.map { |seg| seg.key rescue seg.value } # we want either the key or the value from the segment
1310
- raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect} - you may have ambiguous routes, or you may need to supply additional parameters for this route. content_url has the following required parameters: #{required_keys_or_values.inspect} - are they all satisifed?"
1416
+ raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect} - you may have ambiguous routes, or you may need to supply additional parameters for this route. content_url has the following required parameters: #{required_keys_or_values.inspect} - are they all satisfied?"
1311
1417
  end
1312
1418
  end
1313
-
1419
+
1314
1420
  def recognize(request)
1315
1421
  params = recognize_path(request.path, extract_request_environment(request))
1316
1422
  request.path_parameters = params.with_indifferent_access
1317
1423
  "#{params[:controller].camelize}Controller".constantize
1318
1424
  end
1319
-
1425
+
1320
1426
  def recognize_path(path, environment={})
1321
- path = CGI.unescape(path)
1322
1427
  routes.each do |route|
1323
1428
  result = route.recognize(path, environment) and return result
1324
1429
  end
1325
- raise RoutingError, "no route found to match #{path.inspect} with #{environment.inspect}"
1430
+
1431
+ allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, :method => verb) } }
1432
+
1433
+ if environment[:method] && !HTTP_METHODS.include?(environment[:method])
1434
+ raise NotImplemented.new(*allows)
1435
+ elsif !allows.empty?
1436
+ raise MethodNotAllowed.new(*allows)
1437
+ else
1438
+ raise RoutingError, "No route matches #{path.inspect} with #{environment.inspect}"
1439
+ end
1326
1440
  end
1327
-
1441
+
1328
1442
  def routes_by_controller
1329
1443
  @routes_by_controller ||= Hash.new do |controller_hash, controller|
1330
1444
  controller_hash[controller] = Hash.new do |action_hash, action|
@@ -1334,23 +1448,23 @@ module ActionController
1334
1448
  end
1335
1449
  end
1336
1450
  end
1337
-
1451
+
1338
1452
  def routes_for(options, merged, expire_on)
1339
1453
  raise "Need controller and action!" unless controller && action
1340
1454
  controller = merged[:controller]
1341
1455
  merged = options if expire_on[:controller]
1342
1456
  action = merged[:action] || 'index'
1343
-
1457
+
1344
1458
  routes_by_controller[controller][action][merged.keys]
1345
1459
  end
1346
-
1460
+
1347
1461
  def routes_for_controller_and_action(controller, action)
1348
1462
  selected = routes.select do |route|
1349
1463
  route.matches_controller_and_action? controller, action
1350
1464
  end
1351
1465
  (selected.length == routes.length) ? routes : selected
1352
1466
  end
1353
-
1467
+
1354
1468
  def routes_for_controller_and_action_and_keys(controller, action, keys)
1355
1469
  selected = routes.select do |route|
1356
1470
  route.matches_controller_and_action? controller, action
@@ -1368,6 +1482,15 @@ module ActionController
1368
1482
  end
1369
1483
 
1370
1484
  Routes = RouteSet.new
1485
+
1486
+ ::Inflector.module_eval do
1487
+ def inflections_with_route_reloading(&block)
1488
+ returning(inflections_without_route_reloading(&block)) {
1489
+ ActionController::Routing::Routes.reload! if block_given?
1490
+ }
1491
+ end
1492
+
1493
+ alias_method_chain :inflections, :route_reloading
1494
+ end
1371
1495
  end
1372
1496
  end
1373
-