actionpack-rack-upgrade 2.3.14

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 (441) hide show
  1. data/CHANGELOG +5250 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +409 -0
  4. data/RUNNING_UNIT_TESTS +24 -0
  5. data/Rakefile +158 -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 +169 -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 +1425 -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 +197 -0
  27. data/lib/action_controller/dispatcher.rb +133 -0
  28. data/lib/action_controller/failsafe.rb +87 -0
  29. data/lib/action_controller/filters.rb +680 -0
  30. data/lib/action_controller/flash.rb +213 -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 +708 -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 +495 -0
  48. data/lib/action_controller/request_forgery_protection.rb +116 -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 +237 -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 +503 -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 +276 -0
  61. data/lib/action_controller/session/cookie_store.rb +240 -0
  62. data/lib/action_controller/session/mem_cache_store.rb +60 -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 +229 -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/helpers/active_record_helper.rb +305 -0
  93. data/lib/action_view/helpers/asset_tag_helper.rb +695 -0
  94. data/lib/action_view/helpers/atom_feed_helper.rb +198 -0
  95. data/lib/action_view/helpers/benchmark_helper.rb +54 -0
  96. data/lib/action_view/helpers/cache_helper.rb +39 -0
  97. data/lib/action_view/helpers/capture_helper.rb +136 -0
  98. data/lib/action_view/helpers/csrf_helper.rb +14 -0
  99. data/lib/action_view/helpers/date_helper.rb +989 -0
  100. data/lib/action_view/helpers/debug_helper.rb +38 -0
  101. data/lib/action_view/helpers/form_helper.rb +1118 -0
  102. data/lib/action_view/helpers/form_options_helper.rb +599 -0
  103. data/lib/action_view/helpers/form_tag_helper.rb +490 -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 +251 -0
  111. data/lib/action_view/helpers/scriptaculous_helper.rb +226 -0
  112. data/lib/action_view/helpers/tag_helper.rb +151 -0
  113. data/lib/action_view/helpers/text_helper.rb +597 -0
  114. data/lib/action_view/helpers/translation_helper.rb +67 -0
  115. data/lib/action_view/helpers/url_helper.rb +637 -0
  116. data/lib/action_view/helpers.rb +61 -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 +241 -0
  120. data/lib/action_view/paths.rb +77 -0
  121. data/lib/action_view/reloadable_template.rb +117 -0
  122. data/lib/action_view/renderable.rb +109 -0
  123. data/lib/action_view/renderable_partial.rb +53 -0
  124. data/lib/action_view/template.rb +252 -0
  125. data/lib/action_view/template_error.rb +99 -0
  126. data/lib/action_view/template_handler.rb +34 -0
  127. data/lib/action_view/template_handlers/builder.rb +17 -0
  128. data/lib/action_view/template_handlers/erb.rb +25 -0
  129. data/lib/action_view/template_handlers/rjs.rb +13 -0
  130. data/lib/action_view/template_handlers.rb +48 -0
  131. data/lib/action_view/test_case.rb +162 -0
  132. data/lib/action_view.rb +58 -0
  133. data/lib/actionpack.rb +2 -0
  134. data/test/abstract_unit.rb +78 -0
  135. data/test/active_record_unit.rb +104 -0
  136. data/test/activerecord/active_record_store_test.rb +221 -0
  137. data/test/activerecord/render_partial_with_record_identification_test.rb +188 -0
  138. data/test/adv_attr_test.rb +20 -0
  139. data/test/controller/action_pack_assertions_test.rb +545 -0
  140. data/test/controller/addresses_render_test.rb +37 -0
  141. data/test/controller/assert_select_test.rb +735 -0
  142. data/test/controller/base_test.rb +217 -0
  143. data/test/controller/benchmark_test.rb +32 -0
  144. data/test/controller/caching_test.rb +743 -0
  145. data/test/controller/capture_test.rb +66 -0
  146. data/test/controller/content_type_test.rb +178 -0
  147. data/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb +0 -0
  148. data/test/controller/controller_fixtures/app/controllers/user_controller.rb +0 -0
  149. data/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb +0 -0
  150. data/test/controller/cookie_test.rb +208 -0
  151. data/test/controller/deprecation/deprecated_base_methods_test.rb +32 -0
  152. data/test/controller/dispatcher_test.rb +144 -0
  153. data/test/controller/dom_assertions_test.rb +53 -0
  154. data/test/controller/failsafe_test.rb +60 -0
  155. data/test/controller/fake_controllers.rb +33 -0
  156. data/test/controller/fake_models.rb +19 -0
  157. data/test/controller/filter_params_test.rb +52 -0
  158. data/test/controller/filters_test.rb +885 -0
  159. data/test/controller/flash_test.rb +174 -0
  160. data/test/controller/header_test.rb +14 -0
  161. data/test/controller/helper_test.rb +224 -0
  162. data/test/controller/html-scanner/cdata_node_test.rb +15 -0
  163. data/test/controller/html-scanner/document_test.rb +148 -0
  164. data/test/controller/html-scanner/node_test.rb +89 -0
  165. data/test/controller/html-scanner/sanitizer_test.rb +281 -0
  166. data/test/controller/html-scanner/tag_node_test.rb +238 -0
  167. data/test/controller/html-scanner/text_node_test.rb +50 -0
  168. data/test/controller/html-scanner/tokenizer_test.rb +131 -0
  169. data/test/controller/http_basic_authentication_test.rb +113 -0
  170. data/test/controller/http_digest_authentication_test.rb +254 -0
  171. data/test/controller/integration_test.rb +526 -0
  172. data/test/controller/layout_test.rb +215 -0
  173. data/test/controller/localized_templates_test.rb +24 -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/output_escaping_test.rb +19 -0
  179. data/test/controller/polymorphic_routes_test.rb +297 -0
  180. data/test/controller/rack_test.rb +308 -0
  181. data/test/controller/record_identifier_test.rb +139 -0
  182. data/test/controller/redirect_test.rb +285 -0
  183. data/test/controller/reloader_test.rb +125 -0
  184. data/test/controller/render_test.rb +1783 -0
  185. data/test/controller/request/json_params_parsing_test.rb +65 -0
  186. data/test/controller/request/multipart_params_parsing_test.rb +177 -0
  187. data/test/controller/request/query_string_parsing_test.rb +120 -0
  188. data/test/controller/request/test_request_test.rb +35 -0
  189. data/test/controller/request/url_encoded_params_parsing_test.rb +146 -0
  190. data/test/controller/request/xml_params_parsing_test.rb +103 -0
  191. data/test/controller/request_forgery_protection_test.rb +233 -0
  192. data/test/controller/request_test.rb +395 -0
  193. data/test/controller/rescue_test.rb +541 -0
  194. data/test/controller/resources_test.rb +1393 -0
  195. data/test/controller/routing_test.rb +2592 -0
  196. data/test/controller/selector_test.rb +628 -0
  197. data/test/controller/send_file_test.rb +171 -0
  198. data/test/controller/session/abstract_store_test.rb +64 -0
  199. data/test/controller/session/cookie_store_test.rb +354 -0
  200. data/test/controller/session/mem_cache_store_test.rb +187 -0
  201. data/test/controller/session/test_session_test.rb +58 -0
  202. data/test/controller/test_test.rb +700 -0
  203. data/test/controller/translation_test.rb +26 -0
  204. data/test/controller/url_rewriter_test.rb +395 -0
  205. data/test/controller/verification_test.rb +270 -0
  206. data/test/controller/view_paths_test.rb +141 -0
  207. data/test/controller/webservice_test.rb +260 -0
  208. data/test/fixtures/_top_level_partial.html.erb +1 -0
  209. data/test/fixtures/_top_level_partial_only.erb +1 -0
  210. data/test/fixtures/addresses/list.erb +1 -0
  211. data/test/fixtures/alternate_helpers/foo_helper.rb +3 -0
  212. data/test/fixtures/bad_customers/_bad_customer.html.erb +1 -0
  213. data/test/fixtures/companies.yml +24 -0
  214. data/test/fixtures/company.rb +10 -0
  215. data/test/fixtures/content_type/render_default_content_types_for_respond_to.rhtml +1 -0
  216. data/test/fixtures/content_type/render_default_for_rhtml.rhtml +1 -0
  217. data/test/fixtures/content_type/render_default_for_rjs.rjs +1 -0
  218. data/test/fixtures/content_type/render_default_for_rxml.rxml +1 -0
  219. data/test/fixtures/customers/_customer.html.erb +1 -0
  220. data/test/fixtures/db_definitions/sqlite.sql +49 -0
  221. data/test/fixtures/developer.rb +9 -0
  222. data/test/fixtures/developers/_developer.erb +1 -0
  223. data/test/fixtures/developers.yml +21 -0
  224. data/test/fixtures/developers_projects.yml +13 -0
  225. data/test/fixtures/failsafe/500.html +1 -0
  226. data/test/fixtures/fun/games/_game.erb +1 -0
  227. data/test/fixtures/fun/games/hello_world.erb +1 -0
  228. data/test/fixtures/fun/serious/games/_game.erb +1 -0
  229. data/test/fixtures/functional_caching/_partial.erb +3 -0
  230. data/test/fixtures/functional_caching/formatted_fragment_cached.html.erb +3 -0
  231. data/test/fixtures/functional_caching/formatted_fragment_cached.js.rjs +6 -0
  232. data/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder +5 -0
  233. data/test/fixtures/functional_caching/fragment_cached.html.erb +2 -0
  234. data/test/fixtures/functional_caching/html_fragment_cached_with_partial.html.erb +1 -0
  235. data/test/fixtures/functional_caching/inline_fragment_cached.html.erb +2 -0
  236. data/test/fixtures/functional_caching/js_fragment_cached_with_partial.js.rjs +1 -0
  237. data/test/fixtures/good_customers/_good_customer.html.erb +1 -0
  238. data/test/fixtures/helpers/abc_helper.rb +5 -0
  239. data/test/fixtures/helpers/fun/games_helper.rb +3 -0
  240. data/test/fixtures/helpers/fun/pdf_helper.rb +3 -0
  241. data/test/fixtures/layout_tests/abs_path_layout.rhtml +1 -0
  242. data/test/fixtures/layout_tests/alt/hello.rhtml +1 -0
  243. data/test/fixtures/layout_tests/alt/layouts/alt.rhtml +0 -0
  244. data/test/fixtures/layout_tests/layouts/controller_name_space/nested.rhtml +1 -0
  245. data/test/fixtures/layout_tests/layouts/item.rhtml +1 -0
  246. data/test/fixtures/layout_tests/layouts/layout_test.rhtml +1 -0
  247. data/test/fixtures/layout_tests/layouts/multiple_extensions.html.erb +1 -0
  248. data/test/fixtures/layout_tests/layouts/third_party_template_library.mab +1 -0
  249. data/test/fixtures/layout_tests/views/hello.rhtml +1 -0
  250. data/test/fixtures/layouts/_column.html.erb +2 -0
  251. data/test/fixtures/layouts/block_with_layout.erb +3 -0
  252. data/test/fixtures/layouts/builder.builder +3 -0
  253. data/test/fixtures/layouts/default_html.html.erb +1 -0
  254. data/test/fixtures/layouts/partial_with_layout.erb +3 -0
  255. data/test/fixtures/layouts/standard.erb +1 -0
  256. data/test/fixtures/layouts/talk_from_action.erb +2 -0
  257. data/test/fixtures/layouts/xhr.html.erb +2 -0
  258. data/test/fixtures/layouts/yield.erb +2 -0
  259. data/test/fixtures/localized/hello_world.de.html +1 -0
  260. data/test/fixtures/localized/hello_world.en.html +1 -0
  261. data/test/fixtures/mascot.rb +3 -0
  262. data/test/fixtures/mascots/_mascot.html.erb +1 -0
  263. data/test/fixtures/mascots.yml +4 -0
  264. data/test/fixtures/multipart/binary_file +0 -0
  265. data/test/fixtures/multipart/boundary_problem_file +10 -0
  266. data/test/fixtures/multipart/bracketed_param +5 -0
  267. data/test/fixtures/multipart/empty +10 -0
  268. data/test/fixtures/multipart/hello.txt +1 -0
  269. data/test/fixtures/multipart/large_text_file +10 -0
  270. data/test/fixtures/multipart/mixed_files +0 -0
  271. data/test/fixtures/multipart/mona_lisa.jpg +0 -0
  272. data/test/fixtures/multipart/none +9 -0
  273. data/test/fixtures/multipart/single_parameter +5 -0
  274. data/test/fixtures/multipart/text_file +10 -0
  275. data/test/fixtures/override/test/hello_world.erb +1 -0
  276. data/test/fixtures/override2/layouts/test/sub.erb +1 -0
  277. data/test/fixtures/post_test/layouts/post.html.erb +1 -0
  278. data/test/fixtures/post_test/layouts/super_post.iphone.erb +1 -0
  279. data/test/fixtures/post_test/post/index.html.erb +1 -0
  280. data/test/fixtures/post_test/post/index.iphone.erb +1 -0
  281. data/test/fixtures/post_test/super_post/index.html.erb +1 -0
  282. data/test/fixtures/post_test/super_post/index.iphone.erb +1 -0
  283. data/test/fixtures/project.rb +3 -0
  284. data/test/fixtures/projects/_project.erb +1 -0
  285. data/test/fixtures/projects.yml +7 -0
  286. data/test/fixtures/public/404.html +1 -0
  287. data/test/fixtures/public/500.da.html +1 -0
  288. data/test/fixtures/public/500.html +1 -0
  289. data/test/fixtures/public/absolute/test.css +23 -0
  290. data/test/fixtures/public/absolute/test.js +63 -0
  291. data/test/fixtures/public/images/rails.png +0 -0
  292. data/test/fixtures/public/javascripts/application.js +1 -0
  293. data/test/fixtures/public/javascripts/bank.js +1 -0
  294. data/test/fixtures/public/javascripts/controls.js +1 -0
  295. data/test/fixtures/public/javascripts/dragdrop.js +1 -0
  296. data/test/fixtures/public/javascripts/effects.js +1 -0
  297. data/test/fixtures/public/javascripts/prototype.js +1 -0
  298. data/test/fixtures/public/javascripts/robber.js +1 -0
  299. data/test/fixtures/public/javascripts/subdir/subdir.js +1 -0
  300. data/test/fixtures/public/javascripts/version.1.0.js +1 -0
  301. data/test/fixtures/public/stylesheets/bank.css +1 -0
  302. data/test/fixtures/public/stylesheets/robber.css +1 -0
  303. data/test/fixtures/public/stylesheets/subdir/subdir.css +1 -0
  304. data/test/fixtures/public/stylesheets/version.1.0.css +1 -0
  305. data/test/fixtures/quiz/questions/_question.html.erb +1 -0
  306. data/test/fixtures/replies/_reply.erb +1 -0
  307. data/test/fixtures/replies.yml +15 -0
  308. data/test/fixtures/reply.rb +7 -0
  309. data/test/fixtures/respond_to/all_types_with_layout.html.erb +1 -0
  310. data/test/fixtures/respond_to/all_types_with_layout.js.rjs +1 -0
  311. data/test/fixtures/respond_to/custom_constant_handling_without_block.mobile.erb +1 -0
  312. data/test/fixtures/respond_to/iphone_with_html_response_type.html.erb +1 -0
  313. data/test/fixtures/respond_to/iphone_with_html_response_type.iphone.erb +1 -0
  314. data/test/fixtures/respond_to/layouts/missing.html.erb +1 -0
  315. data/test/fixtures/respond_to/layouts/standard.html.erb +1 -0
  316. data/test/fixtures/respond_to/layouts/standard.iphone.erb +1 -0
  317. data/test/fixtures/respond_to/using_defaults.html.erb +1 -0
  318. data/test/fixtures/respond_to/using_defaults.js.rjs +1 -0
  319. data/test/fixtures/respond_to/using_defaults.xml.builder +1 -0
  320. data/test/fixtures/respond_to/using_defaults_with_type_list.html.erb +1 -0
  321. data/test/fixtures/respond_to/using_defaults_with_type_list.js.rjs +1 -0
  322. data/test/fixtures/respond_to/using_defaults_with_type_list.xml.builder +1 -0
  323. data/test/fixtures/scope/test/modgreet.erb +1 -0
  324. data/test/fixtures/session_autoload_test/session_autoload_test/foo.rb +10 -0
  325. data/test/fixtures/shared.html.erb +1 -0
  326. data/test/fixtures/symlink_parent/symlinked_layout.erb +5 -0
  327. data/test/fixtures/test/_counter.html.erb +1 -0
  328. data/test/fixtures/test/_customer.erb +1 -0
  329. data/test/fixtures/test/_customer_counter.erb +1 -0
  330. data/test/fixtures/test/_customer_counter_with_as.erb +1 -0
  331. data/test/fixtures/test/_customer_greeting.erb +1 -0
  332. data/test/fixtures/test/_customer_with_var.erb +1 -0
  333. data/test/fixtures/test/_form.erb +1 -0
  334. data/test/fixtures/test/_from_helper.erb +1 -0
  335. data/test/fixtures/test/_hash_greeting.erb +1 -0
  336. data/test/fixtures/test/_hash_object.erb +2 -0
  337. data/test/fixtures/test/_hello.builder +1 -0
  338. data/test/fixtures/test/_labelling_form.erb +1 -0
  339. data/test/fixtures/test/_layout_for_block_with_args.html.erb +3 -0
  340. data/test/fixtures/test/_layout_for_partial.html.erb +3 -0
  341. data/test/fixtures/test/_local_inspector.html.erb +1 -0
  342. data/test/fixtures/test/_one.html.erb +1 -0
  343. data/test/fixtures/test/_partial.erb +1 -0
  344. data/test/fixtures/test/_partial.html.erb +1 -0
  345. data/test/fixtures/test/_partial.js.erb +1 -0
  346. data/test/fixtures/test/_partial_for_use_in_layout.html.erb +1 -0
  347. data/test/fixtures/test/_partial_only.erb +1 -0
  348. data/test/fixtures/test/_partial_with_only_html_version.html.erb +1 -0
  349. data/test/fixtures/test/_person.erb +2 -0
  350. data/test/fixtures/test/_raise.html.erb +1 -0
  351. data/test/fixtures/test/_two.html.erb +1 -0
  352. data/test/fixtures/test/_utf8_partial.html.erb +1 -0
  353. data/test/fixtures/test/_utf8_partial_magic.html.erb +2 -0
  354. data/test/fixtures/test/action_talk_to_layout.erb +2 -0
  355. data/test/fixtures/test/array_translation.erb +1 -0
  356. data/test/fixtures/test/calling_partial_with_layout.html.erb +1 -0
  357. data/test/fixtures/test/capturing.erb +4 -0
  358. data/test/fixtures/test/content_for.erb +2 -0
  359. data/test/fixtures/test/content_for_concatenated.erb +3 -0
  360. data/test/fixtures/test/content_for_with_parameter.erb +2 -0
  361. data/test/fixtures/test/delete_with_js.rjs +2 -0
  362. data/test/fixtures/test/dont_pick_me +1 -0
  363. data/test/fixtures/test/dot.directory/render_file_with_ivar.erb +1 -0
  364. data/test/fixtures/test/enum_rjs_test.rjs +6 -0
  365. data/test/fixtures/test/formatted_html_erb.html.erb +1 -0
  366. data/test/fixtures/test/formatted_xml_erb.builder +1 -0
  367. data/test/fixtures/test/formatted_xml_erb.html.erb +1 -0
  368. data/test/fixtures/test/formatted_xml_erb.xml.erb +1 -0
  369. data/test/fixtures/test/greeting.erb +1 -0
  370. data/test/fixtures/test/greeting.js.rjs +1 -0
  371. data/test/fixtures/test/hello.builder +4 -0
  372. data/test/fixtures/test/hello_world.da.html.erb +1 -0
  373. data/test/fixtures/test/hello_world.erb +1 -0
  374. data/test/fixtures/test/hello_world.erb~ +1 -0
  375. data/test/fixtures/test/hello_world.pt-BR.html.erb +1 -0
  376. data/test/fixtures/test/hello_world_container.builder +3 -0
  377. data/test/fixtures/test/hello_world_from_rxml.builder +4 -0
  378. data/test/fixtures/test/hello_world_with_layout_false.erb +1 -0
  379. data/test/fixtures/test/hello_xml_world.builder +11 -0
  380. data/test/fixtures/test/hyphen-ated.erb +1 -0
  381. data/test/fixtures/test/implicit_content_type.atom.builder +2 -0
  382. data/test/fixtures/test/list.erb +1 -0
  383. data/test/fixtures/test/malformed/malformed.en.html.erb~ +1 -0
  384. data/test/fixtures/test/malformed/malformed.erb~ +1 -0
  385. data/test/fixtures/test/malformed/malformed.html.erb~ +1 -0
  386. data/test/fixtures/test/nested_layout.erb +3 -0
  387. data/test/fixtures/test/non_erb_block_content_for.builder +4 -0
  388. data/test/fixtures/test/potential_conflicts.erb +4 -0
  389. data/test/fixtures/test/render_explicit_html_template.js.rjs +1 -0
  390. data/test/fixtures/test/render_file_from_template.html.erb +1 -0
  391. data/test/fixtures/test/render_file_with_ivar.erb +1 -0
  392. data/test/fixtures/test/render_file_with_locals.erb +1 -0
  393. data/test/fixtures/test/render_implicit_html_template.js.rjs +1 -0
  394. data/test/fixtures/test/render_implicit_html_template_from_xhr_request.da.html.erb +1 -0
  395. data/test/fixtures/test/render_implicit_html_template_from_xhr_request.html.erb +1 -0
  396. data/test/fixtures/test/render_implicit_js_template_without_layout.js.erb +1 -0
  397. data/test/fixtures/test/render_to_string_test.erb +1 -0
  398. data/test/fixtures/test/scoped_array_translation.erb +1 -0
  399. data/test/fixtures/test/sub_template_raise.html.erb +1 -0
  400. data/test/fixtures/test/template.erb +1 -0
  401. data/test/fixtures/test/translation.erb +1 -0
  402. data/test/fixtures/test/update_element_with_capture.erb +9 -0
  403. data/test/fixtures/test/using_layout_around_block.html.erb +1 -0
  404. data/test/fixtures/test/using_layout_around_block_with_args.html.erb +1 -0
  405. data/test/fixtures/test/utf8.html.erb +4 -0
  406. data/test/fixtures/test/utf8_magic.html.erb +5 -0
  407. data/test/fixtures/test/utf8_magic_with_bare_partial.html.erb +5 -0
  408. data/test/fixtures/topic.rb +3 -0
  409. data/test/fixtures/topics/_topic.html.erb +1 -0
  410. data/test/fixtures/topics.yml +22 -0
  411. data/test/template/active_record_helper_i18n_test.rb +51 -0
  412. data/test/template/active_record_helper_test.rb +302 -0
  413. data/test/template/asset_tag_helper_test.rb +770 -0
  414. data/test/template/atom_feed_helper_test.rb +315 -0
  415. data/test/template/benchmark_helper_test.rb +86 -0
  416. data/test/template/compiled_templates_test.rb +204 -0
  417. data/test/template/date_helper_i18n_test.rb +121 -0
  418. data/test/template/date_helper_test.rb +2603 -0
  419. data/test/template/erb_util_test.rb +36 -0
  420. data/test/template/form_helper_test.rb +1447 -0
  421. data/test/template/form_options_helper_i18n_test.rb +27 -0
  422. data/test/template/form_options_helper_test.rb +811 -0
  423. data/test/template/form_tag_helper_test.rb +356 -0
  424. data/test/template/javascript_helper_test.rb +106 -0
  425. data/test/template/number_helper_i18n_test.rb +69 -0
  426. data/test/template/number_helper_test.rb +132 -0
  427. data/test/template/prototype_helper_test.rb +639 -0
  428. data/test/template/raw_output_helper_test.rb +21 -0
  429. data/test/template/record_tag_helper_test.rb +58 -0
  430. data/test/template/render_test.rb +329 -0
  431. data/test/template/sanitize_helper_test.rb +57 -0
  432. data/test/template/scriptaculous_helper_test.rb +90 -0
  433. data/test/template/tag_helper_test.rb +98 -0
  434. data/test/template/template_test.rb +32 -0
  435. data/test/template/test_test.rb +54 -0
  436. data/test/template/text_helper_test.rb +597 -0
  437. data/test/template/translation_helper_test.rb +95 -0
  438. data/test/template/url_helper_test.rb +641 -0
  439. data/test/testing_sandbox.rb +15 -0
  440. data/test/view/test_case_test.rb +176 -0
  441. metadata +519 -0
@@ -0,0 +1,1118 @@
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>_destroy</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 :_destroy %>
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>_destroy</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 :_destroy %>
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 a translation
504
+ # is found in the current I18n locale (through views.labels.<modelname>.<attribute>) or you specify it explicitly.
505
+ # Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
506
+ # onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to
507
+ # target labels for radio_button tags (where the value is used in the ID of the input tag).
508
+ #
509
+ # ==== Examples
510
+ # label(:post, :title)
511
+ # # => <label for="post_title">Title</label>
512
+ #
513
+ # You can localize your labels based on model and attribute names.
514
+ # For example you can define the following in your locale (e.g. en.yml)
515
+ #
516
+ # views:
517
+ # labels:
518
+ # post:
519
+ # body: "Write your entire text here"
520
+ #
521
+ # Which then will result in
522
+ #
523
+ # label(:post, :body)
524
+ # # => <label for="post_body">Write your entire text here</label>
525
+ #
526
+ # Localization can also be based purely on the translation of the attribute-name like this:
527
+ #
528
+ # activerecord:
529
+ # attribute:
530
+ # post:
531
+ # cost: "Total cost"
532
+ #
533
+ # label(:post, :cost)
534
+ # # => <label for="post_cost">Total cost</label>
535
+ #
536
+ # label(:post, :title, "A short title")
537
+ # # => <label for="post_title">A short title</label>
538
+ #
539
+ # label(:post, :title, "A short title", :class => "title_label")
540
+ # # => <label for="post_title" class="title_label">A short title</label>
541
+ #
542
+ # label(:post, :privacy, "Public Post", :value => "public")
543
+ # # => <label for="post_privacy_public">Public Post</label>
544
+ #
545
+ def label(object_name, method, text = nil, options = {})
546
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options)
547
+ end
548
+
549
+ # Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
550
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
551
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
552
+ # shown.
553
+ #
554
+ # ==== Examples
555
+ # text_field(:post, :title, :size => 20)
556
+ # # => <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" />
557
+ #
558
+ # text_field(:post, :title, :class => "create_input")
559
+ # # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
560
+ #
561
+ # text_field(:session, :user, :onchange => "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }")
562
+ # # => <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!'); }"/>
563
+ #
564
+ # text_field(:snippet, :code, :size => 20, :class => 'code_input')
565
+ # # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
566
+ #
567
+ def text_field(object_name, method, options = {})
568
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("text", options)
569
+ end
570
+
571
+ # Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
572
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
573
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
574
+ # shown.
575
+ #
576
+ # ==== Examples
577
+ # password_field(:login, :pass, :size => 20)
578
+ # # => <input type="text" id="login_pass" name="login[pass]" size="20" value="#{@login.pass}" />
579
+ #
580
+ # password_field(:account, :secret, :class => "form_input")
581
+ # # => <input type="text" id="account_secret" name="account[secret]" value="#{@account.secret}" class="form_input" />
582
+ #
583
+ # password_field(:user, :password, :onchange => "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }")
584
+ # # => <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!'); }"/>
585
+ #
586
+ # password_field(:account, :pin, :size => 20, :class => 'form_input')
587
+ # # => <input type="text" id="account_pin" name="account[pin]" size="20" value="#{@account.pin}" class="form_input" />
588
+ #
589
+ def password_field(object_name, method, options = {})
590
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("password", options)
591
+ end
592
+
593
+ # Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
594
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
595
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
596
+ # shown.
597
+ #
598
+ # ==== Examples
599
+ # hidden_field(:signup, :pass_confirm)
600
+ # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
601
+ #
602
+ # hidden_field(:post, :tag_list)
603
+ # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
604
+ #
605
+ # hidden_field(:user, :token)
606
+ # # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
607
+ def hidden_field(object_name, method, options = {})
608
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("hidden", options)
609
+ end
610
+
611
+ # Returns an file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
612
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
613
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
614
+ # shown.
615
+ #
616
+ # ==== Examples
617
+ # file_field(:user, :avatar)
618
+ # # => <input type="file" id="user_avatar" name="user[avatar]" />
619
+ #
620
+ # file_field(:post, :attached, :accept => 'text/html')
621
+ # # => <input type="file" id="post_attached" name="post[attached]" />
622
+ #
623
+ # file_field(:attachment, :file, :class => 'file_input')
624
+ # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
625
+ #
626
+ def file_field(object_name, method, options = {})
627
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("file", options)
628
+ end
629
+
630
+ # Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
631
+ # on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
632
+ # hash with +options+.
633
+ #
634
+ # ==== Examples
635
+ # text_area(:post, :body, :cols => 20, :rows => 40)
636
+ # # => <textarea cols="20" rows="40" id="post_body" name="post[body]">
637
+ # # #{@post.body}
638
+ # # </textarea>
639
+ #
640
+ # text_area(:comment, :text, :size => "20x30")
641
+ # # => <textarea cols="20" rows="30" id="comment_text" name="comment[text]">
642
+ # # #{@comment.text}
643
+ # # </textarea>
644
+ #
645
+ # text_area(:application, :notes, :cols => 40, :rows => 15, :class => 'app_input')
646
+ # # => <textarea cols="40" rows="15" id="application_notes" name="application[notes]" class="app_input">
647
+ # # #{@application.notes}
648
+ # # </textarea>
649
+ #
650
+ # text_area(:entry, :body, :size => "20x20", :disabled => 'disabled')
651
+ # # => <textarea cols="20" rows="20" id="entry_body" name="entry[body]" disabled="disabled">
652
+ # # #{@entry.body}
653
+ # # </textarea>
654
+ def text_area(object_name, method, options = {})
655
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_text_area_tag(options)
656
+ end
657
+
658
+ # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
659
+ # assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object.
660
+ # It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked.
661
+ # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
662
+ # while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
663
+ #
664
+ # ==== Gotcha
665
+ #
666
+ # The HTML specification says unchecked check boxes are not successful, and
667
+ # thus web browsers do not send them. Unfortunately this introduces a gotcha:
668
+ # if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid
669
+ # invoice the user unchecks its check box, no +paid+ parameter is sent. So,
670
+ # any mass-assignment idiom like
671
+ #
672
+ # @invoice.update_attributes(params[:invoice])
673
+ #
674
+ # wouldn't update the flag.
675
+ #
676
+ # To prevent this the helper generates an auxiliary hidden field before
677
+ # the very check box. The hidden field has the same name and its
678
+ # attributes mimick an unchecked check box.
679
+ #
680
+ # This way, the client either sends only the hidden field (representing
681
+ # the check box is unchecked), or both fields. Since the HTML specification
682
+ # says key/value pairs have to be sent in the same order they appear in the
683
+ # form, and parameters extraction gets the last occurrence of any repeated
684
+ # key in the query string, that works for ordinary forms.
685
+ #
686
+ # Unfortunately that workaround does not work when the check box goes
687
+ # within an array-like parameter, as in
688
+ #
689
+ # <% fields_for "project[invoice_attributes][]", invoice, :index => nil do |form| %>
690
+ # <%= form.check_box :paid %>
691
+ # ...
692
+ # <% end %>
693
+ #
694
+ # because parameter name repetition is precisely what Rails seeks to distinguish
695
+ # the elements of the array. For each item with a checked check box you
696
+ # get an extra ghost item with only that attribute, assigned to "0".
697
+ #
698
+ # In that case it is preferable to either use +check_box_tag+ or to use
699
+ # hashes instead of arrays.
700
+ #
701
+ # ==== Examples
702
+ # # Let's say that @post.validated? is 1:
703
+ # check_box("post", "validated")
704
+ # # => <input name="post[validated]" type="hidden" value="0" />
705
+ # # <input type="checkbox" id="post_validated" name="post[validated]" value="1" />
706
+ #
707
+ # # Let's say that @puppy.gooddog is "no":
708
+ # check_box("puppy", "gooddog", {}, "yes", "no")
709
+ # # => <input name="puppy[gooddog]" type="hidden" value="no" />
710
+ # # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
711
+ #
712
+ # check_box("eula", "accepted", { :class => 'eula_check' }, "yes", "no")
713
+ # # => <input name="eula[accepted]" type="hidden" value="no" />
714
+ # # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
715
+ #
716
+ def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
717
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
718
+ end
719
+
720
+ # Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
721
+ # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the
722
+ # radio button will be checked.
723
+ #
724
+ # To force the radio button to be checked pass <tt>:checked => true</tt> in the
725
+ # +options+ hash. You may pass HTML options there as well.
726
+ #
727
+ # ==== Examples
728
+ # # Let's say that @post.category returns "rails":
729
+ # radio_button("post", "category", "rails")
730
+ # radio_button("post", "category", "java")
731
+ # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
732
+ # # <input type="radio" id="post_category_java" name="post[category]" value="java" />
733
+ #
734
+ # radio_button("user", "receive_newsletter", "yes")
735
+ # radio_button("user", "receive_newsletter", "no")
736
+ # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
737
+ # # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
738
+ def radio_button(object_name, method, tag_value, options = {})
739
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_radio_button_tag(tag_value, options)
740
+ end
741
+ end
742
+
743
+ class InstanceTag #:nodoc:
744
+ include Helpers::TagHelper, Helpers::FormTagHelper
745
+
746
+ attr_reader :method_name, :object_name
747
+
748
+ DEFAULT_FIELD_OPTIONS = { "size" => 30 }.freeze unless const_defined?(:DEFAULT_FIELD_OPTIONS)
749
+ DEFAULT_RADIO_OPTIONS = { }.freeze unless const_defined?(:DEFAULT_RADIO_OPTIONS)
750
+ DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }.freeze unless const_defined?(:DEFAULT_TEXT_AREA_OPTIONS)
751
+
752
+ def initialize(object_name, method_name, template_object, object = nil)
753
+ @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
754
+ @template_object = template_object
755
+ @object = object
756
+ if @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
757
+ if (object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}")) && object.respond_to?(:to_param)
758
+ @auto_index = object.to_param
759
+ else
760
+ raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
761
+ end
762
+ end
763
+ end
764
+
765
+ def to_label_tag(text = nil, options = {})
766
+ options = options.stringify_keys
767
+ tag_value = options.delete("value")
768
+ name_and_id = options.dup
769
+ name_and_id["id"] = name_and_id["for"]
770
+ add_default_name_and_id_for_value(tag_value, name_and_id)
771
+ options.delete("index")
772
+ options["for"] ||= name_and_id["id"]
773
+
774
+ content = if text.blank?
775
+ i18n_label = I18n.t("helpers.label.#{object_name}.#{method_name}", :default => "")
776
+ i18n_label if i18n_label.present?
777
+ else
778
+ text.to_s
779
+ end
780
+
781
+ content ||= if object && object.class.respond_to?(:human_attribute_name)
782
+ object.class.human_attribute_name(method_name)
783
+ end
784
+
785
+ content ||= method_name.humanize
786
+
787
+ label_tag(name_and_id["id"], content, options)
788
+ end
789
+
790
+ def to_input_field_tag(field_type, options = {})
791
+ options = options.stringify_keys
792
+ options["size"] = options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] unless options.key?("size")
793
+ options = DEFAULT_FIELD_OPTIONS.merge(options)
794
+ if field_type == "hidden"
795
+ options.delete("size")
796
+ end
797
+ options["type"] = field_type
798
+ options["value"] ||= value_before_type_cast(object) unless field_type == "file"
799
+ options["value"] &&= html_escape(options["value"])
800
+ add_default_name_and_id(options)
801
+ tag("input", options)
802
+ end
803
+
804
+ def to_radio_button_tag(tag_value, options = {})
805
+ options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
806
+ options["type"] = "radio"
807
+ options["value"] = tag_value
808
+ if options.has_key?("checked")
809
+ cv = options.delete "checked"
810
+ checked = cv == true || cv == "checked"
811
+ else
812
+ checked = self.class.radio_button_checked?(value(object), tag_value)
813
+ end
814
+ options["checked"] = "checked" if checked
815
+ add_default_name_and_id_for_value(tag_value, options)
816
+ tag("input", options)
817
+ end
818
+
819
+ def to_text_area_tag(options = {})
820
+ options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys)
821
+ add_default_name_and_id(options)
822
+
823
+ if size = options.delete("size")
824
+ options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
825
+ end
826
+
827
+ content_tag("textarea", html_escape(options.delete('value') || value_before_type_cast(object)), options)
828
+ end
829
+
830
+ def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
831
+ options = options.stringify_keys
832
+ options["type"] = "checkbox"
833
+ options["value"] = checked_value
834
+ if options.has_key?("checked")
835
+ cv = options.delete "checked"
836
+ checked = cv == true || cv == "checked"
837
+ else
838
+ checked = self.class.check_box_checked?(value(object), checked_value)
839
+ end
840
+ options["checked"] = "checked" if checked
841
+ add_default_name_and_id(options)
842
+ hidden = tag("input", "name" => options["name"], "type" => "hidden", "value" => options['disabled'] && checked ? checked_value : unchecked_value)
843
+ checkbox = tag("input", options)
844
+ (hidden + checkbox).html_safe
845
+ end
846
+
847
+ def to_boolean_select_tag(options = {})
848
+ options = options.stringify_keys
849
+ add_default_name_and_id(options)
850
+ value = value(object)
851
+ tag_text = "<select"
852
+ tag_text << tag_options(options)
853
+ tag_text << "><option value=\"false\""
854
+ tag_text << " selected" if value == false
855
+ tag_text << ">False</option><option value=\"true\""
856
+ tag_text << " selected" if value
857
+ tag_text << ">True</option></select>"
858
+ end
859
+
860
+ def to_content_tag(tag_name, options = {})
861
+ content_tag(tag_name, value(object), options)
862
+ end
863
+
864
+ def object
865
+ @object || @template_object.instance_variable_get("@#{@object_name}")
866
+ rescue NameError
867
+ # As @object_name may contain the nested syntax (item[subobject]) we
868
+ # need to fallback to nil.
869
+ nil
870
+ end
871
+
872
+ def value(object)
873
+ self.class.value(object, @method_name)
874
+ end
875
+
876
+ def value_before_type_cast(object)
877
+ self.class.value_before_type_cast(object, @method_name)
878
+ end
879
+
880
+ class << self
881
+ def value(object, method_name)
882
+ object.send method_name unless object.nil?
883
+ end
884
+
885
+ def value_before_type_cast(object, method_name)
886
+ unless object.nil?
887
+ object.respond_to?(method_name + "_before_type_cast") ?
888
+ object.send(method_name + "_before_type_cast") :
889
+ object.send(method_name)
890
+ end
891
+ end
892
+
893
+ def check_box_checked?(value, checked_value)
894
+ case value
895
+ when TrueClass, FalseClass
896
+ value
897
+ when NilClass
898
+ false
899
+ when Integer
900
+ value != 0
901
+ when String
902
+ value == checked_value
903
+ when Array
904
+ value.include?(checked_value)
905
+ else
906
+ value.to_i != 0
907
+ end
908
+ end
909
+
910
+ def radio_button_checked?(value, checked_value)
911
+ value.to_s == checked_value.to_s
912
+ end
913
+ end
914
+
915
+ private
916
+ def add_default_name_and_id_for_value(tag_value, options)
917
+ unless tag_value.nil?
918
+ pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
919
+ specified_id = options["id"]
920
+ add_default_name_and_id(options)
921
+ options["id"] += "_#{pretty_tag_value}" unless specified_id
922
+ else
923
+ add_default_name_and_id(options)
924
+ end
925
+ end
926
+
927
+ def add_default_name_and_id(options)
928
+ if options.has_key?("index")
929
+ options["name"] ||= tag_name_with_index(options["index"])
930
+ options["id"] ||= tag_id_with_index(options["index"])
931
+ options.delete("index")
932
+ elsif defined?(@auto_index)
933
+ options["name"] ||= tag_name_with_index(@auto_index)
934
+ options["id"] ||= tag_id_with_index(@auto_index)
935
+ else
936
+ options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '')
937
+ options["id"] ||= tag_id
938
+ end
939
+ end
940
+
941
+ def tag_name
942
+ "#{@object_name}[#{sanitized_method_name}]"
943
+ end
944
+
945
+ def tag_name_with_index(index)
946
+ "#{@object_name}[#{index}][#{sanitized_method_name}]"
947
+ end
948
+
949
+ def tag_id
950
+ "#{sanitized_object_name}_#{sanitized_method_name}"
951
+ end
952
+
953
+ def tag_id_with_index(index)
954
+ "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
955
+ end
956
+
957
+ def sanitized_object_name
958
+ @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
959
+ end
960
+
961
+ def sanitized_method_name
962
+ @sanitized_method_name ||= @method_name.sub(/\?$/,"")
963
+ end
964
+ end
965
+
966
+ class FormBuilder #:nodoc:
967
+ # The methods which wrap a form helper call.
968
+ class_inheritable_accessor :field_helpers
969
+ self.field_helpers = (FormHelper.instance_methods - ['form_for'])
970
+
971
+ attr_accessor :object_name, :object, :options
972
+
973
+ def initialize(object_name, object, template, options, proc)
974
+ @nested_child_index = {}
975
+ @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
976
+ @default_options = @options ? @options.slice(:index) : {}
977
+ if @object_name.to_s.match(/\[\]$/)
978
+ if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
979
+ @auto_index = object.to_param
980
+ else
981
+ raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
982
+ end
983
+ end
984
+ end
985
+
986
+ (field_helpers - %w(label check_box radio_button fields_for hidden_field)).each do |selector|
987
+ src, line = <<-end_src, __LINE__ + 1
988
+ def #{selector}(method, options = {}) # def text_field(method, options = {})
989
+ @template.send( # @template.send(
990
+ #{selector.inspect}, # "text_field",
991
+ @object_name, # @object_name,
992
+ method, # method,
993
+ objectify_options(options)) # objectify_options(options))
994
+ end # end
995
+ end_src
996
+ class_eval src, __FILE__, line
997
+ end
998
+
999
+ def fields_for(record_or_name_or_array, *args, &block)
1000
+ if options.has_key?(:index)
1001
+ index = "[#{options[:index]}]"
1002
+ elsif defined?(@auto_index)
1003
+ self.object_name = @object_name.to_s.sub(/\[\]$/,"")
1004
+ index = "[#{@auto_index}]"
1005
+ else
1006
+ index = ""
1007
+ end
1008
+
1009
+ if options[:builder]
1010
+ args << {} unless args.last.is_a?(Hash)
1011
+ args.last[:builder] ||= options[:builder]
1012
+ end
1013
+
1014
+ case record_or_name_or_array
1015
+ when String, Symbol
1016
+ if nested_attributes_association?(record_or_name_or_array)
1017
+ return fields_for_with_nested_attributes(record_or_name_or_array, args, block)
1018
+ else
1019
+ name = "#{object_name}#{index}[#{record_or_name_or_array}]"
1020
+ end
1021
+ when Array
1022
+ object = record_or_name_or_array.last
1023
+ name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
1024
+ args.unshift(object)
1025
+ else
1026
+ object = record_or_name_or_array
1027
+ name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
1028
+ args.unshift(object)
1029
+ end
1030
+
1031
+ @template.fields_for(name, *args, &block)
1032
+ end
1033
+
1034
+ def label(method, text = nil, options = {})
1035
+ @template.label(@object_name, method, text, objectify_options(options))
1036
+ end
1037
+
1038
+ def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
1039
+ @template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value)
1040
+ end
1041
+
1042
+ def radio_button(method, tag_value, options = {})
1043
+ @template.radio_button(@object_name, method, tag_value, objectify_options(options))
1044
+ end
1045
+
1046
+ def hidden_field(method, options = {})
1047
+ @emitted_hidden_id = true if method == :id
1048
+ @template.hidden_field(@object_name, method, objectify_options(options))
1049
+ end
1050
+
1051
+ def error_message_on(method, *args)
1052
+ @template.error_message_on(@object || @object_name, method, *args)
1053
+ end
1054
+
1055
+ def error_messages(options = {})
1056
+ @template.error_messages_for(@object_name, objectify_options(options))
1057
+ end
1058
+
1059
+ def submit(value = "Save changes", options = {})
1060
+ @template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit"))
1061
+ end
1062
+
1063
+ def emitted_hidden_id?
1064
+ @emitted_hidden_id
1065
+ end
1066
+
1067
+ private
1068
+ def objectify_options(options)
1069
+ @default_options.merge(options.merge(:object => @object))
1070
+ end
1071
+
1072
+ def nested_attributes_association?(association_name)
1073
+ @object.respond_to?("#{association_name}_attributes=")
1074
+ end
1075
+
1076
+ def fields_for_with_nested_attributes(association_name, args, block)
1077
+ name = "#{object_name}[#{association_name}_attributes]"
1078
+ association = args.first
1079
+
1080
+ if association.respond_to?(:new_record?)
1081
+ association = [association] if @object.send(association_name).is_a?(Array)
1082
+ elsif !association.is_a?(Array)
1083
+ association = @object.send(association_name)
1084
+ end
1085
+
1086
+ if association.is_a?(Array)
1087
+ explicit_child_index = args.last[:child_index] if args.last.is_a?(Hash)
1088
+ association.map do |child|
1089
+ fields_for_nested_model("#{name}[#{explicit_child_index || nested_child_index(name)}]", child, args, block)
1090
+ end.join
1091
+ elsif association
1092
+ fields_for_nested_model(name, association, args, block)
1093
+ end
1094
+ end
1095
+
1096
+ def fields_for_nested_model(name, object, args, block)
1097
+ if object.new_record?
1098
+ @template.fields_for(name, object, *args, &block)
1099
+ else
1100
+ @template.fields_for(name, object, *args) do |builder|
1101
+ block.call(builder)
1102
+ @template.concat builder.hidden_field(:id) unless builder.emitted_hidden_id?
1103
+ end
1104
+ end
1105
+ end
1106
+
1107
+ def nested_child_index(name)
1108
+ @nested_child_index[name] ||= -1
1109
+ @nested_child_index[name] += 1
1110
+ end
1111
+ end
1112
+ end
1113
+
1114
+ class Base
1115
+ cattr_accessor :default_form_builder
1116
+ self.default_form_builder = ::ActionView::Helpers::FormBuilder
1117
+ end
1118
+ end