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,10 +1,14 @@
1
1
  module ActionView
2
2
  module Helpers
3
- # Provides a set of methods for making it easier to locate problems.
3
+ # Provides a set of methods for making it easier to debug Rails objects.
4
4
  module DebugHelper
5
- # Returns a <pre>-tag set with the +object+ dumped by YAML. Very readable way to inspect an object.
5
+ # Returns a <pre>-tag that has +object+ dumped by YAML. This creates a very
6
+ # readable way to inspect an object.
7
+ #
8
+ # ==== Example
6
9
  # my_hash = {'first' => 1, 'second' => 'two', 'third' => [1,2,3]}
7
10
  # debug(my_hash)
11
+ #
8
12
  # => <pre class='debug_dump'>---
9
13
  # first: 1
10
14
  # second: two
@@ -1,72 +1,67 @@
1
1
  require 'cgi'
2
- require File.dirname(__FILE__) + '/date_helper'
3
- require File.dirname(__FILE__) + '/tag_helper'
2
+ require 'action_view/helpers/date_helper'
3
+ require 'action_view/helpers/tag_helper'
4
4
 
5
5
  module ActionView
6
6
  module Helpers
7
- # Provides a set of methods for working with forms and especially forms related to objects assigned to the template.
8
- # The following is an example of a complete form for a person object that works for both creates and updates built
9
- # with all the form helpers. The <tt>@person</tt> object was assigned by an action on the controller:
10
- # <form action="save_person" method="post">
11
- # Name:
12
- # <%= text_field "person", "name", "size" => 20 %>
7
+ # Form helpers are designed to make working with models much easier compared to using just standard HTML
8
+ # elements by providing a set of methods for creating forms based on your models. This helper generates the HTML
9
+ # for forms, providing a method for each sort of input (e.g., text, password, select, and so on). When the form
10
+ # is submitted (i.e., when the user hits the submit button or <tt>form.submit</tt> is called via JavaScript), the form inputs will be bundled into the <tt>params</tt> object and passed back to the controller.
13
11
  #
14
- # Password:
15
- # <%= password_field "person", "password", "maxsize" => 20 %>
12
+ # There are two types of form helpers: those that specifically work with model attributes and those that don't.
13
+ # This helper deals with those that work with model attributes; to see an example of form helpers that don't work
14
+ # with model attributes, check the ActionView::Helpers::FormTagHelper documentation.
16
15
  #
17
- # Single?:
18
- # <%= check_box "person", "single" %>
16
+ # The core method of this helper, form_for, gives you the ability to create a form for a model instance;
17
+ # for example, let's say that you have a model <tt>Person</tt> and want to create a new instance of it:
19
18
  #
20
- # Description:
21
- # <%= text_area "person", "description", "cols" => 20 %>
19
+ # # Note: a @person variable will have been created in the controller.
20
+ # # For example: @person = Person.new
21
+ # <% form_for :person, @person, :url => { :action => "create" } do |f| %>
22
+ # <%= f.text_field :first_name %>
23
+ # <%= f.text_field :last_name %>
24
+ # <%= submit_tag 'Create' %>
25
+ # <% end %>
22
26
  #
23
- # <input type="submit" value="Save">
24
- # </form>
27
+ # The HTML generated for this would be:
25
28
  #
26
- # ...is compiled to:
29
+ # <form action="/persons/create" method="post">
30
+ # <input id="person_first_name" name="person[first_name]" size="30" type="text" />
31
+ # <input id="person_last_name" name="person[last_name]" size="30" type="text" />
32
+ # <input name="commit" type="submit" value="Create" />
33
+ # </form>
27
34
  #
28
- # <form action="save_person" method="post">
29
- # Name:
30
- # <input type="text" id="person_name" name="person[name]"
31
- # size="20" value="<%= @person.name %>" />
35
+ # The <tt>params</tt> object created when this form is submitted would look like:
32
36
  #
33
- # Password:
34
- # <input type="password" id="person_password" name="person[password]"
35
- # size="20" maxsize="20" value="<%= @person.password %>" />
37
+ # {"action"=>"create", "controller"=>"persons", "person"=>{"first_name"=>"William", "last_name"=>"Smith"}}
36
38
  #
37
- # Single?:
38
- # <input type="checkbox" id="person_single" name="person[single]" value="1" />
39
+ # The params hash has a nested <tt>person</tt> value, which can therefore be accessed with <tt>params[:person]</tt> in the controller.
40
+ # If were editing/updating an instance (e.g., <tt>Person.find(1)</tt> rather than <tt>Person.new</tt> in the controller), the objects
41
+ # attribute values are filled into the form (e.g., the <tt>person_first_name</tt> field would have that person's first name in it).
39
42
  #
40
- # Description:
41
- # <textarea cols="20" rows="40" id="person_description" name="person[description]">
42
- # <%= @person.description %>
43
- # </textarea>
44
- #
45
- # <input type="submit" value="Save">
46
- # </form>
47
- #
48
- # If the object name contains square brackets the id for the object will be inserted. Example:
43
+ # If the object name contains square brackets the id for the object will be inserted. For example:
49
44
  #
50
45
  # <%= text_field "person[]", "name" %>
51
- #
52
- # ...becomes:
46
+ #
47
+ # ...will generate the following ERb.
53
48
  #
54
49
  # <input type="text" id="person_<%= @person.id %>_name" name="person[<%= @person.id %>][name]" value="<%= @person.name %>" />
55
50
  #
56
51
  # If the helper is being used to generate a repetitive sequence of similar form elements, for example in a partial
57
- # used by render_collection_of_partials, the "index" option may come in handy. Example:
52
+ # used by <tt>render_collection_of_partials</tt>, the <tt>index</tt> option may come in handy. Example:
58
53
  #
59
54
  # <%= text_field "person", "name", "index" => 1 %>
60
55
  #
61
- # becomes
56
+ # ...becomes...
62
57
  #
63
58
  # <input type="text" id="person_1_name" name="person[1][name]" value="<%= @person.name %>" />
64
59
  #
65
- # There's also methods for helping to build form tags in link:classes/ActionView/Helpers/FormOptionsHelper.html,
60
+ # There are also methods for helping to build form tags in link:classes/ActionView/Helpers/FormOptionsHelper.html,
66
61
  # link:classes/ActionView/Helpers/DateHelper.html, and link:classes/ActionView/Helpers/ActiveRecordHelper.html
67
62
  module FormHelper
68
- # Creates a form and a scope around a specific model object, which is then used as a base for questioning about
69
- # values for the fields. Examples:
63
+ # Creates a form and a scope around a specific model object that is used as a base for questioning about
64
+ # values for the fields.
70
65
  #
71
66
  # <% form_for :person, @person, :url => { :action => "update" } do |f| %>
72
67
  # First name: <%= f.text_field :first_name %>
@@ -75,18 +70,18 @@ module ActionView
75
70
  # Admin? : <%= f.check_box :admin %>
76
71
  # <% end %>
77
72
  #
78
- # Worth noting is that the form_for tag is called in a ERb evaluation block, not a ERb output block. So that's <tt><% %></tt>,
79
- # not <tt><%= %></tt>. Also worth noting is that the form_for yields a form_builder object, in this example as f, which emulates
73
+ # Worth noting is that the form_for tag is called in a ERb evaluation block, not an ERb output block. So that's <tt><% %></tt>,
74
+ # not <tt><%= %></tt>. Also worth noting is that form_for yields a <tt>form_builder</tt> object, in this example as <tt>f</tt>, which emulates
80
75
  # the API for the stand-alone FormHelper methods, but without the object name. So instead of <tt>text_field :person, :name</tt>,
81
- # you get away with <tt>f.text_field :name</tt>.
76
+ # you get away with <tt>f.text_field :name</tt>.
82
77
  #
83
- # That in itself is a modest increase in comfort. The big news is that form_for allows us to more easily escape the instance
84
- # variable convention, so while the stand-alone approach would require <tt>text_field :person, :name, :object => person</tt>
78
+ # Even further, the form_for method allows you to more easily escape the instance variable convention. So while the stand-alone
79
+ # approach would require <tt>text_field :person, :name, :object => person</tt>
85
80
  # to work with local variables instead of instance ones, the form_for calls remain the same. You simply declare once with
86
81
  # <tt>:person, person</tt> and all subsequent field calls save <tt>:person</tt> and <tt>:object => person</tt>.
87
82
  #
88
83
  # Also note that form_for doesn't create an exclusive scope. It's still possible to use both the stand-alone FormHelper methods
89
- # and methods from FormTagHelper. Example:
84
+ # and methods from FormTagHelper. For example:
90
85
  #
91
86
  # <% form_for :person, @person, :url => { :action => "update" } do |f| %>
92
87
  # First name: <%= f.text_field :first_name %>
@@ -95,84 +90,260 @@ module ActionView
95
90
  # Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %>
96
91
  # <% end %>
97
92
  #
98
- # Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base.
99
- # Like collection_select and datetime_select.
93
+ # Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base,
94
+ # like FormOptionHelper#collection_select and DateHelper#datetime_select.
95
+ #
96
+ # HTML attributes for the form tag can be given as :html => {...}. For example:
100
97
  #
101
- # Html attributes for the form tag can be given as :html => {...}. Example:
102
- #
103
98
  # <% form_for :person, @person, :html => {:id => 'person_form'} do |f| %>
104
99
  # ...
105
100
  # <% end %>
106
101
  #
102
+ # The above form will then have the <tt>id</tt> attribute with the value </tt>person_form</tt>, which you can then
103
+ # style with CSS or manipulate with JavaScript.
104
+ #
105
+ # === Relying on record identification
106
+ #
107
+ # In addition to manually configuring the form_for call, you can also rely on record identification, which will use
108
+ # the conventions and named routes of that approach. Examples:
109
+ #
110
+ # <% form_for(@post) do |f| %>
111
+ # ...
112
+ # <% end %>
113
+ #
114
+ # This will expand to be the same as:
115
+ #
116
+ # <% form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
117
+ # ...
118
+ # <% end %>
119
+ #
120
+ # And for new records:
121
+ #
122
+ # <% form_for(Post.new) do |f| %>
123
+ # ...
124
+ # <% end %>
125
+ #
126
+ # This will expand to be the same as:
127
+ #
128
+ # <% form_for :post, @post, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %>
129
+ # ...
130
+ # <% end %>
131
+ #
132
+ # You can also overwrite the individual conventions, like this:
133
+ #
134
+ # <% form_for(@post, :url => super_post_path(@post)) do |f| %>
135
+ # ...
136
+ # <% end %>
137
+ #
138
+ # And for namespaced routes, like admin_post_url:
139
+ #
140
+ # <% form_for([:admin, @post]) do |f| %>
141
+ # ...
142
+ # <% end %>
143
+ #
144
+ # === Customized form builders
145
+ #
107
146
  # You can also build forms using a customized FormBuilder class. Subclass FormBuilder and override or define some more helpers,
108
- # then use your custom builder like so:
109
- #
147
+ # then use your custom builder. For example, let's say you made a helper to automatically add labels to form inputs.
148
+ #
110
149
  # <% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %>
111
150
  # <%= f.text_field :first_name %>
112
151
  # <%= f.text_field :last_name %>
113
152
  # <%= text_area :person, :biography %>
114
153
  # <%= check_box_tag "person[admin]", @person.company.admin? %>
115
154
  # <% end %>
116
- #
117
- # In many cases you will want to wrap the above in another helper, such as:
155
+ #
156
+ # In many cases you will want to wrap the above in another helper, so you could do something like the following:
118
157
  #
119
158
  # def labelled_form_for(name, object, options, &proc)
120
159
  # form_for(name, object, options.merge(:builder => LabellingFormBuiler), &proc)
121
160
  # end
122
161
  #
123
- def form_for(object_name, *args, &proc)
162
+ # If you don't need to attach a form to a model instance, then check out FormTagHelper#form_tag.
163
+ def form_for(record_or_name_or_array, *args, &proc)
124
164
  raise ArgumentError, "Missing block" unless block_given?
125
- options = args.last.is_a?(Hash) ? args.pop : {}
165
+
166
+ options = args.extract_options!
167
+
168
+ case record_or_name_or_array
169
+ when String, Symbol
170
+ object_name = record_or_name_or_array
171
+ when Array
172
+ object = record_or_name_or_array.last
173
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
174
+ apply_form_for_options!(record_or_name_or_array, options)
175
+ args.unshift object
176
+ else
177
+ object = record_or_name_or_array
178
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
179
+ apply_form_for_options!([object], options)
180
+ args.unshift object
181
+ end
182
+
126
183
  concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}), proc.binding)
127
184
  fields_for(object_name, *(args << options), &proc)
128
185
  concat('</form>', proc.binding)
129
186
  end
130
187
 
188
+ def apply_form_for_options!(object_or_array, options) #:nodoc:
189
+ object = object_or_array.is_a?(Array) ? object_or_array.last : object_or_array
190
+
191
+ html_options =
192
+ if object.respond_to?(:new_record?) && object.new_record?
193
+ { :class => dom_class(object, :new), :id => dom_id(object), :method => :post }
194
+ else
195
+ { :class => dom_class(object, :edit), :id => dom_id(object, :edit), :method => :put }
196
+ end
197
+
198
+ options[:html] ||= {}
199
+ options[:html].reverse_merge!(html_options)
200
+ options[:url] ||= polymorphic_path(object_or_array)
201
+ end
202
+
131
203
  # Creates a scope around a specific model object like form_for, but doesn't create the form tags themselves. This makes
132
- # fields_for suitable for specifying additional model objects in the same form. Example:
204
+ # fields_for suitable for specifying additional model objects in the same form:
133
205
  #
134
- # <% form_for :person, @person, :url => { :action => "update" } do |person_form| %>
206
+ # ==== Examples
207
+ # <% form_for @person, :url => { :action => "update" } do |person_form| %>
135
208
  # First name: <%= person_form.text_field :first_name %>
136
209
  # Last name : <%= person_form.text_field :last_name %>
137
- #
138
- # <% fields_for :permission, @person.permission do |permission_fields| %>
210
+ #
211
+ # <% fields_for @person.permission do |permission_fields| %>
139
212
  # Admin? : <%= permission_fields.check_box :admin %>
140
213
  # <% end %>
141
214
  # <% end %>
142
215
  #
143
- # Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base.
144
- # Like collection_select and datetime_select.
145
- def fields_for(object_name, *args, &block)
216
+ # ...or if you have an object that needs to be represented as a different parameter, like a Client that acts as a Person:
217
+ #
218
+ # <% fields_for :person, @client do |permission_fields| %>
219
+ # Admin?: <%= permission_fields.check_box :admin %>
220
+ # <% end %>
221
+ #
222
+ # ...or if you don't have an object, just a name of the parameter
223
+ #
224
+ # <% fields_for :person do |permission_fields| %>
225
+ # Admin?: <%= permission_fields.check_box :admin %>
226
+ # <% end %>
227
+ #
228
+ # Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base,
229
+ # like FormOptionHelper#collection_select and DateHelper#datetime_select.
230
+ def fields_for(record_or_name_or_array, *args, &block)
146
231
  raise ArgumentError, "Missing block" unless block_given?
147
- options = args.last.is_a?(Hash) ? args.pop : {}
148
- object = args.first
232
+ options = args.extract_options!
233
+
234
+ case record_or_name_or_array
235
+ when String, Symbol
236
+ object_name = record_or_name_or_array
237
+ object = args.first
238
+ when Array
239
+ object = record_or_name_or_array.last
240
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
241
+ apply_form_for_options!(record_or_name_or_array, options)
242
+ else
243
+ object = record_or_name_or_array
244
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
245
+ end
149
246
 
150
247
  builder = options[:builder] || ActionView::Base.default_form_builder
151
248
  yield builder.new(object_name, object, self, options, block)
152
249
  end
153
250
 
251
+ # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
252
+ # assigned to the template (identified by +object+). The text of label will default to the attribute name unless you specify
253
+ # it explicitly. Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
254
+ # onto the HTML as an HTML element attribute as in the example shown.
255
+ #
256
+ # ==== Examples
257
+ # label(:post, :title)
258
+ # #=> <label for="post_title">Title</label>
259
+ #
260
+ # label(:post, :title, "A short title")
261
+ # #=> <label for="post_title">A short title</label>
262
+ #
263
+ # label(:post, :title, "A short title", :class => "title_label")
264
+ # #=> <label for="post_title" class="title_label">A short title</label>
265
+ #
266
+ def label(object_name, method, text = nil, options = {})
267
+ InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_label_tag(text, options)
268
+ end
269
+
154
270
  # Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
155
271
  # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
156
- # hash with +options+.
272
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
273
+ # shown.
274
+ #
275
+ # ==== Examples
276
+ # text_field(:post, :title, :size => 20)
277
+ # # => <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" />
278
+ #
279
+ # text_field(:post, :title, :class => "create_input")
280
+ # # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
281
+ #
282
+ # text_field(:session, :user, :onchange => "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }")
283
+ # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange = "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }"/>
284
+ #
285
+ # text_field(:snippet, :code, :size => 20, :class => 'code_input')
286
+ # # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
157
287
  #
158
- # Examples (call, result):
159
- # text_field("post", "title", "size" => 20)
160
- # <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" />
161
288
  def text_field(object_name, method, options = {})
162
289
  InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("text", options)
163
290
  end
164
291
 
165
- # Works just like text_field, but returns an input tag of the "password" type instead.
292
+ # Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
293
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
294
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
295
+ # shown.
296
+ #
297
+ # ==== Examples
298
+ # password_field(:login, :pass, :size => 20)
299
+ # # => <input type="text" id="login_pass" name="login[pass]" size="20" value="#{@login.pass}" />
300
+ #
301
+ # password_field(:account, :secret, :class => "form_input")
302
+ # # => <input type="text" id="account_secret" name="account[secret]" value="#{@account.secret}" class="form_input" />
303
+ #
304
+ # password_field(:user, :password, :onchange => "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }")
305
+ # # => <input type="text" id="user_password" name="user[password]" value="#{@user.password}" onchange = "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }"/>
306
+ #
307
+ # password_field(:account, :pin, :size => 20, :class => 'form_input')
308
+ # # => <input type="text" id="account_pin" name="account[pin]" size="20" value="#{@account.pin}" class="form_input" />
309
+ #
166
310
  def password_field(object_name, method, options = {})
167
311
  InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("password", options)
168
312
  end
169
313
 
170
- # Works just like text_field, but returns an input tag of the "hidden" type instead.
314
+ # Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
315
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
316
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
317
+ # shown.
318
+ #
319
+ # ==== Examples
320
+ # hidden_field(:signup, :pass_confirm)
321
+ # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
322
+ #
323
+ # hidden_field(:post, :tag_list)
324
+ # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
325
+ #
326
+ # hidden_field(:user, :token)
327
+ # # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
171
328
  def hidden_field(object_name, method, options = {})
172
329
  InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("hidden", options)
173
330
  end
174
331
 
175
- # Works just like text_field, but returns an input tag of the "file" type instead, which won't have a default value.
332
+ # Returns an file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
333
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
334
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
335
+ # shown.
336
+ #
337
+ # ==== Examples
338
+ # file_field(:user, :avatar)
339
+ # # => <input type="file" id="user_avatar" name="user[avatar]" />
340
+ #
341
+ # file_field(:post, :attached, :accept => 'text/html')
342
+ # # => <input type="file" id="post_attached" name="post[attached]" />
343
+ #
344
+ # file_field(:attachment, :file, :class => 'file_input')
345
+ # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
346
+ #
176
347
  def file_field(object_name, method, options = {})
177
348
  InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("file", options)
178
349
  end
@@ -181,11 +352,26 @@ module ActionView
181
352
  # on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
182
353
  # hash with +options+.
183
354
  #
184
- # Example (call, result):
185
- # text_area("post", "body", "cols" => 20, "rows" => 40)
186
- # <textarea cols="20" rows="40" id="post_body" name="post[body]">
187
- # #{@post.body}
188
- # </textarea>
355
+ # ==== Examples
356
+ # text_area(:post, :body, :cols => 20, :rows => 40)
357
+ # # => <textarea cols="20" rows="40" id="post_body" name="post[body]">
358
+ # # #{@post.body}
359
+ # # </textarea>
360
+ #
361
+ # text_area(:comment, :text, :size => "20x30")
362
+ # # => <textarea cols="20" rows="30" id="comment_text" name="comment[text]">
363
+ # # #{@comment.text}
364
+ # # </textarea>
365
+ #
366
+ # text_area(:application, :notes, :cols => 40, :rows => 15, :class => 'app_input')
367
+ # # => <textarea cols="40" rows="15" id="application_notes" name="application[notes]" class="app_input">
368
+ # # #{@application.notes}
369
+ # # </textarea>
370
+ #
371
+ # text_area(:entry, :body, :size => "20x20", :disabled => 'disabled')
372
+ # # => <textarea cols="20" rows="20" id="entry_body" name="entry[body]" disabled="disabled">
373
+ # # #{@entry.body}
374
+ # # </textarea>
189
375
  def text_area(object_name, method, options = {})
190
376
  InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_text_area_tag(options)
191
377
  end
@@ -194,18 +380,24 @@ module ActionView
194
380
  # assigned to the template (identified by +object+). It's intended that +method+ returns an integer and if that
195
381
  # integer is above zero, then the checkbox is checked. Additional options on the input tag can be passed as a
196
382
  # hash with +options+. The +checked_value+ defaults to 1 while the default +unchecked_value+
197
- # is set to 0 which is convenient for boolean values. Usually unchecked checkboxes don't post anything.
198
- # We work around this problem by adding a hidden value with the same name as the checkbox.
383
+ # is set to 0 which is convenient for boolean values. Since HTTP standards say that unchecked checkboxes don't post anything,
384
+ # we add a hidden value with the same name as the checkbox as a work around.
199
385
  #
200
- # Example (call, result). Imagine that @post.validated? returns 1:
386
+ # ==== Examples
387
+ # # Let's say that @post.validated? is 1:
201
388
  # check_box("post", "validated")
202
- # <input type="checkbox" id="post_validate" name="post[validated]" value="1" checked="checked" />
203
- # <input name="post[validated]" type="hidden" value="0" />
389
+ # # => <input type="checkbox" id="post_validate" name="post[validated]" value="1" checked="checked" />
390
+ # # <input name="post[validated]" type="hidden" value="0" />
204
391
  #
205
- # Example (call, result). Imagine that @puppy.gooddog returns no:
392
+ # # Let's say that @puppy.gooddog is "no":
206
393
  # check_box("puppy", "gooddog", {}, "yes", "no")
207
- # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
208
- # <input name="puppy[gooddog]" type="hidden" value="no" />
394
+ # # => <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
395
+ # # <input name="puppy[gooddog]" type="hidden" value="no" />
396
+ #
397
+ # check_box("eula", "accepted", {}, "yes", "no", :class => 'eula_check')
398
+ # # => <input type="checkbox" id="eula_accepted" name="eula[accepted]" value="no" />
399
+ # # <input name="eula[accepted]" type="hidden" value="no" />
400
+ #
209
401
  def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
210
402
  InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
211
403
  end
@@ -214,12 +406,18 @@ module ActionView
214
406
  # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the
215
407
  # radio button will be checked. Additional options on the input tag can be passed as a
216
408
  # hash with +options+.
217
- # Example (call, result). Imagine that @post.category returns "rails":
409
+ #
410
+ # ==== Examples
411
+ # # Let's say that @post.category returns "rails":
218
412
  # radio_button("post", "category", "rails")
219
413
  # radio_button("post", "category", "java")
220
- # <input type="radio" id="post_category" name="post[category]" value="rails" checked="checked" />
221
- # <input type="radio" id="post_category" name="post[category]" value="java" />
414
+ # # => <input type="radio" id="post_category" name="post[category]" value="rails" checked="checked" />
415
+ # # <input type="radio" id="post_category" name="post[category]" value="java" />
222
416
  #
417
+ # radio_button("user", "receive_newsletter", "yes")
418
+ # radio_button("user", "receive_newsletter", "no")
419
+ # # => <input type="radio" id="user_receive_newsletter" name="user[receive_newsletter]" value="yes" />
420
+ # # <input type="radio" id="user_receive_newsletter" name="user[receive_newsletter]" value="no" checked="checked" />
223
421
  def radio_button(object_name, method, tag_value, options = {})
224
422
  InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_radio_button_tag(tag_value, options)
225
423
  end
@@ -240,17 +438,25 @@ module ActionView
240
438
  @template_object, @local_binding = template_object, local_binding
241
439
  @object = object
242
440
  if @object_name.sub!(/\[\]$/,"")
243
- if object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:id_before_type_cast)
244
- @auto_index = object.id_before_type_cast
441
+ if object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
442
+ @auto_index = object.to_param
245
443
  else
246
- raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to id_before_type_cast: #{object.inspect}"
444
+ raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
247
445
  end
248
446
  end
249
447
  end
250
448
 
449
+ def to_label_tag(text = nil, options = {})
450
+ name_and_id = options.dup
451
+ add_default_name_and_id(name_and_id)
452
+ options["for"] = name_and_id["id"]
453
+ content = (text.blank? ? nil : text.to_s) || method_name.humanize
454
+ content_tag("label", content, options)
455
+ end
456
+
251
457
  def to_input_field_tag(field_type, options = {})
252
458
  options = options.stringify_keys
253
- options["size"] ||= options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"]
459
+ options["size"] = options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] unless options.key?("size")
254
460
  options = DEFAULT_FIELD_OPTIONS.merge(options)
255
461
  if field_type == "hidden"
256
462
  options.delete("size")
@@ -273,7 +479,7 @@ module ActionView
273
479
  end
274
480
  options["checked"] = "checked" if checked
275
481
  pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
276
- options["id"] ||= defined?(@auto_index) ?
482
+ options["id"] ||= defined?(@auto_index) ?
277
483
  "#{@object_name}_#{@auto_index}_#{@method_name}_#{pretty_tag_value}" :
278
484
  "#{@object_name}_#{@method_name}_#{pretty_tag_value}"
279
485
  add_default_name_and_id(options)
@@ -285,7 +491,7 @@ module ActionView
285
491
  add_default_name_and_id(options)
286
492
 
287
493
  if size = options.delete("size")
288
- options["cols"], options["rows"] = size.split("x")
494
+ options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
289
495
  end
290
496
 
291
497
  content_tag("textarea", html_escape(options.delete('value') || value_before_type_cast(object)), options)
@@ -303,7 +509,7 @@ module ActionView
303
509
  end
304
510
  options["checked"] = "checked" if checked
305
511
  add_default_name_and_id(options)
306
- tag("input", options) << tag("input", "name" => options["name"], "type" => "hidden", "value" => unchecked_value)
512
+ tag("input", options) << tag("input", "name" => options["name"], "type" => "hidden", "value" => options['disabled'] && checked ? checked_value : unchecked_value)
307
513
  end
308
514
 
309
515
  def to_date_tag()
@@ -327,13 +533,13 @@ module ActionView
327
533
  tag_text << " selected" if value
328
534
  tag_text << ">True</option></select>"
329
535
  end
330
-
536
+
331
537
  def to_content_tag(tag_name, options = {})
332
538
  content_tag(tag_name, value(object), options)
333
539
  end
334
-
540
+
335
541
  def object
336
- @object || @template_object.instance_variable_get("@#{@object_name}")
542
+ @object || (@template_object.instance_variable_get("@#{@object_name}") rescue nil)
337
543
  end
338
544
 
339
545
  def value(object)
@@ -343,12 +549,12 @@ module ActionView
343
549
  def value_before_type_cast(object)
344
550
  self.class.value_before_type_cast(object, @method_name)
345
551
  end
346
-
552
+
347
553
  class << self
348
554
  def value(object, method_name)
349
555
  object.send method_name unless object.nil?
350
556
  end
351
-
557
+
352
558
  def value_before_type_cast(object, method_name)
353
559
  unless object.nil?
354
560
  object.respond_to?(method_name + "_before_type_cast") ?
@@ -356,7 +562,7 @@ module ActionView
356
562
  object.send(method_name)
357
563
  end
358
564
  end
359
-
565
+
360
566
  def check_box_checked?(value, checked_value)
361
567
  case value
362
568
  when TrueClass, FalseClass
@@ -371,7 +577,7 @@ module ActionView
371
577
  value.to_i != 0
372
578
  end
373
579
  end
374
-
580
+
375
581
  def radio_button_checked?(value, checked_value)
376
582
  value.to_s == checked_value.to_s
377
583
  end
@@ -401,11 +607,15 @@ module ActionView
401
607
  end
402
608
 
403
609
  def tag_id
404
- "#{@object_name}_#{@method_name}"
610
+ "#{sanitized_object_name}_#{@method_name}"
405
611
  end
406
612
 
407
613
  def tag_id_with_index(index)
408
- "#{@object_name}_#{index}_#{@method_name}"
614
+ "#{sanitized_object_name}_#{index}_#{@method_name}"
615
+ end
616
+
617
+ def sanitized_object_name
618
+ @object_name.gsub(/[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
409
619
  end
410
620
  end
411
621
 
@@ -417,10 +627,10 @@ module ActionView
417
627
  attr_accessor :object_name, :object, :options
418
628
 
419
629
  def initialize(object_name, object, template, options, proc)
420
- @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
630
+ @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
421
631
  end
422
-
423
- (field_helpers - %w(check_box radio_button)).each do |selector|
632
+
633
+ (field_helpers - %w(label check_box radio_button fields_for)).each do |selector|
424
634
  src = <<-end_src
425
635
  def #{selector}(method, options = {})
426
636
  @template.send(#{selector.inspect}, @object_name, method, options.merge(:object => @object))
@@ -428,14 +638,47 @@ module ActionView
428
638
  end_src
429
639
  class_eval src, __FILE__, __LINE__
430
640
  end
431
-
641
+
642
+ def fields_for(record_or_name_or_array, *args, &block)
643
+ case record_or_name_or_array
644
+ when String, Symbol
645
+ name = "#{object_name}[#{record_or_name_or_array}]"
646
+ when Array
647
+ object = record_or_name_or_array.last
648
+ name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
649
+ args.unshift(object)
650
+ else
651
+ object = record_or_name_or_array
652
+ name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
653
+ args.unshift(object)
654
+ end
655
+
656
+ @template.fields_for(name, *args, &block)
657
+ end
658
+
659
+ def label(method, text = nil, options = {})
660
+ @template.label(@object_name, method, text, options.merge(:object => @object))
661
+ end
662
+
432
663
  def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
433
664
  @template.check_box(@object_name, method, options.merge(:object => @object), checked_value, unchecked_value)
434
665
  end
435
-
666
+
436
667
  def radio_button(method, tag_value, options = {})
437
668
  @template.radio_button(@object_name, method, tag_value, options.merge(:object => @object))
438
669
  end
670
+
671
+ def error_message_on(method, prepend_text = "", append_text = "", css_class = "formError")
672
+ @template.error_message_on(@object, method, prepend_text, append_text, css_class)
673
+ end
674
+
675
+ def error_messages(options = {})
676
+ @template.error_messages_for(@object_name, options.merge(:object => @object))
677
+ end
678
+
679
+ def submit(value = "Save changes", options = {})
680
+ @template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit"))
681
+ end
439
682
  end
440
683
  end
441
684