actionpack_csi 2.3.5.p6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (429) hide show
  1. data/CHANGELOG +5184 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +409 -0
  4. data/RUNNING_UNIT_TESTS +24 -0
  5. data/Rakefile +160 -0
  6. data/install.rb +30 -0
  7. data/lib/action_controller/assertions/dom_assertions.rb +55 -0
  8. data/lib/action_controller/assertions/model_assertions.rb +21 -0
  9. data/lib/action_controller/assertions/response_assertions.rb +160 -0
  10. data/lib/action_controller/assertions/routing_assertions.rb +146 -0
  11. data/lib/action_controller/assertions/selector_assertions.rb +638 -0
  12. data/lib/action_controller/assertions/tag_assertions.rb +127 -0
  13. data/lib/action_controller/base.rb +1423 -0
  14. data/lib/action_controller/benchmarking.rb +107 -0
  15. data/lib/action_controller/caching/actions.rb +177 -0
  16. data/lib/action_controller/caching/fragments.rb +120 -0
  17. data/lib/action_controller/caching/pages.rb +152 -0
  18. data/lib/action_controller/caching/sweeper.rb +45 -0
  19. data/lib/action_controller/caching/sweeping.rb +55 -0
  20. data/lib/action_controller/caching.rb +71 -0
  21. data/lib/action_controller/cgi_ext/cookie.rb +112 -0
  22. data/lib/action_controller/cgi_ext/query_extension.rb +22 -0
  23. data/lib/action_controller/cgi_ext/stdinput.rb +24 -0
  24. data/lib/action_controller/cgi_ext.rb +15 -0
  25. data/lib/action_controller/cgi_process.rb +77 -0
  26. data/lib/action_controller/cookies.rb +95 -0
  27. data/lib/action_controller/dispatcher.rb +133 -0
  28. data/lib/action_controller/failsafe.rb +86 -0
  29. data/lib/action_controller/filters.rb +680 -0
  30. data/lib/action_controller/flash.rb +171 -0
  31. data/lib/action_controller/headers.rb +33 -0
  32. data/lib/action_controller/helpers.rb +225 -0
  33. data/lib/action_controller/http_authentication.rb +309 -0
  34. data/lib/action_controller/integration.rb +692 -0
  35. data/lib/action_controller/layout.rb +286 -0
  36. data/lib/action_controller/middleware_stack.rb +119 -0
  37. data/lib/action_controller/middlewares.rb +14 -0
  38. data/lib/action_controller/mime_responds.rb +193 -0
  39. data/lib/action_controller/mime_type.rb +212 -0
  40. data/lib/action_controller/mime_types.rb +21 -0
  41. data/lib/action_controller/params_parser.rb +77 -0
  42. data/lib/action_controller/performance_test.rb +15 -0
  43. data/lib/action_controller/polymorphic_routes.rb +189 -0
  44. data/lib/action_controller/rack_lint_patch.rb +36 -0
  45. data/lib/action_controller/record_identifier.rb +104 -0
  46. data/lib/action_controller/reloader.rb +54 -0
  47. data/lib/action_controller/request.rb +493 -0
  48. data/lib/action_controller/request_forgery_protection.rb +113 -0
  49. data/lib/action_controller/rescue.rb +183 -0
  50. data/lib/action_controller/resources.rb +682 -0
  51. data/lib/action_controller/response.rb +239 -0
  52. data/lib/action_controller/routing/builder.rb +197 -0
  53. data/lib/action_controller/routing/optimisations.rb +130 -0
  54. data/lib/action_controller/routing/recognition_optimisation.rb +167 -0
  55. data/lib/action_controller/routing/route.rb +265 -0
  56. data/lib/action_controller/routing/route_set.rb +502 -0
  57. data/lib/action_controller/routing/routing_ext.rb +49 -0
  58. data/lib/action_controller/routing/segments.rb +343 -0
  59. data/lib/action_controller/routing.rb +388 -0
  60. data/lib/action_controller/session/abstract_store.rb +181 -0
  61. data/lib/action_controller/session/cookie_store.rb +221 -0
  62. data/lib/action_controller/session/mem_cache_store.rb +51 -0
  63. data/lib/action_controller/session_management.rb +54 -0
  64. data/lib/action_controller/status_codes.rb +88 -0
  65. data/lib/action_controller/streaming.rb +181 -0
  66. data/lib/action_controller/string_coercion.rb +29 -0
  67. data/lib/action_controller/templates/rescues/_request_and_response.erb +24 -0
  68. data/lib/action_controller/templates/rescues/_trace.erb +26 -0
  69. data/lib/action_controller/templates/rescues/diagnostics.erb +11 -0
  70. data/lib/action_controller/templates/rescues/layout.erb +29 -0
  71. data/lib/action_controller/templates/rescues/missing_template.erb +2 -0
  72. data/lib/action_controller/templates/rescues/routing_error.erb +10 -0
  73. data/lib/action_controller/templates/rescues/template_error.erb +21 -0
  74. data/lib/action_controller/templates/rescues/unknown_action.erb +2 -0
  75. data/lib/action_controller/test_case.rb +209 -0
  76. data/lib/action_controller/test_process.rb +580 -0
  77. data/lib/action_controller/translation.rb +13 -0
  78. data/lib/action_controller/uploaded_file.rb +44 -0
  79. data/lib/action_controller/url_rewriter.rb +216 -0
  80. data/lib/action_controller/vendor/html-scanner/html/document.rb +68 -0
  81. data/lib/action_controller/vendor/html-scanner/html/node.rb +537 -0
  82. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +173 -0
  83. data/lib/action_controller/vendor/html-scanner/html/selector.rb +828 -0
  84. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +105 -0
  85. data/lib/action_controller/vendor/html-scanner/html/version.rb +11 -0
  86. data/lib/action_controller/vendor/html-scanner.rb +16 -0
  87. data/lib/action_controller/verification.rb +130 -0
  88. data/lib/action_controller.rb +113 -0
  89. data/lib/action_pack/version.rb +9 -0
  90. data/lib/action_pack.rb +24 -0
  91. data/lib/action_view/base.rb +362 -0
  92. data/lib/action_view/erb/util.rb +44 -0
  93. data/lib/action_view/helpers/active_record_helper.rb +305 -0
  94. data/lib/action_view/helpers/asset_tag_helper.rb +694 -0
  95. data/lib/action_view/helpers/atom_feed_helper.rb +198 -0
  96. data/lib/action_view/helpers/benchmark_helper.rb +54 -0
  97. data/lib/action_view/helpers/cache_helper.rb +39 -0
  98. data/lib/action_view/helpers/capture_helper.rb +136 -0
  99. data/lib/action_view/helpers/date_helper.rb +988 -0
  100. data/lib/action_view/helpers/debug_helper.rb +38 -0
  101. data/lib/action_view/helpers/form_helper.rb +1074 -0
  102. data/lib/action_view/helpers/form_options_helper.rb +600 -0
  103. data/lib/action_view/helpers/form_tag_helper.rb +487 -0
  104. data/lib/action_view/helpers/javascript_helper.rb +208 -0
  105. data/lib/action_view/helpers/number_helper.rb +308 -0
  106. data/lib/action_view/helpers/prototype_helper.rb +1305 -0
  107. data/lib/action_view/helpers/raw_output_helper.rb +9 -0
  108. data/lib/action_view/helpers/record_identification_helper.rb +20 -0
  109. data/lib/action_view/helpers/record_tag_helper.rb +58 -0
  110. data/lib/action_view/helpers/sanitize_helper.rb +259 -0
  111. data/lib/action_view/helpers/scriptaculous_helper.rb +226 -0
  112. data/lib/action_view/helpers/tag_helper.rb +150 -0
  113. data/lib/action_view/helpers/text_helper.rb +587 -0
  114. data/lib/action_view/helpers/translation_helper.rb +39 -0
  115. data/lib/action_view/helpers/url_helper.rb +639 -0
  116. data/lib/action_view/helpers.rb +59 -0
  117. data/lib/action_view/inline_template.rb +19 -0
  118. data/lib/action_view/locale/en.yml +117 -0
  119. data/lib/action_view/partials.rb +240 -0
  120. data/lib/action_view/paths.rb +69 -0
  121. data/lib/action_view/reloadable_template.rb +117 -0
  122. data/lib/action_view/renderable.rb +95 -0
  123. data/lib/action_view/renderable_partial.rb +47 -0
  124. data/lib/action_view/safe_buffer.rb +28 -0
  125. data/lib/action_view/template.rb +252 -0
  126. data/lib/action_view/template_error.rb +99 -0
  127. data/lib/action_view/template_handler.rb +34 -0
  128. data/lib/action_view/template_handlers/builder.rb +17 -0
  129. data/lib/action_view/template_handlers/erb.rb +22 -0
  130. data/lib/action_view/template_handlers/rjs.rb +13 -0
  131. data/lib/action_view/template_handlers.rb +48 -0
  132. data/lib/action_view/test_case.rb +162 -0
  133. data/lib/action_view.rb +58 -0
  134. data/lib/actionpack.rb +2 -0
  135. data/test/abstract_unit.rb +61 -0
  136. data/test/active_record_unit.rb +104 -0
  137. data/test/activerecord/active_record_store_test.rb +174 -0
  138. data/test/activerecord/render_partial_with_record_identification_test.rb +188 -0
  139. data/test/adv_attr_test.rb +20 -0
  140. data/test/controller/action_pack_assertions_test.rb +543 -0
  141. data/test/controller/addresses_render_test.rb +37 -0
  142. data/test/controller/assert_select_test.rb +734 -0
  143. data/test/controller/base_test.rb +217 -0
  144. data/test/controller/benchmark_test.rb +32 -0
  145. data/test/controller/caching_test.rb +729 -0
  146. data/test/controller/capture_test.rb +66 -0
  147. data/test/controller/content_type_test.rb +168 -0
  148. data/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb +0 -0
  149. data/test/controller/controller_fixtures/app/controllers/user_controller.rb +0 -0
  150. data/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb +0 -0
  151. data/test/controller/cookie_test.rb +134 -0
  152. data/test/controller/deprecation/deprecated_base_methods_test.rb +32 -0
  153. data/test/controller/dispatcher_test.rb +144 -0
  154. data/test/controller/dom_assertions_test.rb +53 -0
  155. data/test/controller/failsafe_test.rb +60 -0
  156. data/test/controller/fake_controllers.rb +33 -0
  157. data/test/controller/fake_models.rb +19 -0
  158. data/test/controller/filter_params_test.rb +52 -0
  159. data/test/controller/filters_test.rb +885 -0
  160. data/test/controller/flash_test.rb +147 -0
  161. data/test/controller/header_test.rb +14 -0
  162. data/test/controller/helper_test.rb +224 -0
  163. data/test/controller/html-scanner/cdata_node_test.rb +15 -0
  164. data/test/controller/html-scanner/document_test.rb +148 -0
  165. data/test/controller/html-scanner/node_test.rb +89 -0
  166. data/test/controller/html-scanner/sanitizer_test.rb +274 -0
  167. data/test/controller/html-scanner/tag_node_test.rb +238 -0
  168. data/test/controller/html-scanner/text_node_test.rb +50 -0
  169. data/test/controller/html-scanner/tokenizer_test.rb +131 -0
  170. data/test/controller/http_basic_authentication_test.rb +113 -0
  171. data/test/controller/http_digest_authentication_test.rb +254 -0
  172. data/test/controller/integration_test.rb +483 -0
  173. data/test/controller/layout_test.rb +215 -0
  174. data/test/controller/logging_test.rb +46 -0
  175. data/test/controller/middleware_stack_test.rb +90 -0
  176. data/test/controller/mime_responds_test.rb +536 -0
  177. data/test/controller/mime_type_test.rb +93 -0
  178. data/test/controller/polymorphic_routes_test.rb +297 -0
  179. data/test/controller/rack_test.rb +311 -0
  180. data/test/controller/record_identifier_test.rb +139 -0
  181. data/test/controller/redirect_test.rb +285 -0
  182. data/test/controller/reloader_test.rb +124 -0
  183. data/test/controller/render_test.rb +1762 -0
  184. data/test/controller/request/json_params_parsing_test.rb +65 -0
  185. data/test/controller/request/multipart_params_parsing_test.rb +162 -0
  186. data/test/controller/request/query_string_parsing_test.rb +120 -0
  187. data/test/controller/request/test_request_test.rb +35 -0
  188. data/test/controller/request/url_encoded_params_parsing_test.rb +146 -0
  189. data/test/controller/request/xml_params_parsing_test.rb +103 -0
  190. data/test/controller/request_forgery_protection_test.rb +265 -0
  191. data/test/controller/request_test.rb +395 -0
  192. data/test/controller/rescue_test.rb +536 -0
  193. data/test/controller/resources_test.rb +1393 -0
  194. data/test/controller/routing_test.rb +2591 -0
  195. data/test/controller/selector_test.rb +628 -0
  196. data/test/controller/send_file_test.rb +171 -0
  197. data/test/controller/session/cookie_store_test.rb +216 -0
  198. data/test/controller/session/mem_cache_store_test.rb +127 -0
  199. data/test/controller/session/test_session_test.rb +58 -0
  200. data/test/controller/test_test.rb +700 -0
  201. data/test/controller/translation_test.rb +26 -0
  202. data/test/controller/url_rewriter_test.rb +385 -0
  203. data/test/controller/verification_test.rb +270 -0
  204. data/test/controller/view_paths_test.rb +141 -0
  205. data/test/controller/webservice_test.rb +273 -0
  206. data/test/fixtures/_top_level_partial.html.erb +1 -0
  207. data/test/fixtures/_top_level_partial_only.erb +1 -0
  208. data/test/fixtures/addresses/list.erb +1 -0
  209. data/test/fixtures/alternate_helpers/foo_helper.rb +3 -0
  210. data/test/fixtures/bad_customers/_bad_customer.html.erb +1 -0
  211. data/test/fixtures/companies.yml +24 -0
  212. data/test/fixtures/company.rb +10 -0
  213. data/test/fixtures/content_type/render_default_content_types_for_respond_to.rhtml +1 -0
  214. data/test/fixtures/content_type/render_default_for_rhtml.rhtml +1 -0
  215. data/test/fixtures/content_type/render_default_for_rjs.rjs +1 -0
  216. data/test/fixtures/content_type/render_default_for_rxml.rxml +1 -0
  217. data/test/fixtures/customers/_customer.html.erb +1 -0
  218. data/test/fixtures/db_definitions/sqlite.sql +49 -0
  219. data/test/fixtures/developer.rb +9 -0
  220. data/test/fixtures/developers/_developer.erb +1 -0
  221. data/test/fixtures/developers.yml +21 -0
  222. data/test/fixtures/developers_projects.yml +13 -0
  223. data/test/fixtures/failsafe/500.html +1 -0
  224. data/test/fixtures/fun/games/_game.erb +1 -0
  225. data/test/fixtures/fun/games/hello_world.erb +1 -0
  226. data/test/fixtures/fun/serious/games/_game.erb +1 -0
  227. data/test/fixtures/functional_caching/_partial.erb +3 -0
  228. data/test/fixtures/functional_caching/formatted_fragment_cached.html.erb +3 -0
  229. data/test/fixtures/functional_caching/formatted_fragment_cached.js.rjs +6 -0
  230. data/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder +5 -0
  231. data/test/fixtures/functional_caching/fragment_cached.html.erb +2 -0
  232. data/test/fixtures/functional_caching/html_fragment_cached_with_partial.html.erb +1 -0
  233. data/test/fixtures/functional_caching/inline_fragment_cached.html.erb +2 -0
  234. data/test/fixtures/functional_caching/js_fragment_cached_with_partial.js.rjs +1 -0
  235. data/test/fixtures/good_customers/_good_customer.html.erb +1 -0
  236. data/test/fixtures/helpers/abc_helper.rb +5 -0
  237. data/test/fixtures/helpers/fun/games_helper.rb +3 -0
  238. data/test/fixtures/helpers/fun/pdf_helper.rb +3 -0
  239. data/test/fixtures/layout_tests/abs_path_layout.rhtml +1 -0
  240. data/test/fixtures/layout_tests/alt/hello.rhtml +1 -0
  241. data/test/fixtures/layout_tests/alt/layouts/alt.rhtml +0 -0
  242. data/test/fixtures/layout_tests/layouts/controller_name_space/nested.rhtml +1 -0
  243. data/test/fixtures/layout_tests/layouts/item.rhtml +1 -0
  244. data/test/fixtures/layout_tests/layouts/layout_test.rhtml +1 -0
  245. data/test/fixtures/layout_tests/layouts/multiple_extensions.html.erb +1 -0
  246. data/test/fixtures/layout_tests/layouts/third_party_template_library.mab +1 -0
  247. data/test/fixtures/layout_tests/views/hello.rhtml +1 -0
  248. data/test/fixtures/layouts/_column.html.erb +2 -0
  249. data/test/fixtures/layouts/block_with_layout.erb +3 -0
  250. data/test/fixtures/layouts/builder.builder +3 -0
  251. data/test/fixtures/layouts/default_html.html.erb +1 -0
  252. data/test/fixtures/layouts/partial_with_layout.erb +3 -0
  253. data/test/fixtures/layouts/standard.erb +1 -0
  254. data/test/fixtures/layouts/talk_from_action.erb +2 -0
  255. data/test/fixtures/layouts/xhr.html.erb +2 -0
  256. data/test/fixtures/layouts/yield.erb +2 -0
  257. data/test/fixtures/mascot.rb +3 -0
  258. data/test/fixtures/mascots/_mascot.html.erb +1 -0
  259. data/test/fixtures/mascots.yml +4 -0
  260. data/test/fixtures/multipart/binary_file +0 -0
  261. data/test/fixtures/multipart/boundary_problem_file +10 -0
  262. data/test/fixtures/multipart/bracketed_param +5 -0
  263. data/test/fixtures/multipart/empty +10 -0
  264. data/test/fixtures/multipart/hello.txt +1 -0
  265. data/test/fixtures/multipart/large_text_file +10 -0
  266. data/test/fixtures/multipart/mixed_files +0 -0
  267. data/test/fixtures/multipart/mona_lisa.jpg +0 -0
  268. data/test/fixtures/multipart/none +9 -0
  269. data/test/fixtures/multipart/single_parameter +5 -0
  270. data/test/fixtures/multipart/text_file +10 -0
  271. data/test/fixtures/override/test/hello_world.erb +1 -0
  272. data/test/fixtures/override2/layouts/test/sub.erb +1 -0
  273. data/test/fixtures/post_test/layouts/post.html.erb +1 -0
  274. data/test/fixtures/post_test/layouts/super_post.iphone.erb +1 -0
  275. data/test/fixtures/post_test/post/index.html.erb +1 -0
  276. data/test/fixtures/post_test/post/index.iphone.erb +1 -0
  277. data/test/fixtures/post_test/super_post/index.html.erb +1 -0
  278. data/test/fixtures/post_test/super_post/index.iphone.erb +1 -0
  279. data/test/fixtures/project.rb +3 -0
  280. data/test/fixtures/projects/_project.erb +1 -0
  281. data/test/fixtures/projects.yml +7 -0
  282. data/test/fixtures/public/404.html +1 -0
  283. data/test/fixtures/public/500.da.html +1 -0
  284. data/test/fixtures/public/500.html +1 -0
  285. data/test/fixtures/public/absolute/test.css +23 -0
  286. data/test/fixtures/public/absolute/test.js +63 -0
  287. data/test/fixtures/public/images/rails.png +0 -0
  288. data/test/fixtures/public/javascripts/application.js +1 -0
  289. data/test/fixtures/public/javascripts/bank.js +1 -0
  290. data/test/fixtures/public/javascripts/controls.js +1 -0
  291. data/test/fixtures/public/javascripts/dragdrop.js +1 -0
  292. data/test/fixtures/public/javascripts/effects.js +1 -0
  293. data/test/fixtures/public/javascripts/prototype.js +1 -0
  294. data/test/fixtures/public/javascripts/robber.js +1 -0
  295. data/test/fixtures/public/javascripts/subdir/subdir.js +1 -0
  296. data/test/fixtures/public/javascripts/version.1.0.js +1 -0
  297. data/test/fixtures/public/stylesheets/bank.css +1 -0
  298. data/test/fixtures/public/stylesheets/robber.css +1 -0
  299. data/test/fixtures/public/stylesheets/subdir/subdir.css +1 -0
  300. data/test/fixtures/public/stylesheets/version.1.0.css +1 -0
  301. data/test/fixtures/quiz/questions/_question.html.erb +1 -0
  302. data/test/fixtures/replies/_reply.erb +1 -0
  303. data/test/fixtures/replies.yml +15 -0
  304. data/test/fixtures/reply.rb +7 -0
  305. data/test/fixtures/respond_to/all_types_with_layout.html.erb +1 -0
  306. data/test/fixtures/respond_to/all_types_with_layout.js.rjs +1 -0
  307. data/test/fixtures/respond_to/custom_constant_handling_without_block.mobile.erb +1 -0
  308. data/test/fixtures/respond_to/iphone_with_html_response_type.html.erb +1 -0
  309. data/test/fixtures/respond_to/iphone_with_html_response_type.iphone.erb +1 -0
  310. data/test/fixtures/respond_to/layouts/missing.html.erb +1 -0
  311. data/test/fixtures/respond_to/layouts/standard.html.erb +1 -0
  312. data/test/fixtures/respond_to/layouts/standard.iphone.erb +1 -0
  313. data/test/fixtures/respond_to/using_defaults.html.erb +1 -0
  314. data/test/fixtures/respond_to/using_defaults.js.rjs +1 -0
  315. data/test/fixtures/respond_to/using_defaults.xml.builder +1 -0
  316. data/test/fixtures/respond_to/using_defaults_with_type_list.html.erb +1 -0
  317. data/test/fixtures/respond_to/using_defaults_with_type_list.js.rjs +1 -0
  318. data/test/fixtures/respond_to/using_defaults_with_type_list.xml.builder +1 -0
  319. data/test/fixtures/scope/test/modgreet.erb +1 -0
  320. data/test/fixtures/shared.html.erb +1 -0
  321. data/test/fixtures/symlink_parent/symlinked_layout.erb +5 -0
  322. data/test/fixtures/test/_counter.html.erb +1 -0
  323. data/test/fixtures/test/_customer.erb +1 -0
  324. data/test/fixtures/test/_customer_counter.erb +1 -0
  325. data/test/fixtures/test/_customer_greeting.erb +1 -0
  326. data/test/fixtures/test/_customer_with_var.erb +1 -0
  327. data/test/fixtures/test/_form.erb +1 -0
  328. data/test/fixtures/test/_from_helper.erb +1 -0
  329. data/test/fixtures/test/_hash_greeting.erb +1 -0
  330. data/test/fixtures/test/_hash_object.erb +2 -0
  331. data/test/fixtures/test/_hello.builder +1 -0
  332. data/test/fixtures/test/_labelling_form.erb +1 -0
  333. data/test/fixtures/test/_layout_for_block_with_args.html.erb +3 -0
  334. data/test/fixtures/test/_layout_for_partial.html.erb +3 -0
  335. data/test/fixtures/test/_local_inspector.html.erb +1 -0
  336. data/test/fixtures/test/_one.html.erb +1 -0
  337. data/test/fixtures/test/_partial.erb +1 -0
  338. data/test/fixtures/test/_partial.html.erb +1 -0
  339. data/test/fixtures/test/_partial.js.erb +1 -0
  340. data/test/fixtures/test/_partial_for_use_in_layout.html.erb +1 -0
  341. data/test/fixtures/test/_partial_only.erb +1 -0
  342. data/test/fixtures/test/_partial_with_only_html_version.html.erb +1 -0
  343. data/test/fixtures/test/_person.erb +2 -0
  344. data/test/fixtures/test/_raise.html.erb +1 -0
  345. data/test/fixtures/test/_two.html.erb +1 -0
  346. data/test/fixtures/test/action_talk_to_layout.erb +2 -0
  347. data/test/fixtures/test/calling_partial_with_layout.html.erb +1 -0
  348. data/test/fixtures/test/capturing.erb +4 -0
  349. data/test/fixtures/test/content_for.erb +2 -0
  350. data/test/fixtures/test/content_for_concatenated.erb +3 -0
  351. data/test/fixtures/test/content_for_with_parameter.erb +2 -0
  352. data/test/fixtures/test/delete_with_js.rjs +2 -0
  353. data/test/fixtures/test/dont_pick_me +1 -0
  354. data/test/fixtures/test/dot.directory/render_file_with_ivar.erb +1 -0
  355. data/test/fixtures/test/enum_rjs_test.rjs +6 -0
  356. data/test/fixtures/test/formatted_html_erb.html.erb +1 -0
  357. data/test/fixtures/test/formatted_xml_erb.builder +1 -0
  358. data/test/fixtures/test/formatted_xml_erb.html.erb +1 -0
  359. data/test/fixtures/test/formatted_xml_erb.xml.erb +1 -0
  360. data/test/fixtures/test/greeting.erb +1 -0
  361. data/test/fixtures/test/greeting.js.rjs +1 -0
  362. data/test/fixtures/test/hello.builder +4 -0
  363. data/test/fixtures/test/hello_world.da.html.erb +1 -0
  364. data/test/fixtures/test/hello_world.erb +1 -0
  365. data/test/fixtures/test/hello_world.erb~ +1 -0
  366. data/test/fixtures/test/hello_world.pt-BR.html.erb +1 -0
  367. data/test/fixtures/test/hello_world_container.builder +3 -0
  368. data/test/fixtures/test/hello_world_from_rxml.builder +4 -0
  369. data/test/fixtures/test/hello_world_with_layout_false.erb +1 -0
  370. data/test/fixtures/test/hello_xml_world.builder +11 -0
  371. data/test/fixtures/test/hyphen-ated.erb +1 -0
  372. data/test/fixtures/test/implicit_content_type.atom.builder +2 -0
  373. data/test/fixtures/test/list.erb +1 -0
  374. data/test/fixtures/test/malformed/malformed.en.html.erb~ +1 -0
  375. data/test/fixtures/test/malformed/malformed.erb~ +1 -0
  376. data/test/fixtures/test/malformed/malformed.html.erb~ +1 -0
  377. data/test/fixtures/test/nested_layout.erb +3 -0
  378. data/test/fixtures/test/non_erb_block_content_for.builder +4 -0
  379. data/test/fixtures/test/potential_conflicts.erb +4 -0
  380. data/test/fixtures/test/render_explicit_html_template.js.rjs +1 -0
  381. data/test/fixtures/test/render_file_from_template.html.erb +1 -0
  382. data/test/fixtures/test/render_file_with_ivar.erb +1 -0
  383. data/test/fixtures/test/render_file_with_locals.erb +1 -0
  384. data/test/fixtures/test/render_implicit_html_template.js.rjs +1 -0
  385. data/test/fixtures/test/render_implicit_html_template_from_xhr_request.da.html.erb +1 -0
  386. data/test/fixtures/test/render_implicit_html_template_from_xhr_request.html.erb +1 -0
  387. data/test/fixtures/test/render_implicit_js_template_without_layout.js.erb +1 -0
  388. data/test/fixtures/test/render_to_string_test.erb +1 -0
  389. data/test/fixtures/test/sub_template_raise.html.erb +1 -0
  390. data/test/fixtures/test/template.erb +1 -0
  391. data/test/fixtures/test/update_element_with_capture.erb +9 -0
  392. data/test/fixtures/test/using_layout_around_block.html.erb +1 -0
  393. data/test/fixtures/test/using_layout_around_block_with_args.html.erb +1 -0
  394. data/test/fixtures/test/utf8.html.erb +2 -0
  395. data/test/fixtures/topic.rb +3 -0
  396. data/test/fixtures/topics/_topic.html.erb +1 -0
  397. data/test/fixtures/topics.yml +22 -0
  398. data/test/template/active_record_helper_i18n_test.rb +44 -0
  399. data/test/template/active_record_helper_test.rb +302 -0
  400. data/test/template/asset_tag_helper_test.rb +771 -0
  401. data/test/template/atom_feed_helper_test.rb +315 -0
  402. data/test/template/benchmark_helper_test.rb +86 -0
  403. data/test/template/compiled_templates_test.rb +204 -0
  404. data/test/template/date_helper_i18n_test.rb +121 -0
  405. data/test/template/date_helper_test.rb +2485 -0
  406. data/test/template/erb_util_test.rb +24 -0
  407. data/test/template/form_helper_test.rb +1393 -0
  408. data/test/template/form_options_helper_i18n_test.rb +27 -0
  409. data/test/template/form_options_helper_test.rb +807 -0
  410. data/test/template/form_tag_helper_test.rb +344 -0
  411. data/test/template/javascript_helper_test.rb +106 -0
  412. data/test/template/number_helper_i18n_test.rb +69 -0
  413. data/test/template/number_helper_test.rb +132 -0
  414. data/test/template/prototype_helper_test.rb +639 -0
  415. data/test/template/raw_output_helper_test.rb +21 -0
  416. data/test/template/record_tag_helper_test.rb +58 -0
  417. data/test/template/render_test.rb +290 -0
  418. data/test/template/sanitize_helper_test.rb +57 -0
  419. data/test/template/scriptaculous_helper_test.rb +90 -0
  420. data/test/template/tag_helper_test.rb +98 -0
  421. data/test/template/template_test.rb +32 -0
  422. data/test/template/test_test.rb +54 -0
  423. data/test/template/text_helper_test.rb +543 -0
  424. data/test/template/translation_helper_test.rb +32 -0
  425. data/test/template/url_helper_test.rb +622 -0
  426. data/test/testing_sandbox.rb +15 -0
  427. data/test/view/safe_buffer_test.rb +36 -0
  428. data/test/view/test_case_test.rb +176 -0
  429. metadata +531 -0
@@ -0,0 +1,1074 @@
1
+ require 'cgi'
2
+ require 'action_view/helpers/date_helper'
3
+ require 'action_view/helpers/tag_helper'
4
+ require 'action_view/helpers/form_tag_helper'
5
+
6
+ module ActionView
7
+ module Helpers
8
+ # Form helpers are designed to make working with models much easier
9
+ # compared to using just standard HTML elements by providing a set of
10
+ # methods for creating forms based on your models. This helper generates
11
+ # the HTML for forms, providing a method for each sort of input
12
+ # (e.g., text, password, select, and so on). When the form is submitted
13
+ # (i.e., when the user hits the submit button or <tt>form.submit</tt> is
14
+ # called via JavaScript), the form inputs will be bundled into the
15
+ # <tt>params</tt> object and passed back to the controller.
16
+ #
17
+ # There are two types of form helpers: those that specifically work with
18
+ # model attributes and those that don't. This helper deals with those that
19
+ # work with model attributes; to see an example of form helpers that don't
20
+ # work with model attributes, check the ActionView::Helpers::FormTagHelper
21
+ # documentation.
22
+ #
23
+ # The core method of this helper, form_for, gives you the ability to create
24
+ # a form for a model instance; for example, let's say that you have a model
25
+ # <tt>Person</tt> and want to create a new instance of it:
26
+ #
27
+ # # Note: a @person variable will have been created in the controller.
28
+ # # For example: @person = Person.new
29
+ # <% form_for :person, @person, :url => { :action => "create" } do |f| %>
30
+ # <%= f.text_field :first_name %>
31
+ # <%= f.text_field :last_name %>
32
+ # <%= submit_tag 'Create' %>
33
+ # <% end %>
34
+ #
35
+ # The HTML generated for this would be:
36
+ #
37
+ # <form action="/persons/create" method="post">
38
+ # <input id="person_first_name" name="person[first_name]" size="30" type="text" />
39
+ # <input id="person_last_name" name="person[last_name]" size="30" type="text" />
40
+ # <input name="commit" type="submit" value="Create" />
41
+ # </form>
42
+ #
43
+ # If you are using a partial for your form fields, you can use this shortcut:
44
+ #
45
+ # <% form_for :person, @person, :url => { :action => "create" } do |f| %>
46
+ # <%= render :partial => f %>
47
+ # <%= submit_tag 'Create' %>
48
+ # <% end %>
49
+ #
50
+ # This example will render the <tt>people/_form</tt> partial, setting a
51
+ # local variable called <tt>form</tt> which references the yielded
52
+ # FormBuilder. The <tt>params</tt> object created when this form is
53
+ # submitted would look like:
54
+ #
55
+ # {"action"=>"create", "controller"=>"persons", "person"=>{"first_name"=>"William", "last_name"=>"Smith"}}
56
+ #
57
+ # The params hash has a nested <tt>person</tt> value, which can therefore
58
+ # be accessed with <tt>params[:person]</tt> in the controller. If were
59
+ # editing/updating an instance (e.g., <tt>Person.find(1)</tt> rather than
60
+ # <tt>Person.new</tt> in the controller), the objects attribute values are
61
+ # filled into the form (e.g., the <tt>person_first_name</tt> field would
62
+ # have that person's first name in it).
63
+ #
64
+ # If the object name contains square brackets the id for the object will be
65
+ # inserted. For example:
66
+ #
67
+ # <%= text_field "person[]", "name" %>
68
+ #
69
+ # ...will generate the following ERb.
70
+ #
71
+ # <input type="text" id="person_<%= @person.id %>_name" name="person[<%= @person.id %>][name]" value="<%= @person.name %>" />
72
+ #
73
+ # If the helper is being used to generate a repetitive sequence of similar
74
+ # form elements, for example in a partial used by
75
+ # <tt>render_collection_of_partials</tt>, the <tt>index</tt> option may
76
+ # come in handy. Example:
77
+ #
78
+ # <%= text_field "person", "name", "index" => 1 %>
79
+ #
80
+ # ...becomes...
81
+ #
82
+ # <input type="text" id="person_1_name" name="person[1][name]" value="<%= @person.name %>" />
83
+ #
84
+ # An <tt>index</tt> option may also be passed to <tt>form_for</tt> and
85
+ # <tt>fields_for</tt>. This automatically applies the <tt>index</tt> to
86
+ # all the nested fields.
87
+ #
88
+ # There are also methods for helping to build form tags in
89
+ # link:classes/ActionView/Helpers/FormOptionsHelper.html,
90
+ # link:classes/ActionView/Helpers/DateHelper.html, and
91
+ # link:classes/ActionView/Helpers/ActiveRecordHelper.html
92
+ module FormHelper
93
+ # Creates a form and a scope around a specific model object that is used
94
+ # as a base for questioning about values for the fields.
95
+ #
96
+ # Rails provides succinct resource-oriented form generation with +form_for+
97
+ # like this:
98
+ #
99
+ # <% form_for @offer do |f| %>
100
+ # <%= f.label :version, 'Version' %>:
101
+ # <%= f.text_field :version %><br />
102
+ # <%= f.label :author, 'Author' %>:
103
+ # <%= f.text_field :author %><br />
104
+ # <% end %>
105
+ #
106
+ # There, +form_for+ is able to generate the rest of RESTful form
107
+ # parameters based on introspection on the record, but to understand what
108
+ # it does we need to dig first into the alternative generic usage it is
109
+ # based upon.
110
+ #
111
+ # === Generic form_for
112
+ #
113
+ # The generic way to call +form_for+ yields a form builder around a
114
+ # model:
115
+ #
116
+ # <% form_for :person, :url => { :action => "update" } do |f| %>
117
+ # <%= f.error_messages %>
118
+ # First name: <%= f.text_field :first_name %><br />
119
+ # Last name : <%= f.text_field :last_name %><br />
120
+ # Biography : <%= f.text_area :biography %><br />
121
+ # Admin? : <%= f.check_box :admin %><br />
122
+ # <% end %>
123
+ #
124
+ # There, the first argument is a symbol or string with the name of the
125
+ # object the form is about, and also the name of the instance variable
126
+ # the object is stored in.
127
+ #
128
+ # The form builder acts as a regular form helper that somehow carries the
129
+ # model. Thus, the idea is that
130
+ #
131
+ # <%= f.text_field :first_name %>
132
+ #
133
+ # gets expanded to
134
+ #
135
+ # <%= text_field :person, :first_name %>
136
+ #
137
+ # If the instance variable is not <tt>@person</tt> you can pass the actual
138
+ # record as the second argument:
139
+ #
140
+ # <% form_for :person, person, :url => { :action => "update" } do |f| %>
141
+ # ...
142
+ # <% end %>
143
+ #
144
+ # In that case you can think
145
+ #
146
+ # <%= f.text_field :first_name %>
147
+ #
148
+ # gets expanded to
149
+ #
150
+ # <%= text_field :person, :first_name, :object => person %>
151
+ #
152
+ # You can even display error messages of the wrapped model this way:
153
+ #
154
+ # <%= f.error_messages %>
155
+ #
156
+ # In any of its variants, the rightmost argument to +form_for+ is an
157
+ # optional hash of options:
158
+ #
159
+ # * <tt>:url</tt> - The URL the form is submitted to. It takes the same
160
+ # fields you pass to +url_for+ or +link_to+. In particular you may pass
161
+ # here a named route directly as well. Defaults to the current action.
162
+ # * <tt>:html</tt> - Optional HTML attributes for the form tag.
163
+ #
164
+ # Worth noting is that the +form_for+ tag is called in a ERb evaluation
165
+ # block, not an ERb output block. So that's <tt><% %></tt>, not
166
+ # <tt><%= %></tt>.
167
+ #
168
+ # Also note that +form_for+ doesn't create an exclusive scope. It's still
169
+ # possible to use both the stand-alone FormHelper methods and methods
170
+ # from FormTagHelper. For example:
171
+ #
172
+ # <% form_for :person, @person, :url => { :action => "update" } do |f| %>
173
+ # First name: <%= f.text_field :first_name %>
174
+ # Last name : <%= f.text_field :last_name %>
175
+ # Biography : <%= text_area :person, :biography %>
176
+ # Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %>
177
+ # <% end %>
178
+ #
179
+ # This also works for the methods in FormOptionHelper and DateHelper that
180
+ # are designed to work with an object as base, like
181
+ # FormOptionHelper#collection_select and DateHelper#datetime_select.
182
+ #
183
+ # === Resource-oriented style
184
+ #
185
+ # As we said above, in addition to manually configuring the +form_for+
186
+ # call, you can rely on automated resource identification, which will use
187
+ # the conventions and named routes of that approach. This is the
188
+ # preferred way to use +form_for+ nowadays.
189
+ #
190
+ # For example, if <tt>@post</tt> is an existing record you want to edit
191
+ #
192
+ # <% form_for @post do |f| %>
193
+ # ...
194
+ # <% end %>
195
+ #
196
+ # is equivalent to something like:
197
+ #
198
+ # <% form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
199
+ # ...
200
+ # <% end %>
201
+ #
202
+ # And for new records
203
+ #
204
+ # <% form_for(Post.new) do |f| %>
205
+ # ...
206
+ # <% end %>
207
+ #
208
+ # expands to
209
+ #
210
+ # <% form_for :post, Post.new, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %>
211
+ # ...
212
+ # <% end %>
213
+ #
214
+ # You can also overwrite the individual conventions, like this:
215
+ #
216
+ # <% form_for(@post, :url => super_post_path(@post)) do |f| %>
217
+ # ...
218
+ # <% end %>
219
+ #
220
+ # And for namespaced routes, like +admin_post_url+:
221
+ #
222
+ # <% form_for([:admin, @post]) do |f| %>
223
+ # ...
224
+ # <% end %>
225
+ #
226
+ # === Customized form builders
227
+ #
228
+ # You can also build forms using a customized FormBuilder class. Subclass
229
+ # FormBuilder and override or define some more helpers, then use your
230
+ # custom builder. For example, let's say you made a helper to
231
+ # automatically add labels to form inputs.
232
+ #
233
+ # <% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %>
234
+ # <%= f.text_field :first_name %>
235
+ # <%= f.text_field :last_name %>
236
+ # <%= text_area :person, :biography %>
237
+ # <%= check_box_tag "person[admin]", @person.company.admin? %>
238
+ # <% end %>
239
+ #
240
+ # In this case, if you use this:
241
+ #
242
+ # <%= render :partial => f %>
243
+ #
244
+ # The rendered template is <tt>people/_labelling_form</tt> and the local
245
+ # variable referencing the form builder is called
246
+ # <tt>labelling_form</tt>.
247
+ #
248
+ # The custom FormBuilder class is automatically merged with the options
249
+ # of a nested fields_for call, unless it's explicitely set.
250
+ #
251
+ # In many cases you will want to wrap the above in another helper, so you
252
+ # could do something like the following:
253
+ #
254
+ # def labelled_form_for(record_or_name_or_array, *args, &proc)
255
+ # options = args.extract_options!
256
+ # form_for(record_or_name_or_array, *(args << options.merge(:builder => LabellingFormBuilder)), &proc)
257
+ # end
258
+ #
259
+ # If you don't need to attach a form to a model instance, then check out
260
+ # FormTagHelper#form_tag.
261
+ def form_for(record_or_name_or_array, *args, &proc)
262
+ raise ArgumentError, "Missing block" unless block_given?
263
+
264
+ options = args.extract_options!
265
+
266
+ case record_or_name_or_array
267
+ when String, Symbol
268
+ object_name = record_or_name_or_array
269
+ when Array
270
+ object = record_or_name_or_array.last
271
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
272
+ apply_form_for_options!(record_or_name_or_array, options)
273
+ args.unshift object
274
+ else
275
+ object = record_or_name_or_array
276
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
277
+ apply_form_for_options!([object], options)
278
+ args.unshift object
279
+ end
280
+
281
+ concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}))
282
+ fields_for(object_name, *(args << options), &proc)
283
+ concat('</form>'.html_safe!)
284
+ end
285
+
286
+ def apply_form_for_options!(object_or_array, options) #:nodoc:
287
+ object = object_or_array.is_a?(Array) ? object_or_array.last : object_or_array
288
+
289
+ html_options =
290
+ if object.respond_to?(:new_record?) && object.new_record?
291
+ { :class => dom_class(object, :new), :id => dom_id(object), :method => :post }
292
+ else
293
+ { :class => dom_class(object, :edit), :id => dom_id(object, :edit), :method => :put }
294
+ end
295
+
296
+ options[:html] ||= {}
297
+ options[:html].reverse_merge!(html_options)
298
+ options[:url] ||= polymorphic_path(object_or_array)
299
+ end
300
+
301
+ # Creates a scope around a specific model object like form_for, but
302
+ # doesn't create the form tags themselves. This makes fields_for suitable
303
+ # for specifying additional model objects in the same form.
304
+ #
305
+ # === Generic Examples
306
+ #
307
+ # <% form_for @person, :url => { :action => "update" } do |person_form| %>
308
+ # First name: <%= person_form.text_field :first_name %>
309
+ # Last name : <%= person_form.text_field :last_name %>
310
+ #
311
+ # <% fields_for @person.permission do |permission_fields| %>
312
+ # Admin? : <%= permission_fields.check_box :admin %>
313
+ # <% end %>
314
+ # <% end %>
315
+ #
316
+ # ...or if you have an object that needs to be represented as a different
317
+ # parameter, like a Client that acts as a Person:
318
+ #
319
+ # <% fields_for :person, @client do |permission_fields| %>
320
+ # Admin?: <%= permission_fields.check_box :admin %>
321
+ # <% end %>
322
+ #
323
+ # ...or if you don't have an object, just a name of the parameter:
324
+ #
325
+ # <% fields_for :person do |permission_fields| %>
326
+ # Admin?: <%= permission_fields.check_box :admin %>
327
+ # <% end %>
328
+ #
329
+ # Note: This also works for the methods in FormOptionHelper and
330
+ # DateHelper that are designed to work with an object as base, like
331
+ # FormOptionHelper#collection_select and DateHelper#datetime_select.
332
+ #
333
+ # === Nested Attributes Examples
334
+ #
335
+ # When the object belonging to the current scope has a nested attribute
336
+ # writer for a certain attribute, fields_for will yield a new scope
337
+ # for that attribute. This allows you to create forms that set or change
338
+ # the attributes of a parent object and its associations in one go.
339
+ #
340
+ # Nested attribute writers are normal setter methods named after an
341
+ # association. The most common way of defining these writers is either
342
+ # with +accepts_nested_attributes_for+ in a model definition or by
343
+ # defining a method with the proper name. For example: the attribute
344
+ # writer for the association <tt>:address</tt> is called
345
+ # <tt>address_attributes=</tt>.
346
+ #
347
+ # Whether a one-to-one or one-to-many style form builder will be yielded
348
+ # depends on whether the normal reader method returns a _single_ object
349
+ # or an _array_ of objects.
350
+ #
351
+ # ==== One-to-one
352
+ #
353
+ # Consider a Person class which returns a _single_ Address from the
354
+ # <tt>address</tt> reader method and responds to the
355
+ # <tt>address_attributes=</tt> writer method:
356
+ #
357
+ # class Person
358
+ # def address
359
+ # @address
360
+ # end
361
+ #
362
+ # def address_attributes=(attributes)
363
+ # # Process the attributes hash
364
+ # end
365
+ # end
366
+ #
367
+ # This model can now be used with a nested fields_for, like so:
368
+ #
369
+ # <% form_for @person, :url => { :action => "update" } do |person_form| %>
370
+ # ...
371
+ # <% person_form.fields_for :address do |address_fields| %>
372
+ # Street : <%= address_fields.text_field :street %>
373
+ # Zip code: <%= address_fields.text_field :zip_code %>
374
+ # <% end %>
375
+ # <% end %>
376
+ #
377
+ # When address is already an association on a Person you can use
378
+ # +accepts_nested_attributes_for+ to define the writer method for you:
379
+ #
380
+ # class Person < ActiveRecord::Base
381
+ # has_one :address
382
+ # accepts_nested_attributes_for :address
383
+ # end
384
+ #
385
+ # If you want to destroy the associated model through the form, you have
386
+ # to enable it first using the <tt>:allow_destroy</tt> option for
387
+ # +accepts_nested_attributes_for+:
388
+ #
389
+ # class Person < ActiveRecord::Base
390
+ # has_one :address
391
+ # accepts_nested_attributes_for :address, :allow_destroy => true
392
+ # end
393
+ #
394
+ # Now, when you use a form element with the <tt>_delete</tt> parameter,
395
+ # with a value that evaluates to +true+, you will destroy the associated
396
+ # model (eg. 1, '1', true, or 'true'):
397
+ #
398
+ # <% form_for @person, :url => { :action => "update" } do |person_form| %>
399
+ # ...
400
+ # <% person_form.fields_for :address do |address_fields| %>
401
+ # ...
402
+ # Delete: <%= address_fields.check_box :_delete %>
403
+ # <% end %>
404
+ # <% end %>
405
+ #
406
+ # ==== One-to-many
407
+ #
408
+ # Consider a Person class which returns an _array_ of Project instances
409
+ # from the <tt>projects</tt> reader method and responds to the
410
+ # <tt>projects_attributes=</tt> writer method:
411
+ #
412
+ # class Person
413
+ # def projects
414
+ # [@project1, @project2]
415
+ # end
416
+ #
417
+ # def projects_attributes=(attributes)
418
+ # # Process the attributes hash
419
+ # end
420
+ # end
421
+ #
422
+ # This model can now be used with a nested fields_for. The block given to
423
+ # the nested fields_for call will be repeated for each instance in the
424
+ # collection:
425
+ #
426
+ # <% form_for @person, :url => { :action => "update" } do |person_form| %>
427
+ # ...
428
+ # <% person_form.fields_for :projects do |project_fields| %>
429
+ # <% if project_fields.object.active? %>
430
+ # Name: <%= project_fields.text_field :name %>
431
+ # <% end %>
432
+ # <% end %>
433
+ # <% end %>
434
+ #
435
+ # It's also possible to specify the instance to be used:
436
+ #
437
+ # <% form_for @person, :url => { :action => "update" } do |person_form| %>
438
+ # ...
439
+ # <% @person.projects.each do |project| %>
440
+ # <% if project.active? %>
441
+ # <% person_form.fields_for :projects, project do |project_fields| %>
442
+ # Name: <%= project_fields.text_field :name %>
443
+ # <% end %>
444
+ # <% end %>
445
+ # <% end %>
446
+ # <% end %>
447
+ #
448
+ # Or a collection to be used:
449
+ #
450
+ # <% form_for @person, :url => { :action => "update" } do |person_form| %>
451
+ # ...
452
+ # <% person_form.fields_for :projects, @active_projects do |project_fields| %>
453
+ # Name: <%= project_fields.text_field :name %>
454
+ # <% end %>
455
+ # <% end %>
456
+ #
457
+ # When projects is already an association on Person you can use
458
+ # +accepts_nested_attributes_for+ to define the writer method for you:
459
+ #
460
+ # class Person < ActiveRecord::Base
461
+ # has_many :projects
462
+ # accepts_nested_attributes_for :projects
463
+ # end
464
+ #
465
+ # If you want to destroy any of the associated models through the
466
+ # form, you have to enable it first using the <tt>:allow_destroy</tt>
467
+ # option for +accepts_nested_attributes_for+:
468
+ #
469
+ # class Person < ActiveRecord::Base
470
+ # has_many :projects
471
+ # accepts_nested_attributes_for :projects, :allow_destroy => true
472
+ # end
473
+ #
474
+ # This will allow you to specify which models to destroy in the
475
+ # attributes hash by adding a form element for the <tt>_delete</tt>
476
+ # parameter with a value that evaluates to +true+
477
+ # (eg. 1, '1', true, or 'true'):
478
+ #
479
+ # <% form_for @person, :url => { :action => "update" } do |person_form| %>
480
+ # ...
481
+ # <% person_form.fields_for :projects do |project_fields| %>
482
+ # Delete: <%= project_fields.check_box :_delete %>
483
+ # <% end %>
484
+ # <% end %>
485
+ def fields_for(record_or_name_or_array, *args, &block)
486
+ raise ArgumentError, "Missing block" unless block_given?
487
+ options = args.extract_options!
488
+
489
+ case record_or_name_or_array
490
+ when String, Symbol
491
+ object_name = record_or_name_or_array
492
+ object = args.first
493
+ else
494
+ object = record_or_name_or_array
495
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
496
+ end
497
+
498
+ builder = options[:builder] || ActionView::Base.default_form_builder
499
+ yield builder.new(object_name, object, self, options, block)
500
+ end
501
+
502
+ # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
503
+ # assigned to the template (identified by +object+). The text of label will default to the attribute name unless you specify
504
+ # it explicitly. Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
505
+ # onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to
506
+ # target labels for radio_button tags (where the value is used in the ID of the input tag).
507
+ #
508
+ # ==== Examples
509
+ # label(:post, :title)
510
+ # # => <label for="post_title">Title</label>
511
+ #
512
+ # label(:post, :title, "A short title")
513
+ # # => <label for="post_title">A short title</label>
514
+ #
515
+ # label(:post, :title, "A short title", :class => "title_label")
516
+ # # => <label for="post_title" class="title_label">A short title</label>
517
+ #
518
+ # label(:post, :privacy, "Public Post", :value => "public")
519
+ # # => <label for="post_privacy_public">Public Post</label>
520
+ #
521
+ def label(object_name, method, text = nil, options = {})
522
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options)
523
+ end
524
+
525
+ # Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
526
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
527
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
528
+ # shown.
529
+ #
530
+ # ==== Examples
531
+ # text_field(:post, :title, :size => 20)
532
+ # # => <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" />
533
+ #
534
+ # text_field(:post, :title, :class => "create_input")
535
+ # # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
536
+ #
537
+ # text_field(:session, :user, :onchange => "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }")
538
+ # # => <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!'); }"/>
539
+ #
540
+ # text_field(:snippet, :code, :size => 20, :class => 'code_input')
541
+ # # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
542
+ #
543
+ def text_field(object_name, method, options = {})
544
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("text", options)
545
+ end
546
+
547
+ # Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
548
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
549
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
550
+ # shown.
551
+ #
552
+ # ==== Examples
553
+ # password_field(:login, :pass, :size => 20)
554
+ # # => <input type="text" id="login_pass" name="login[pass]" size="20" value="#{@login.pass}" />
555
+ #
556
+ # password_field(:account, :secret, :class => "form_input")
557
+ # # => <input type="text" id="account_secret" name="account[secret]" value="#{@account.secret}" class="form_input" />
558
+ #
559
+ # password_field(:user, :password, :onchange => "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }")
560
+ # # => <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!'); }"/>
561
+ #
562
+ # password_field(:account, :pin, :size => 20, :class => 'form_input')
563
+ # # => <input type="text" id="account_pin" name="account[pin]" size="20" value="#{@account.pin}" class="form_input" />
564
+ #
565
+ def password_field(object_name, method, options = {})
566
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("password", options)
567
+ end
568
+
569
+ # Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
570
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
571
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
572
+ # shown.
573
+ #
574
+ # ==== Examples
575
+ # hidden_field(:signup, :pass_confirm)
576
+ # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
577
+ #
578
+ # hidden_field(:post, :tag_list)
579
+ # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
580
+ #
581
+ # hidden_field(:user, :token)
582
+ # # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
583
+ def hidden_field(object_name, method, options = {})
584
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("hidden", options)
585
+ end
586
+
587
+ # Returns an file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
588
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
589
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
590
+ # shown.
591
+ #
592
+ # ==== Examples
593
+ # file_field(:user, :avatar)
594
+ # # => <input type="file" id="user_avatar" name="user[avatar]" />
595
+ #
596
+ # file_field(:post, :attached, :accept => 'text/html')
597
+ # # => <input type="file" id="post_attached" name="post[attached]" />
598
+ #
599
+ # file_field(:attachment, :file, :class => 'file_input')
600
+ # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
601
+ #
602
+ def file_field(object_name, method, options = {})
603
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("file", options)
604
+ end
605
+
606
+ # Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
607
+ # on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
608
+ # hash with +options+.
609
+ #
610
+ # ==== Examples
611
+ # text_area(:post, :body, :cols => 20, :rows => 40)
612
+ # # => <textarea cols="20" rows="40" id="post_body" name="post[body]">
613
+ # # #{@post.body}
614
+ # # </textarea>
615
+ #
616
+ # text_area(:comment, :text, :size => "20x30")
617
+ # # => <textarea cols="20" rows="30" id="comment_text" name="comment[text]">
618
+ # # #{@comment.text}
619
+ # # </textarea>
620
+ #
621
+ # text_area(:application, :notes, :cols => 40, :rows => 15, :class => 'app_input')
622
+ # # => <textarea cols="40" rows="15" id="application_notes" name="application[notes]" class="app_input">
623
+ # # #{@application.notes}
624
+ # # </textarea>
625
+ #
626
+ # text_area(:entry, :body, :size => "20x20", :disabled => 'disabled')
627
+ # # => <textarea cols="20" rows="20" id="entry_body" name="entry[body]" disabled="disabled">
628
+ # # #{@entry.body}
629
+ # # </textarea>
630
+ def text_area(object_name, method, options = {})
631
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_text_area_tag(options)
632
+ end
633
+
634
+ # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
635
+ # assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object.
636
+ # It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked.
637
+ # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
638
+ # while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
639
+ #
640
+ # ==== Gotcha
641
+ #
642
+ # The HTML specification says unchecked check boxes are not successful, and
643
+ # thus web browsers do not send them. Unfortunately this introduces a gotcha:
644
+ # if an Invoice model has a +paid+ flag, and in the form that edits a paid
645
+ # invoice the user unchecks its check box, no +paid+ parameter is sent. So,
646
+ # any mass-assignment idiom like
647
+ #
648
+ # @invoice.update_attributes(params[:invoice])
649
+ #
650
+ # wouldn't update the flag.
651
+ #
652
+ # To prevent this the helper generates a hidden field with the same name as
653
+ # the checkbox after the very check box. So, the client either sends only the
654
+ # hidden field (representing the check box is unchecked), or both fields.
655
+ # Since the HTML specification says key/value pairs have to be sent in the
656
+ # same order they appear in the form and Rails parameters extraction always
657
+ # gets the first occurrence of any given key, that works in ordinary forms.
658
+ #
659
+ # Unfortunately that workaround does not work when the check box goes
660
+ # within an array-like parameter, as in
661
+ #
662
+ # <% fields_for "project[invoice_attributes][]", invoice, :index => nil do |form| %>
663
+ # <%= form.check_box :paid %>
664
+ # ...
665
+ # <% end %>
666
+ #
667
+ # because parameter name repetition is precisely what Rails seeks to distinguish
668
+ # the elements of the array.
669
+ #
670
+ # ==== Examples
671
+ # # Let's say that @post.validated? is 1:
672
+ # check_box("post", "validated")
673
+ # # => <input type="checkbox" id="post_validated" name="post[validated]" value="1" />
674
+ # # <input name="post[validated]" type="hidden" value="0" />
675
+ #
676
+ # # Let's say that @puppy.gooddog is "no":
677
+ # check_box("puppy", "gooddog", {}, "yes", "no")
678
+ # # => <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
679
+ # # <input name="puppy[gooddog]" type="hidden" value="no" />
680
+ #
681
+ # check_box("eula", "accepted", { :class => 'eula_check' }, "yes", "no")
682
+ # # => <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
683
+ # # <input name="eula[accepted]" type="hidden" value="no" />
684
+ #
685
+ def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
686
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
687
+ end
688
+
689
+ # Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
690
+ # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the
691
+ # radio button will be checked.
692
+ #
693
+ # To force the radio button to be checked pass <tt>:checked => true</tt> in the
694
+ # +options+ hash. You may pass HTML options there as well.
695
+ #
696
+ # ==== Examples
697
+ # # Let's say that @post.category returns "rails":
698
+ # radio_button("post", "category", "rails")
699
+ # radio_button("post", "category", "java")
700
+ # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
701
+ # # <input type="radio" id="post_category_java" name="post[category]" value="java" />
702
+ #
703
+ # radio_button("user", "receive_newsletter", "yes")
704
+ # radio_button("user", "receive_newsletter", "no")
705
+ # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
706
+ # # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
707
+ def radio_button(object_name, method, tag_value, options = {})
708
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_radio_button_tag(tag_value, options)
709
+ end
710
+ end
711
+
712
+ class InstanceTag #:nodoc:
713
+ include Helpers::TagHelper, Helpers::FormTagHelper
714
+
715
+ attr_reader :method_name, :object_name
716
+
717
+ DEFAULT_FIELD_OPTIONS = { "size" => 30 }.freeze unless const_defined?(:DEFAULT_FIELD_OPTIONS)
718
+ DEFAULT_RADIO_OPTIONS = { }.freeze unless const_defined?(:DEFAULT_RADIO_OPTIONS)
719
+ DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }.freeze unless const_defined?(:DEFAULT_TEXT_AREA_OPTIONS)
720
+
721
+ def initialize(object_name, method_name, template_object, object = nil)
722
+ @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
723
+ @template_object = template_object
724
+ @object = object
725
+ if @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
726
+ if (object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}")) && object.respond_to?(:to_param)
727
+ @auto_index = object.to_param
728
+ else
729
+ raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
730
+ end
731
+ end
732
+ end
733
+
734
+ def to_label_tag(text = nil, options = {})
735
+ options = options.stringify_keys
736
+ tag_value = options.delete("value")
737
+ name_and_id = options.dup
738
+ name_and_id["id"] = name_and_id["for"]
739
+ add_default_name_and_id_for_value(tag_value, name_and_id)
740
+ options.delete("index")
741
+ options["for"] ||= name_and_id["id"]
742
+ content = (text.blank? ? nil : text.to_s) || method_name.humanize
743
+ label_tag(name_and_id["id"], content, options)
744
+ end
745
+
746
+ def to_input_field_tag(field_type, options = {})
747
+ options = options.stringify_keys
748
+ options["size"] = options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] unless options.key?("size")
749
+ options = DEFAULT_FIELD_OPTIONS.merge(options)
750
+ if field_type == "hidden"
751
+ options.delete("size")
752
+ end
753
+ options["type"] = field_type
754
+ options["value"] ||= value_before_type_cast(object) unless field_type == "file"
755
+ options["value"] &&= html_escape(options["value"])
756
+ add_default_name_and_id(options)
757
+ tag("input", options)
758
+ end
759
+
760
+ def to_radio_button_tag(tag_value, options = {})
761
+ options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
762
+ options["type"] = "radio"
763
+ options["value"] = tag_value
764
+ if options.has_key?("checked")
765
+ cv = options.delete "checked"
766
+ checked = cv == true || cv == "checked"
767
+ else
768
+ checked = self.class.radio_button_checked?(value(object), tag_value)
769
+ end
770
+ options["checked"] = "checked" if checked
771
+ add_default_name_and_id_for_value(tag_value, options)
772
+ tag("input", options)
773
+ end
774
+
775
+ def to_text_area_tag(options = {})
776
+ options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys)
777
+ add_default_name_and_id(options)
778
+
779
+ if size = options.delete("size")
780
+ options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
781
+ end
782
+
783
+ content_tag("textarea", html_escape(options.delete('value') || value_before_type_cast(object)), options)
784
+ end
785
+
786
+ def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
787
+ options = options.stringify_keys
788
+ options["type"] = "checkbox"
789
+ options["value"] = checked_value
790
+ if options.has_key?("checked")
791
+ cv = options.delete "checked"
792
+ checked = cv == true || cv == "checked"
793
+ else
794
+ checked = self.class.check_box_checked?(value(object), checked_value)
795
+ end
796
+ options["checked"] = "checked" if checked
797
+ add_default_name_and_id(options)
798
+ hidden = tag("input", "name" => options["name"], "type" => "hidden", "value" => options['disabled'] && checked ? checked_value : unchecked_value)
799
+ checkbox = tag("input", options)
800
+ (hidden + checkbox).html_safe!
801
+ end
802
+
803
+ def to_boolean_select_tag(options = {})
804
+ options = options.stringify_keys
805
+ add_default_name_and_id(options)
806
+ value = value(object)
807
+ tag_text = "<select"
808
+ tag_text << tag_options(options)
809
+ tag_text << "><option value=\"false\""
810
+ tag_text << " selected" if value == false
811
+ tag_text << ">False</option><option value=\"true\""
812
+ tag_text << " selected" if value
813
+ tag_text << ">True</option></select>"
814
+ end
815
+
816
+ def to_content_tag(tag_name, options = {})
817
+ content_tag(tag_name, value(object), options)
818
+ end
819
+
820
+ def object
821
+ @object || @template_object.instance_variable_get("@#{@object_name}")
822
+ rescue NameError
823
+ # As @object_name may contain the nested syntax (item[subobject]) we
824
+ # need to fallback to nil.
825
+ nil
826
+ end
827
+
828
+ def value(object)
829
+ self.class.value(object, @method_name)
830
+ end
831
+
832
+ def value_before_type_cast(object)
833
+ self.class.value_before_type_cast(object, @method_name)
834
+ end
835
+
836
+ class << self
837
+ def value(object, method_name)
838
+ object.send method_name unless object.nil?
839
+ end
840
+
841
+ def value_before_type_cast(object, method_name)
842
+ unless object.nil?
843
+ object.respond_to?(method_name + "_before_type_cast") ?
844
+ object.send(method_name + "_before_type_cast") :
845
+ object.send(method_name)
846
+ end
847
+ end
848
+
849
+ def check_box_checked?(value, checked_value)
850
+ case value
851
+ when TrueClass, FalseClass
852
+ value
853
+ when NilClass
854
+ false
855
+ when Integer
856
+ value != 0
857
+ when String
858
+ value == checked_value
859
+ when Array
860
+ value.include?(checked_value)
861
+ else
862
+ value.to_i != 0
863
+ end
864
+ end
865
+
866
+ def radio_button_checked?(value, checked_value)
867
+ value.to_s == checked_value.to_s
868
+ end
869
+ end
870
+
871
+ private
872
+ def add_default_name_and_id_for_value(tag_value, options)
873
+ unless tag_value.nil?
874
+ pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
875
+ specified_id = options["id"]
876
+ add_default_name_and_id(options)
877
+ options["id"] += "_#{pretty_tag_value}" unless specified_id
878
+ else
879
+ add_default_name_and_id(options)
880
+ end
881
+ end
882
+
883
+ def add_default_name_and_id(options)
884
+ if options.has_key?("index")
885
+ options["name"] ||= tag_name_with_index(options["index"])
886
+ options["id"] ||= tag_id_with_index(options["index"])
887
+ options.delete("index")
888
+ elsif defined?(@auto_index)
889
+ options["name"] ||= tag_name_with_index(@auto_index)
890
+ options["id"] ||= tag_id_with_index(@auto_index)
891
+ else
892
+ options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '')
893
+ options["id"] ||= tag_id
894
+ end
895
+ end
896
+
897
+ def tag_name
898
+ "#{@object_name}[#{sanitized_method_name}]"
899
+ end
900
+
901
+ def tag_name_with_index(index)
902
+ "#{@object_name}[#{index}][#{sanitized_method_name}]"
903
+ end
904
+
905
+ def tag_id
906
+ "#{sanitized_object_name}_#{sanitized_method_name}"
907
+ end
908
+
909
+ def tag_id_with_index(index)
910
+ "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
911
+ end
912
+
913
+ def sanitized_object_name
914
+ @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
915
+ end
916
+
917
+ def sanitized_method_name
918
+ @sanitized_method_name ||= @method_name.sub(/\?$/,"")
919
+ end
920
+ end
921
+
922
+ class FormBuilder #:nodoc:
923
+ # The methods which wrap a form helper call.
924
+ class_inheritable_accessor :field_helpers
925
+ self.field_helpers = (FormHelper.instance_methods - ['form_for'])
926
+
927
+ attr_accessor :object_name, :object, :options
928
+
929
+ def initialize(object_name, object, template, options, proc)
930
+ @nested_child_index = {}
931
+ @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
932
+ @default_options = @options ? @options.slice(:index) : {}
933
+ if @object_name.to_s.match(/\[\]$/)
934
+ if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
935
+ @auto_index = object.to_param
936
+ else
937
+ raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
938
+ end
939
+ end
940
+ end
941
+
942
+ (field_helpers - %w(label check_box radio_button fields_for hidden_field)).each do |selector|
943
+ src = <<-end_src
944
+ def #{selector}(method, options = {}) # def text_field(method, options = {})
945
+ @template.send( # @template.send(
946
+ #{selector.inspect}, # "text_field",
947
+ @object_name, # @object_name,
948
+ method, # method,
949
+ objectify_options(options)) # objectify_options(options))
950
+ end # end
951
+ end_src
952
+ class_eval src, __FILE__, __LINE__
953
+ end
954
+
955
+ def fields_for(record_or_name_or_array, *args, &block)
956
+ if options.has_key?(:index)
957
+ index = "[#{options[:index]}]"
958
+ elsif defined?(@auto_index)
959
+ self.object_name = @object_name.to_s.sub(/\[\]$/,"")
960
+ index = "[#{@auto_index}]"
961
+ else
962
+ index = ""
963
+ end
964
+
965
+ if options[:builder]
966
+ args << {} unless args.last.is_a?(Hash)
967
+ args.last[:builder] ||= options[:builder]
968
+ end
969
+
970
+ case record_or_name_or_array
971
+ when String, Symbol
972
+ if nested_attributes_association?(record_or_name_or_array)
973
+ return fields_for_with_nested_attributes(record_or_name_or_array, args, block)
974
+ else
975
+ name = "#{object_name}#{index}[#{record_or_name_or_array}]"
976
+ end
977
+ when Array
978
+ object = record_or_name_or_array.last
979
+ name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
980
+ args.unshift(object)
981
+ else
982
+ object = record_or_name_or_array
983
+ name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
984
+ args.unshift(object)
985
+ end
986
+
987
+ @template.fields_for(name, *args, &block)
988
+ end
989
+
990
+ def label(method, text = nil, options = {})
991
+ @template.label(@object_name, method, text, objectify_options(options))
992
+ end
993
+
994
+ def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
995
+ @template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value)
996
+ end
997
+
998
+ def radio_button(method, tag_value, options = {})
999
+ @template.radio_button(@object_name, method, tag_value, objectify_options(options))
1000
+ end
1001
+
1002
+ def hidden_field(method, options = {})
1003
+ @emitted_hidden_id = true if method == :id
1004
+ @template.hidden_field(@object_name, method, objectify_options(options))
1005
+ end
1006
+
1007
+ def error_message_on(method, *args)
1008
+ @template.error_message_on(@object, method, *args)
1009
+ end
1010
+
1011
+ def error_messages(options = {})
1012
+ @template.error_messages_for(@object_name, objectify_options(options))
1013
+ end
1014
+
1015
+ def submit(value = "Save changes", options = {})
1016
+ @template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit"))
1017
+ end
1018
+
1019
+ def emitted_hidden_id?
1020
+ @emitted_hidden_id
1021
+ end
1022
+
1023
+ private
1024
+ def objectify_options(options)
1025
+ @default_options.merge(options.merge(:object => @object))
1026
+ end
1027
+
1028
+ def nested_attributes_association?(association_name)
1029
+ @object.respond_to?("#{association_name}_attributes=")
1030
+ end
1031
+
1032
+ def fields_for_with_nested_attributes(association_name, args, block)
1033
+ name = "#{object_name}[#{association_name}_attributes]"
1034
+ association = args.first
1035
+
1036
+ if association.respond_to?(:new_record?)
1037
+ association = [association] if @object.send(association_name).is_a?(Array)
1038
+ elsif !association.is_a?(Array)
1039
+ association = @object.send(association_name)
1040
+ end
1041
+
1042
+ if association.is_a?(Array)
1043
+ explicit_child_index = args.last[:child_index] if args.last.is_a?(Hash)
1044
+ association.map do |child|
1045
+ fields_for_nested_model("#{name}[#{explicit_child_index || nested_child_index(name)}]", child, args, block)
1046
+ end.join
1047
+ elsif association
1048
+ fields_for_nested_model(name, association, args, block)
1049
+ end
1050
+ end
1051
+
1052
+ def fields_for_nested_model(name, object, args, block)
1053
+ if object.new_record?
1054
+ @template.fields_for(name, object, *args, &block)
1055
+ else
1056
+ @template.fields_for(name, object, *args) do |builder|
1057
+ block.call(builder)
1058
+ @template.concat builder.hidden_field(:id) unless builder.emitted_hidden_id?
1059
+ end
1060
+ end
1061
+ end
1062
+
1063
+ def nested_child_index(name)
1064
+ @nested_child_index[name] ||= -1
1065
+ @nested_child_index[name] += 1
1066
+ end
1067
+ end
1068
+ end
1069
+
1070
+ class Base
1071
+ cattr_accessor :default_form_builder
1072
+ self.default_form_builder = ::ActionView::Helpers::FormBuilder
1073
+ end
1074
+ end