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,4 +1,3 @@
1
-
2
1
  module ActionView
3
2
 
4
3
  # CompiledTemplates modules hold methods that have been compiled.
@@ -40,7 +39,7 @@ module ActionView
40
39
  # Compile the provided source code for the given argument names and with the given initial line number.
41
40
  # The identifier should be unique to this source.
42
41
  #
43
- # The file_name, if provided will appear in backtraces. If not provded, the file_name defaults
42
+ # The file_name, if provided will appear in backtraces. If not provided, the file_name defaults
44
43
  # to the identifier.
45
44
  #
46
45
  # This method will return the selector for the compiled version of this method.
@@ -1,5 +1,5 @@
1
1
  require 'cgi'
2
- require File.dirname(__FILE__) + '/form_helper'
2
+ require 'action_view/helpers/form_helper'
3
3
 
4
4
  module ActionView
5
5
  class Base
@@ -10,7 +10,7 @@ module ActionView
10
10
  module Helpers
11
11
  # The Active Record Helper makes it easier to create forms for records kept in instance variables. The most far-reaching is the form
12
12
  # method that creates a complete form for all the basic content types of the record (not associations or aggregations, though). This
13
- # is a great of making the record quickly available for editing, but likely to prove lackluster for a complicated real-world form.
13
+ # is a great way of making the record quickly available for editing, but likely to prove lackluster for a complicated real-world form.
14
14
  # In that case, it's better to use the input method and the specialized form methods in link:classes/ActionView/Helpers/FormHelper.html
15
15
  module ActiveRecordHelper
16
16
  # Returns a default input tag for the type of object returned by the method. For example, let's say you have a model
@@ -76,16 +76,21 @@ module ActionView
76
76
 
77
77
  # Returns a string containing the error message attached to the +method+ on the +object+ if one exists.
78
78
  # This error message is wrapped in a <tt>DIV</tt> tag, which can be extended to include a +prepend_text+ and/or +append_text+
79
- # (to properly explain the error), and a +css_class+ to style it accordingly. As an example, let's say you have a model
79
+ # (to properly explain the error), and a +css_class+ to style it accordingly. +object+ should either be the name of an instance variable or
80
+ # the actual object. As an example, let's say you have a model
80
81
  # +post+ that has an error message on the +title+ attribute:
81
82
  #
82
83
  # <%= error_message_on "post", "title" %> =>
83
84
  # <div class="formError">can't be empty</div>
84
85
  #
86
+ # <%= error_message_on @post, "title" %> =>
87
+ # <div class="formError">can't be empty</div>
88
+ #
85
89
  # <%= error_message_on "post", "title", "Title simply ", " (or it won't work).", "inputError" %> =>
86
90
  # <div class="inputError">Title simply can't be empty (or it won't work).</div>
87
91
  def error_message_on(object, method, prepend_text = "", append_text = "", css_class = "formError")
88
- if (obj = instance_variable_get("@#{object}")) && (errors = obj.errors.on(method))
92
+ if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) &&
93
+ (errors = obj.errors.on(method))
89
94
  content_tag("div", "#{prepend_text}#{errors.is_a?(Array) ? errors.first : errors}#{append_text}", :class => css_class)
90
95
  else
91
96
  ''
@@ -101,25 +106,35 @@ module ActionView
101
106
  # * <tt>header_tag</tt> - Used for the header of the error div (default: h2)
102
107
  # * <tt>id</tt> - The id of the error div (default: errorExplanation)
103
108
  # * <tt>class</tt> - The class of the error div (default: errorExplanation)
104
- # * <tt>object_name</tt> - The object name to use in the header, or
105
- # any text that you prefer. If <tt>object_name</tt> is not set, the name of
106
- # the first object will be used.
109
+ # * <tt>object</tt> - The object (or array of objects) for which to display errors, if you need to escape the instance variable convention
110
+ # * <tt>object_name</tt> - The object name to use in the header, or any text that you prefer. If <tt>object_name</tt> is not set, the name of the first object will be used.
111
+ # * <tt>header_message</tt> - The message in the header of the error div. Pass +nil+ or an empty string to avoid the header message altogether. (default: X errors prohibited this object from being saved)
112
+ # * <tt>message</tt> - The explanation message after the header message and before the error list. Pass +nil+ or an empty string to avoid the explanation message altogether. (default: There were problems with the following fields:)
107
113
  #
108
114
  # To specify the display for one object, you simply provide its name as a parameter. For example, for the +User+ model:
109
115
  #
110
116
  # error_messages_for 'user'
111
117
  #
112
118
  # To specify more than one object, you simply list them; optionally, you can add an extra +object_name+ parameter, which
113
- # be the name in the header.
119
+ # will be the name used in the header message.
114
120
  #
115
121
  # error_messages_for 'user_common', 'user', :object_name => 'user'
116
122
  #
123
+ # If the objects cannot be located as instance variables, you can add an extra +object+ paremeter which gives the actual
124
+ # object (or array of objects to use)
125
+ #
126
+ # error_messages_for 'user', :object => @question.user
127
+ #
117
128
  # NOTE: This is a pre-packaged presentation of the errors with embedded strings and a certain HTML structure. If what
118
129
  # you need is significantly different from the default presentation, it makes plenty of sense to access the object.errors
119
130
  # instance yourself and set it up. View the source of this method to see how easy it is.
120
131
  def error_messages_for(*params)
121
- options = params.last.is_a?(Hash) ? params.pop.symbolize_keys : {}
122
- objects = params.collect {|object_name| instance_variable_get("@#{object_name}") }.compact
132
+ options = params.extract_options!.symbolize_keys
133
+ if object = options.delete(:object)
134
+ objects = [object].flatten
135
+ else
136
+ objects = params.collect {|object_name| instance_variable_get("@#{object_name}") }.compact
137
+ end
123
138
  count = objects.inject(0) {|sum, object| sum + object.errors.count }
124
139
  unless count.zero?
125
140
  html = {}
@@ -131,14 +146,17 @@ module ActionView
131
146
  html[key] = 'errorExplanation'
132
147
  end
133
148
  end
134
- header_message = "#{pluralize(count, 'error')} prohibited this #{(options[:object_name] || params.first).to_s.gsub('_', ' ')} from being saved"
149
+ options[:object_name] ||= params.first
150
+ options[:header_message] = "#{pluralize(count, 'error')} prohibited this #{options[:object_name].to_s.gsub('_', ' ')} from being saved" unless options.include?(:header_message)
151
+ options[:message] ||= 'There were problems with the following fields:' unless options.include?(:message)
135
152
  error_messages = objects.map {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }
136
- content_tag(:div,
137
- content_tag(options[:header_tag] || :h2, header_message) <<
138
- content_tag(:p, 'There were problems with the following fields:') <<
139
- content_tag(:ul, error_messages),
140
- html
141
- )
153
+
154
+ contents = ''
155
+ contents << content_tag(options[:header_tag] || :h2, options[:header_message]) unless options[:header_message].blank?
156
+ contents << content_tag(:p, options[:message]) unless options[:message].blank?
157
+ contents << content_tag(:ul, error_messages)
158
+
159
+ content_tag(:div, contents, html)
142
160
  else
143
161
  ''
144
162
  end
@@ -1,44 +1,97 @@
1
1
  require 'cgi'
2
- require File.dirname(__FILE__) + '/url_helper'
3
- require File.dirname(__FILE__) + '/tag_helper'
2
+ require 'action_view/helpers/url_helper'
3
+ require 'action_view/helpers/tag_helper'
4
4
 
5
5
  module ActionView
6
6
  module Helpers #:nodoc:
7
- # Provides methods for linking an HTML page together with other assets such
8
- # as images, javascripts, stylesheets, and feeds. You can direct Rails to
9
- # link to assets from a dedicated assets server by setting ActionController::Base.asset_host
10
- # in your environment.rb. These methods do not verify the assets exist before
11
- # linking to them.
7
+ # This module provides methods for generating HTML that links views to assets such
8
+ # as images, javascripts, stylesheets, and feeds. These methods do not verify
9
+ # the assets exist before linking to them.
12
10
  #
13
- # ActionController::Base.asset_host = "http://assets.example.com"
14
- # image_tag("rails.png")
11
+ # === Using asset hosts
12
+ # By default, Rails links to these assets on the current host in the public
13
+ # folder, but you can direct Rails to link to assets from a dedicated assets server by
14
+ # setting ActionController::Base.asset_host in your environment.rb. For example,
15
+ # let's say your asset host is assets.example.com.
16
+ #
17
+ # ActionController::Base.asset_host = "assets.example.com"
18
+ # image_tag("rails.png")
15
19
  # => <img src="http://assets.example.com/images/rails.png" alt="Rails" />
16
20
  # stylesheet_include_tag("application")
17
- # => <link href="http://assets.example.com/stylesheets/application.css" media="screen" rel="Stylesheet" type="text/css" />
21
+ # => <link href="http://assets.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
22
+ #
23
+ # This is useful since browsers typically open at most two connections to a single host,
24
+ # which means your assets often wait in single file for their turn to load. You can
25
+ # alleviate this by using a %d wildcard in <tt>asset_host</tt> (for example, "assets%d.example.com")
26
+ # to automatically distribute asset requests among four hosts (e.g., assets0.example.com through assets3.example.com)
27
+ # so browsers will open eight connections rather than two.
28
+ #
29
+ # image_tag("rails.png")
30
+ # => <img src="http://assets0.example.com/images/rails.png" alt="Rails" />
31
+ # stylesheet_include_tag("application")
32
+ # => <link href="http://assets3.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
33
+ #
34
+ # To do this, you can either setup four actual hosts, or you can use wildcard DNS to CNAME
35
+ # the wildcard to a single asset host. You can read more about setting up your DNS CNAME records from
36
+ # your ISP.
37
+ #
38
+ # Note: This is purely a browser performance optimization and is not meant
39
+ # for server load balancing. See http://www.die.net/musings/page_load_time/
40
+ # for background.
41
+ #
42
+ # === Using asset timestamps
43
+ #
44
+ # By default, Rails will append all asset paths with that asset's timestamp. This allows you to set a cache-expiration date for the
45
+ # asset far into the future, but still be able to instantly invalidate it by simply updating the file (and hence updating the timestamp,
46
+ # which then updates the URL as the timestamp is part of that, which in turn busts the cache).
47
+ #
48
+ # It's the responsibility of the web server you use to set the far-future expiration date on cache assets that you need to take
49
+ # advantage of this feature. Here's an example for Apache:
50
+ #
51
+ # # Asset Expiration
52
+ # ExpiresActive On
53
+ # <FilesMatch "\.(ico|gif|jpe?g|png|js|css)$">
54
+ # ExpiresDefault "access plus 1 year"
55
+ # </FilesMatch>
56
+ #
57
+ # Also note that in order for this to work, all your application servers must return the same timestamps. This means that they must
58
+ # have their clocks synchronized. If one of them drift out of sync, you'll see different timestamps at random and the cache won't
59
+ # work. Which means that the browser will request the same assets over and over again even thought they didn't change. You can use
60
+ # something like Live HTTP Headers for Firefox to verify that the cache is indeed working (and that the assets are not being
61
+ # requested over and over).
18
62
  module AssetTagHelper
63
+ ASSETS_DIR = defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/public" : "public"
64
+ JAVASCRIPTS_DIR = "#{ASSETS_DIR}/javascripts"
65
+ STYLESHEETS_DIR = "#{ASSETS_DIR}/stylesheets"
66
+
19
67
  # Returns a link tag that browsers and news readers can use to auto-detect
20
- # an RSS or ATOM feed. The +type+ can either be <tt>:rss</tt> (default) or
68
+ # an RSS or ATOM feed. The +type+ can either be <tt>:rss</tt> (default) or
21
69
  # <tt>:atom</tt>. Control the link options in url_for format using the
22
70
  # +url_options+. You can modify the LINK tag itself in +tag_options+.
23
71
  #
24
- # Tag Options:
72
+ # ==== Options:
25
73
  # * <tt>:rel</tt> - Specify the relation of this link, defaults to "alternate"
26
74
  # * <tt>:type</tt> - Override the auto-generated mime type
27
75
  # * <tt>:title</tt> - Specify the title of the link, defaults to the +type+
28
76
  #
77
+ # ==== Examples
29
78
  # auto_discovery_link_tag # =>
30
- # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.curenthost.com/controller/action" />
79
+ # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/action" />
31
80
  # auto_discovery_link_tag(:atom) # =>
32
- # <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.curenthost.com/controller/action" />
81
+ # <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.currenthost.com/controller/action" />
33
82
  # auto_discovery_link_tag(:rss, {:action => "feed"}) # =>
34
- # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.curenthost.com/controller/feed" />
83
+ # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/feed" />
35
84
  # auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"}) # =>
36
- # <link rel="alternate" type="application/rss+xml" title="My RSS" href="http://www.curenthost.com/controller/feed" />
85
+ # <link rel="alternate" type="application/rss+xml" title="My RSS" href="http://www.currenthost.com/controller/feed" />
86
+ # auto_discovery_link_tag(:rss, {:controller => "news", :action => "feed"}) # =>
87
+ # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/news/feed" />
88
+ # auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {:title => "Example RSS"}) # =>
89
+ # <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed" />
37
90
  def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {})
38
91
  tag(
39
- "link",
92
+ "link",
40
93
  "rel" => tag_options[:rel] || "alternate",
41
- "type" => tag_options[:type] || "application/#{type}+xml",
94
+ "type" => tag_options[:type] || Mime::Type.lookup_by_extension(type.to_s).to_s,
42
95
  "title" => tag_options[:title] || type.to_s.upcase,
43
96
  "href" => url_options.is_a?(Hash) ? url_for(url_options.merge(:only_path => false)) : url_options
44
97
  )
@@ -49,12 +102,16 @@ module ActionView
49
102
  # Full paths from the document root will be passed through.
50
103
  # Used internally by javascript_include_tag to build the script path.
51
104
  #
105
+ # ==== Examples
52
106
  # javascript_path "xmlhr" # => /javascripts/xmlhr.js
53
107
  # javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js
54
108
  # javascript_path "/dir/xmlhr" # => /dir/xmlhr.js
109
+ # javascript_path "http://www.railsapplication.com/js/xmlhr" # => http://www.railsapplication.com/js/xmlhr.js
110
+ # javascript_path "http://www.railsapplication.com/js/xmlhr.js" # => http://www.railsapplication.com/js/xmlhr.js
55
111
  def javascript_path(source)
56
- compute_public_path(source, 'javascripts', 'js')
112
+ compute_public_path(source, 'javascripts', 'js')
57
113
  end
114
+ alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
58
115
 
59
116
  JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'] unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
60
117
  @@javascript_default_sources = JAVASCRIPT_DEFAULT_SOURCES.dup
@@ -64,49 +121,114 @@ module ActionView
64
121
  # that exist in your public/javascripts directory for inclusion into the
65
122
  # current page or you can pass the full path relative to your document
66
123
  # root. To include the Prototype and Scriptaculous javascript libraries in
67
- # your application, pass <tt>:defaults</tt> as the source. When using
68
- # :defaults, if an <tt>application.js</tt> file exists in your public
69
- # javascripts directory, it will be included as well. You can modify the
70
- # html attributes of the script tag by passing a hash as the last argument.
124
+ # your application, pass <tt>:defaults</tt> as the source. When using
125
+ # :defaults, if an <tt>application.js</tt> file exists in your public
126
+ # javascripts directory, it will be included as well. You can modify the
127
+ # html attributes of the script tag by passing a hash as the last argument.
71
128
  #
129
+ # ==== Examples
72
130
  # javascript_include_tag "xmlhr" # =>
73
131
  # <script type="text/javascript" src="/javascripts/xmlhr.js"></script>
74
132
  #
133
+ # javascript_include_tag "xmlhr.js" # =>
134
+ # <script type="text/javascript" src="/javascripts/xmlhr.js"></script>
135
+ #
75
136
  # javascript_include_tag "common.javascript", "/elsewhere/cools" # =>
76
137
  # <script type="text/javascript" src="/javascripts/common.javascript"></script>
77
138
  # <script type="text/javascript" src="/elsewhere/cools.js"></script>
78
139
  #
140
+ # javascript_include_tag "http://www.railsapplication.com/xmlhr" # =>
141
+ # <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js"></script>
142
+ #
143
+ # javascript_include_tag "http://www.railsapplication.com/xmlhr.js" # =>
144
+ # <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js"></script>
145
+ #
79
146
  # javascript_include_tag :defaults # =>
80
147
  # <script type="text/javascript" src="/javascripts/prototype.js"></script>
81
148
  # <script type="text/javascript" src="/javascripts/effects.js"></script>
82
149
  # ...
83
- # <script type="text/javascript" src="/javascripts/application.js"></script> *see below
150
+ # <script type="text/javascript" src="/javascripts/application.js"></script>
151
+ #
152
+ # * = The application.js file is only referenced if it exists
153
+ #
154
+ # Though it's not really recommended practice, if you need to extend the default JavaScript set for any reason
155
+ # (e.g., you're going to be using a certain .js file in every action), then take a look at the register_javascript_include_default method.
156
+ #
157
+ # You can also include all javascripts in the javascripts directory using <tt>:all</tt> as the source:
158
+ #
159
+ # javascript_include_tag :all # =>
160
+ # <script type="text/javascript" src="/javascripts/prototype.js"></script>
161
+ # <script type="text/javascript" src="/javascripts/effects.js"></script>
162
+ # ...
163
+ # <script type="text/javascript" src="/javascripts/application.js"></script>
164
+ # <script type="text/javascript" src="/javascripts/shop.js"></script>
165
+ # <script type="text/javascript" src="/javascripts/checkout.js"></script>
166
+ #
167
+ # Note that the default javascript files will be included first. So Prototype and Scriptaculous are available to
168
+ # all subsequently included files.
169
+ #
170
+ # == Caching multiple javascripts into one
171
+ #
172
+ # You can also cache multiple javascripts into one file, which requires less HTTP connections to download and can better be
173
+ # compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching
174
+ # is set to <tt>true</tt> (which is the case by default for the Rails production environment, but not for the development
175
+ # environment).
176
+ #
177
+ # ==== Examples
178
+ # javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is false =>
179
+ # <script type="text/javascript" src="/javascripts/prototype.js"></script>
180
+ # <script type="text/javascript" src="/javascripts/effects.js"></script>
181
+ # ...
182
+ # <script type="text/javascript" src="/javascripts/application.js"></script>
183
+ # <script type="text/javascript" src="/javascripts/shop.js"></script>
184
+ # <script type="text/javascript" src="/javascripts/checkout.js"></script>
185
+ #
186
+ # javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is true =>
187
+ # <script type="text/javascript" src="/javascripts/all.js"></script>
188
+ #
189
+ # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when ActionController::Base.perform_caching is false =>
190
+ # <script type="text/javascript" src="/javascripts/prototype.js"></script>
191
+ # <script type="text/javascript" src="/javascripts/cart.js"></script>
192
+ # <script type="text/javascript" src="/javascripts/checkout.js"></script>
193
+ #
194
+ # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when ActionController::Base.perform_caching is false =>
195
+ # <script type="text/javascript" src="/javascripts/shop.js"></script>
84
196
  def javascript_include_tag(*sources)
85
- options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { }
197
+ options = sources.extract_options!.stringify_keys
198
+ cache = options.delete("cache")
86
199
 
87
- if sources.include?(:defaults)
88
- sources = sources[0..(sources.index(:defaults))] +
89
- @@javascript_default_sources.dup +
90
- sources[(sources.index(:defaults) + 1)..sources.length]
200
+ if ActionController::Base.perform_caching && cache
201
+ joined_javascript_name = (cache == true ? "all" : cache) + ".js"
202
+ joined_javascript_path = File.join(JAVASCRIPTS_DIR, joined_javascript_name)
91
203
 
92
- sources.delete(:defaults)
93
- sources << "application" if defined?(RAILS_ROOT) && File.exists?("#{RAILS_ROOT}/public/javascripts/application.js")
94
- end
204
+ if !file_exist?(joined_javascript_path)
205
+ File.open(joined_javascript_path, "w+") do |cache|
206
+ javascript_paths = expand_javascript_sources(sources).collect do |source|
207
+ compute_public_path(source, 'javascripts', 'js', false)
208
+ end
209
+
210
+ cache.write(join_asset_file_contents(javascript_paths))
211
+ end
212
+ end
95
213
 
96
- sources.collect do |source|
97
- source = javascript_path(source)
98
- content_tag("script", "", { "type" => "text/javascript", "src" => source }.merge(options))
99
- end.join("\n")
214
+ content_tag("script", "", {
215
+ "type" => Mime::JS, "src" => path_to_javascript(joined_javascript_name)
216
+ }.merge(options))
217
+ else
218
+ expand_javascript_sources(sources).collect do |source|
219
+ content_tag("script", "", { "type" => Mime::JS, "src" => path_to_javascript(source) }.merge(options))
220
+ end.join("\n")
221
+ end
100
222
  end
101
-
223
+
102
224
  # Register one or more additional JavaScript files to be included when
103
225
  # <tt>javascript_include_tag :defaults</tt> is called. This method is
104
- # only intended to be called from plugin initialization to register additional
226
+ # typically intended to be called from plugin initialization to register additional
105
227
  # .js files that the plugin installed in <tt>public/javascripts</tt>.
106
228
  def self.register_javascript_include_default(*sources)
107
229
  @@javascript_default_sources.concat(sources)
108
230
  end
109
-
231
+
110
232
  def self.reset_javascript_include_default #:nodoc:
111
233
  @@javascript_default_sources = JAVASCRIPT_DEFAULT_SOURCES.dup
112
234
  end
@@ -116,102 +238,288 @@ module ActionView
116
238
  # Full paths from the document root will be passed through.
117
239
  # Used internally by stylesheet_link_tag to build the stylesheet path.
118
240
  #
241
+ # ==== Examples
119
242
  # stylesheet_path "style" # => /stylesheets/style.css
120
243
  # stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css
121
244
  # stylesheet_path "/dir/style.css" # => /dir/style.css
245
+ # stylesheet_path "http://www.railsapplication.com/css/style" # => http://www.railsapplication.com/css/style.css
246
+ # stylesheet_path "http://www.railsapplication.com/css/style.js" # => http://www.railsapplication.com/css/style.css
122
247
  def stylesheet_path(source)
123
248
  compute_public_path(source, 'stylesheets', 'css')
124
249
  end
250
+ alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
125
251
 
126
252
  # Returns a stylesheet link tag for the sources specified as arguments. If
127
253
  # you don't specify an extension, .css will be appended automatically.
128
254
  # You can modify the link attributes by passing a hash as the last argument.
129
255
  #
256
+ # ==== Examples
130
257
  # stylesheet_link_tag "style" # =>
131
- # <link href="/stylesheets/style.css" media="screen" rel="Stylesheet" type="text/css" />
258
+ # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />
259
+ #
260
+ # stylesheet_link_tag "style.css" # =>
261
+ # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />
262
+ #
263
+ # stylesheet_link_tag "http://www.railsapplication.com/style.css" # =>
264
+ # <link href="http://www.railsapplication.com/style.css" media="screen" rel="stylesheet" type="text/css" />
132
265
  #
133
266
  # stylesheet_link_tag "style", :media => "all" # =>
134
- # <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css" />
267
+ # <link href="/stylesheets/style.css" media="all" rel="stylesheet" type="text/css" />
268
+ #
269
+ # stylesheet_link_tag "style", :media => "print" # =>
270
+ # <link href="/stylesheets/style.css" media="print" rel="stylesheet" type="text/css" />
135
271
  #
136
272
  # stylesheet_link_tag "random.styles", "/css/stylish" # =>
137
- # <link href="/stylesheets/random.styles" media="screen" rel="Stylesheet" type="text/css" />
138
- # <link href="/css/stylish.css" media="screen" rel="Stylesheet" type="text/css" />
273
+ # <link href="/stylesheets/random.styles" media="screen" rel="stylesheet" type="text/css" />
274
+ # <link href="/css/stylish.css" media="screen" rel="stylesheet" type="text/css" />
275
+ #
276
+ # You can also include all styles in the stylesheet directory using :all as the source:
277
+ #
278
+ # stylesheet_link_tag :all # =>
279
+ # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" type="text/css" />
280
+ # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" type="text/css" />
281
+ # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" type="text/css" />
282
+ #
283
+ # == Caching multiple stylesheets into one
284
+ #
285
+ # You can also cache multiple stylesheets into one file, which requires less HTTP connections and can better be
286
+ # compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching
287
+ # is set to true (which is the case by default for the Rails production environment, but not for the development
288
+ # environment). Examples:
289
+ #
290
+ # ==== Examples
291
+ # stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is false =>
292
+ # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" type="text/css" />
293
+ # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" type="text/css" />
294
+ # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" type="text/css" />
295
+ #
296
+ # stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is true =>
297
+ # <link href="/stylesheets/all.css" media="screen" rel="stylesheet" type="text/css" />
298
+ #
299
+ # stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when ActionController::Base.perform_caching is false =>
300
+ # <link href="/stylesheets/shop.css" media="screen" rel="stylesheet" type="text/css" />
301
+ # <link href="/stylesheets/cart.css" media="screen" rel="stylesheet" type="text/css" />
302
+ # <link href="/stylesheets/checkout.css" media="screen" rel="stylesheet" type="text/css" />
303
+ #
304
+ # stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when ActionController::Base.perform_caching is true =>
305
+ # <link href="/stylesheets/payment.css" media="screen" rel="stylesheet" type="text/css" />
139
306
  def stylesheet_link_tag(*sources)
140
- options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { }
141
- sources.collect do |source|
142
- source = stylesheet_path(source)
143
- tag("link", { "rel" => "Stylesheet", "type" => "text/css", "media" => "screen", "href" => source }.merge(options))
144
- end.join("\n")
307
+ options = sources.extract_options!.stringify_keys
308
+ cache = options.delete("cache")
309
+
310
+ if ActionController::Base.perform_caching && cache
311
+ joined_stylesheet_name = (cache == true ? "all" : cache) + ".css"
312
+ joined_stylesheet_path = File.join(STYLESHEETS_DIR, joined_stylesheet_name)
313
+
314
+ if !file_exist?(joined_stylesheet_path)
315
+ File.open(joined_stylesheet_path, "w+") do |cache|
316
+ stylesheet_paths = expand_stylesheet_sources(sources).collect do |source|
317
+ compute_public_path(source, 'stylesheets', 'css', false)
318
+ end
319
+
320
+ cache.write(join_asset_file_contents(stylesheet_paths))
321
+ end
322
+ end
323
+
324
+ tag("link", {
325
+ "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen",
326
+ "href" => html_escape(path_to_stylesheet(joined_stylesheet_name))
327
+ }.merge(options), false, false)
328
+ else
329
+ options.delete("cache")
330
+
331
+ expand_stylesheet_sources(sources).collect do |source|
332
+ tag("link", {
333
+ "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => html_escape(path_to_stylesheet(source))
334
+ }.merge(options), false, false)
335
+ end.join("\n")
336
+ end
145
337
  end
146
338
 
147
339
  # Computes the path to an image asset in the public images directory.
148
340
  # Full paths from the document root will be passed through.
149
- # Used internally by image_tag to build the image path. Passing
150
- # a filename without an extension is deprecated.
341
+ # Used internally by image_tag to build the image path.
151
342
  #
152
- # image_path("edit.png") # => /images/edit.png
153
- # image_path("icons/edit.png") # => /images/icons/edit.png
154
- # image_path("/icons/edit.png") # => /icons/edit.png
343
+ # ==== Examples
344
+ # image_path("edit") # => /images/edit
345
+ # image_path("edit.png") # => /images/edit.png
346
+ # image_path("icons/edit.png") # => /images/icons/edit.png
347
+ # image_path("/icons/edit.png") # => /icons/edit.png
348
+ # image_path("http://www.railsapplication.com/img/edit.png") # => http://www.railsapplication.com/img/edit.png
155
349
  def image_path(source)
156
- unless (source.split("/").last || source).include?(".") || source.blank?
157
- ActiveSupport::Deprecation.warn(
158
- "You've called image_path with a source that doesn't include an extension. " +
159
- "In Rails 2.0, that will not result in .png automatically being appended. " +
160
- "So you should call image_path('#{source}.png') instead", caller
161
- )
162
- end
163
-
164
- compute_public_path(source, 'images', 'png')
350
+ compute_public_path(source, 'images')
165
351
  end
352
+ alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
166
353
 
167
354
  # Returns an html image tag for the +source+. The +source+ can be a full
168
- # path or a file that exists in your public images directory. Note that
169
- # specifying a filename without the extension is now deprecated in Rails.
170
- # You can add html attributes using the +options+. The +options+ supports
171
- # two additional keys for convienence and conformance:
355
+ # path or a file that exists in your public images directory.
356
+ #
357
+ # ==== Options
358
+ # You can add HTML attributes using the +options+. The +options+ supports
359
+ # three additional keys for convenience and conformance:
172
360
  #
173
- # * <tt>:alt</tt> - If no alt text is given, the file name part of the
361
+ # * <tt>:alt</tt> - If no alt text is given, the file name part of the
174
362
  # +source+ is used (capitalized and without the extension)
175
- # * <tt>:size</tt> - Supplied as "{Width}x{Height}", so "30x45" becomes
363
+ # * <tt>:size</tt> - Supplied as "{Width}x{Height}", so "30x45" becomes
176
364
  # width="30" and height="45". <tt>:size</tt> will be ignored if the
177
365
  # value is not in the correct format.
366
+ # * <tt>:mouseover</tt> - Set an alternate image to be used when the onmouseover
367
+ # event is fired, and sets the original image to be replaced onmouseout.
368
+ # This can be used to implement an easy image toggle that fires on onmouseover.
178
369
  #
370
+ # ==== Examples
371
+ # image_tag("icon") # =>
372
+ # <img src="/images/icon" alt="Icon" />
179
373
  # image_tag("icon.png") # =>
180
374
  # <img src="/images/icon.png" alt="Icon" />
181
375
  # image_tag("icon.png", :size => "16x10", :alt => "Edit Entry") # =>
182
376
  # <img src="/images/icon.png" width="16" height="10" alt="Edit Entry" />
183
377
  # image_tag("/icons/icon.gif", :size => "16x16") # =>
184
378
  # <img src="/icons/icon.gif" width="16" height="16" alt="Icon" />
379
+ # image_tag("/icons/icon.gif", :height => '32', :width => '32') # =>
380
+ # <img alt="Icon" height="32" src="/icons/icon.gif" width="32" />
381
+ # image_tag("/icons/icon.gif", :class => "menu_icon") # =>
382
+ # <img alt="Icon" class="menu_icon" src="/icons/icon.gif" />
383
+ # image_tag("mouse.png", :mouseover => "/images/mouse_over.png") # =>
384
+ # <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
385
+ # image_tag("mouse.png", :mouseover => image_path("mouse_over.png")) # =>
386
+ # <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
185
387
  def image_tag(source, options = {})
186
388
  options.symbolize_keys!
187
-
188
- options[:src] = image_path(source)
389
+
390
+ options[:src] = path_to_image(source)
189
391
  options[:alt] ||= File.basename(options[:src], '.*').split('.').first.capitalize
190
-
191
- if options[:size]
192
- options[:width], options[:height] = options[:size].split("x") if options[:size] =~ %r{^\d+x\d+$}
193
- options.delete(:size)
392
+
393
+ if size = options.delete(:size)
394
+ options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
395
+ end
396
+
397
+ if mouseover = options.delete(:mouseover)
398
+ options[:onmouseover] = "this.src='#{image_path(mouseover)}'"
399
+ options[:onmouseout] = "this.src='#{image_path(options[:src])}'"
194
400
  end
195
401
 
196
402
  tag("img", options)
197
403
  end
198
-
404
+
199
405
  private
200
- def compute_public_path(source, dir, ext)
201
- source = source.dup
202
- source << ".#{ext}" if File.extname(source).blank?
203
- unless source =~ %r{^[-a-z]+://}
204
- source = "/#{dir}/#{source}" unless source[0] == ?/
205
- asset_id = rails_asset_id(source)
206
- source << '?' + asset_id if defined?(RAILS_ROOT) && !asset_id.blank?
207
- source = "#{ActionController::Base.asset_host}#{@controller.request.relative_url_root}#{source}"
406
+ def file_exist?(path)
407
+ @@file_exist_cache ||= {}
408
+ if !(@@file_exist_cache[path] ||= File.exist?(path))
409
+ @@file_exist_cache[path] = true
410
+ false
411
+ else
412
+ true
413
+ end
414
+ end
415
+
416
+ # Add the .ext if not present. Return full URLs otherwise untouched.
417
+ # Prefix with /dir/ if lacking a leading /. Account for relative URL
418
+ # roots. Rewrite the asset path for cache-busting asset ids. Include
419
+ # a single or wildcarded asset host, if configured, with the correct
420
+ # request protocol.
421
+ def compute_public_path(source, dir, ext = nil, include_host = true)
422
+ has_request = @controller.respond_to?(:request)
423
+
424
+ cache_key =
425
+ if has_request
426
+ [ @controller.request.protocol,
427
+ ActionController::Base.asset_host,
428
+ @controller.request.relative_url_root,
429
+ dir, source, ext, include_host ].join
430
+ else
431
+ [ ActionController::Base.asset_host,
432
+ dir, source, ext, include_host ].join
433
+ end
434
+
435
+ ActionView::Base.computed_public_paths[cache_key] ||=
436
+ begin
437
+ source += ".#{ext}" if File.extname(source).blank? && ext
438
+
439
+ if source =~ %r{^[-a-z]+://}
440
+ source
441
+ else
442
+ source = "/#{dir}/#{source}" unless source[0] == ?/
443
+ if has_request
444
+ source = "#{@controller.request.relative_url_root}#{source}"
445
+ end
446
+ rewrite_asset_path!(source)
447
+
448
+ if include_host
449
+ host = compute_asset_host(source)
450
+
451
+ if has_request && !host.blank? && host !~ %r{^[-a-z]+://}
452
+ host = "#{@controller.request.protocol}#{host}"
453
+ end
454
+
455
+ "#{host}#{source}"
456
+ else
457
+ source
458
+ end
459
+ end
460
+ end
461
+ end
462
+
463
+ # Pick an asset host for this source. Returns nil if no host is set,
464
+ # the host if no wildcard is set, or the host interpolated with the
465
+ # numbers 0-3 if it contains %d. The number is the source hash mod 4.
466
+ def compute_asset_host(source)
467
+ if host = ActionController::Base.asset_host
468
+ host % (source.hash % 4)
208
469
  end
209
- source
210
470
  end
211
-
471
+
472
+ # Use the RAILS_ASSET_ID environment variable or the source's
473
+ # modification time as its cache-busting asset id.
212
474
  def rails_asset_id(source)
213
- ENV["RAILS_ASSET_ID"] ||
214
- File.mtime("#{RAILS_ROOT}/public/#{source}").to_i.to_s rescue ""
475
+ if asset_id = ENV["RAILS_ASSET_ID"]
476
+ asset_id
477
+ else
478
+ path = File.join(ASSETS_DIR, source)
479
+
480
+ if File.exist?(path)
481
+ File.mtime(path).to_i.to_s
482
+ else
483
+ ''
484
+ end
485
+ end
486
+ end
487
+
488
+ # Break out the asset path rewrite so you wish to put the asset id
489
+ # someplace other than the query string.
490
+ def rewrite_asset_path!(source)
491
+ asset_id = rails_asset_id(source)
492
+ source << "?#{asset_id}" if !asset_id.blank?
493
+ end
494
+
495
+ def expand_javascript_sources(sources)
496
+ case
497
+ when sources.include?(:all)
498
+ all_javascript_files = Dir[File.join(JAVASCRIPTS_DIR, '*.js')].collect { |file| File.basename(file).split(".", 0).first }.sort
499
+ sources = ((@@javascript_default_sources.dup & all_javascript_files) + all_javascript_files).uniq
500
+
501
+ when sources.include?(:defaults)
502
+ sources = sources[0..(sources.index(:defaults))] +
503
+ @@javascript_default_sources.dup +
504
+ sources[(sources.index(:defaults) + 1)..sources.length]
505
+
506
+ sources.delete(:defaults)
507
+ sources << "application" if file_exist?(File.join(JAVASCRIPTS_DIR, "application.js"))
508
+ end
509
+
510
+ sources
511
+ end
512
+
513
+ def expand_stylesheet_sources(sources)
514
+ if sources.first == :all
515
+ @@all_stylesheet_sources ||= Dir[File.join(STYLESHEETS_DIR, '*.css')].collect { |file| File.basename(file).split(".", 1).first }.sort
516
+ else
517
+ sources
518
+ end
519
+ end
520
+
521
+ def join_asset_file_contents(paths)
522
+ paths.collect { |path| File.read(File.join(ASSETS_DIR, path.split("?").first)) }.join("\n\n")
215
523
  end
216
524
  end
217
525
  end