actionpack 2.2.3 → 2.3.2

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

Potentially problematic release.


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

Files changed (264) hide show
  1. data/CHANGELOG +433 -375
  2. data/MIT-LICENSE +1 -1
  3. data/README +21 -75
  4. data/Rakefile +1 -1
  5. data/lib/action_controller.rb +80 -43
  6. data/lib/action_controller/assertions/model_assertions.rb +1 -0
  7. data/lib/action_controller/assertions/response_assertions.rb +43 -16
  8. data/lib/action_controller/assertions/routing_assertions.rb +1 -1
  9. data/lib/action_controller/assertions/selector_assertions.rb +17 -12
  10. data/lib/action_controller/assertions/tag_assertions.rb +1 -4
  11. data/lib/action_controller/base.rb +153 -82
  12. data/lib/action_controller/benchmarking.rb +9 -9
  13. data/lib/action_controller/caching.rb +9 -11
  14. data/lib/action_controller/caching/actions.rb +11 -18
  15. data/lib/action_controller/caching/fragments.rb +28 -20
  16. data/lib/action_controller/caching/pages.rb +13 -15
  17. data/lib/action_controller/caching/sweeping.rb +2 -2
  18. data/lib/action_controller/cgi_ext.rb +0 -1
  19. data/lib/action_controller/cgi_ext/cookie.rb +2 -0
  20. data/lib/action_controller/cgi_process.rb +54 -162
  21. data/lib/action_controller/cookies.rb +13 -25
  22. data/lib/action_controller/dispatcher.rb +43 -122
  23. data/lib/action_controller/failsafe.rb +52 -0
  24. data/lib/action_controller/flash.rb +38 -47
  25. data/lib/action_controller/helpers.rb +13 -9
  26. data/lib/action_controller/http_authentication.rb +203 -23
  27. data/lib/action_controller/integration.rb +126 -70
  28. data/lib/action_controller/layout.rb +36 -39
  29. data/lib/action_controller/middleware_stack.rb +119 -0
  30. data/lib/action_controller/middlewares.rb +13 -0
  31. data/lib/action_controller/mime_responds.rb +19 -4
  32. data/lib/action_controller/mime_type.rb +8 -0
  33. data/lib/action_controller/params_parser.rb +71 -0
  34. data/lib/action_controller/performance_test.rb +0 -1
  35. data/lib/action_controller/polymorphic_routes.rb +36 -30
  36. data/lib/action_controller/reloader.rb +14 -0
  37. data/lib/action_controller/request.rb +107 -499
  38. data/lib/action_controller/request_forgery_protection.rb +7 -39
  39. data/lib/action_controller/rescue.rb +55 -35
  40. data/lib/action_controller/resources.rb +34 -31
  41. data/lib/action_controller/response.rb +99 -57
  42. data/lib/action_controller/rewindable_input.rb +28 -0
  43. data/lib/action_controller/routing.rb +7 -7
  44. data/lib/action_controller/routing/builder.rb +4 -1
  45. data/lib/action_controller/routing/optimisations.rb +1 -1
  46. data/lib/action_controller/routing/recognition_optimisation.rb +1 -2
  47. data/lib/action_controller/routing/route.rb +15 -5
  48. data/lib/action_controller/routing/route_set.rb +82 -35
  49. data/lib/action_controller/routing/segments.rb +35 -0
  50. data/lib/action_controller/session/abstract_store.rb +181 -0
  51. data/lib/action_controller/session/cookie_store.rb +197 -175
  52. data/lib/action_controller/session/mem_cache_store.rb +36 -83
  53. data/lib/action_controller/session_management.rb +26 -134
  54. data/lib/action_controller/streaming.rb +24 -7
  55. data/lib/action_controller/templates/rescues/diagnostics.erb +2 -2
  56. data/lib/action_controller/templates/rescues/template_error.erb +2 -2
  57. data/lib/action_controller/test_case.rb +87 -30
  58. data/lib/action_controller/test_process.rb +145 -104
  59. data/lib/action_controller/uploaded_file.rb +44 -0
  60. data/lib/action_controller/url_rewriter.rb +3 -6
  61. data/lib/action_controller/vendor/html-scanner.rb +16 -0
  62. data/lib/action_controller/vendor/html-scanner/html/selector.rb +1 -1
  63. data/lib/action_controller/vendor/rack-1.0/rack.rb +89 -0
  64. data/lib/action_controller/vendor/rack-1.0/rack/adapter/camping.rb +22 -0
  65. data/lib/action_controller/vendor/rack-1.0/rack/auth/abstract/handler.rb +37 -0
  66. data/lib/action_controller/vendor/rack-1.0/rack/auth/abstract/request.rb +37 -0
  67. data/lib/action_controller/vendor/rack-1.0/rack/auth/basic.rb +58 -0
  68. data/lib/action_controller/vendor/rack-1.0/rack/auth/digest/md5.rb +124 -0
  69. data/lib/action_controller/vendor/rack-1.0/rack/auth/digest/nonce.rb +51 -0
  70. data/lib/action_controller/vendor/rack-1.0/rack/auth/digest/params.rb +55 -0
  71. data/lib/action_controller/vendor/rack-1.0/rack/auth/digest/request.rb +40 -0
  72. data/lib/action_controller/vendor/rack-1.0/rack/auth/openid.rb +480 -0
  73. data/lib/action_controller/vendor/rack-1.0/rack/builder.rb +63 -0
  74. data/lib/action_controller/vendor/rack-1.0/rack/cascade.rb +36 -0
  75. data/lib/action_controller/vendor/rack-1.0/rack/chunked.rb +49 -0
  76. data/lib/action_controller/vendor/rack-1.0/rack/commonlogger.rb +61 -0
  77. data/lib/action_controller/vendor/rack-1.0/rack/conditionalget.rb +45 -0
  78. data/lib/action_controller/vendor/rack-1.0/rack/content_length.rb +29 -0
  79. data/lib/action_controller/vendor/rack-1.0/rack/content_type.rb +23 -0
  80. data/lib/action_controller/vendor/rack-1.0/rack/deflater.rb +85 -0
  81. data/lib/action_controller/vendor/rack-1.0/rack/directory.rb +153 -0
  82. data/lib/action_controller/vendor/rack-1.0/rack/file.rb +88 -0
  83. data/lib/action_controller/vendor/rack-1.0/rack/handler.rb +48 -0
  84. data/lib/action_controller/vendor/rack-1.0/rack/handler/cgi.rb +61 -0
  85. data/lib/action_controller/vendor/rack-1.0/rack/handler/evented_mongrel.rb +8 -0
  86. data/lib/action_controller/vendor/rack-1.0/rack/handler/fastcgi.rb +89 -0
  87. data/lib/action_controller/vendor/rack-1.0/rack/handler/lsws.rb +55 -0
  88. data/lib/action_controller/vendor/rack-1.0/rack/handler/mongrel.rb +84 -0
  89. data/lib/action_controller/vendor/rack-1.0/rack/handler/scgi.rb +59 -0
  90. data/lib/action_controller/vendor/rack-1.0/rack/handler/swiftiplied_mongrel.rb +8 -0
  91. data/lib/action_controller/vendor/rack-1.0/rack/handler/thin.rb +18 -0
  92. data/lib/action_controller/vendor/rack-1.0/rack/handler/webrick.rb +67 -0
  93. data/lib/action_controller/vendor/rack-1.0/rack/head.rb +19 -0
  94. data/lib/action_controller/vendor/rack-1.0/rack/lint.rb +462 -0
  95. data/lib/action_controller/vendor/rack-1.0/rack/lobster.rb +65 -0
  96. data/lib/action_controller/vendor/rack-1.0/rack/lock.rb +16 -0
  97. data/lib/action_controller/vendor/rack-1.0/rack/methodoverride.rb +27 -0
  98. data/lib/action_controller/vendor/rack-1.0/rack/mime.rb +204 -0
  99. data/lib/action_controller/vendor/rack-1.0/rack/mock.rb +160 -0
  100. data/lib/action_controller/vendor/rack-1.0/rack/recursive.rb +57 -0
  101. data/lib/action_controller/vendor/rack-1.0/rack/reloader.rb +64 -0
  102. data/lib/action_controller/vendor/rack-1.0/rack/request.rb +241 -0
  103. data/lib/action_controller/vendor/rack-1.0/rack/response.rb +179 -0
  104. data/lib/action_controller/vendor/rack-1.0/rack/session/abstract/id.rb +142 -0
  105. data/lib/action_controller/vendor/rack-1.0/rack/session/cookie.rb +91 -0
  106. data/lib/action_controller/vendor/rack-1.0/rack/session/memcache.rb +109 -0
  107. data/lib/action_controller/vendor/rack-1.0/rack/session/pool.rb +100 -0
  108. data/lib/action_controller/vendor/rack-1.0/rack/showexceptions.rb +349 -0
  109. data/lib/action_controller/vendor/rack-1.0/rack/showstatus.rb +106 -0
  110. data/lib/action_controller/vendor/rack-1.0/rack/static.rb +38 -0
  111. data/lib/action_controller/vendor/rack-1.0/rack/urlmap.rb +55 -0
  112. data/lib/action_controller/vendor/rack-1.0/rack/utils.rb +392 -0
  113. data/lib/action_controller/verification.rb +1 -1
  114. data/lib/action_pack.rb +1 -1
  115. data/lib/action_pack/version.rb +2 -2
  116. data/lib/action_view.rb +22 -17
  117. data/lib/action_view/base.rb +53 -79
  118. data/lib/action_view/erb/util.rb +38 -0
  119. data/lib/action_view/helpers.rb +24 -5
  120. data/lib/action_view/helpers/active_record_helper.rb +2 -2
  121. data/lib/action_view/helpers/asset_tag_helper.rb +81 -50
  122. data/lib/action_view/helpers/atom_feed_helper.rb +1 -1
  123. data/lib/action_view/helpers/benchmark_helper.rb +26 -5
  124. data/lib/action_view/helpers/date_helper.rb +82 -7
  125. data/lib/action_view/helpers/form_helper.rb +295 -64
  126. data/lib/action_view/helpers/form_options_helper.rb +160 -18
  127. data/lib/action_view/helpers/form_tag_helper.rb +2 -2
  128. data/lib/action_view/helpers/number_helper.rb +31 -18
  129. data/lib/action_view/helpers/prototype_helper.rb +2 -12
  130. data/lib/action_view/helpers/sanitize_helper.rb +0 -10
  131. data/lib/action_view/helpers/scriptaculous_helper.rb +1 -0
  132. data/lib/action_view/helpers/tag_helper.rb +3 -4
  133. data/lib/action_view/helpers/text_helper.rb +99 -122
  134. data/lib/action_view/helpers/translation_helper.rb +19 -1
  135. data/lib/action_view/helpers/url_helper.rb +25 -2
  136. data/lib/action_view/inline_template.rb +1 -1
  137. data/lib/action_view/locale/en.yml +19 -1
  138. data/lib/action_view/partials.rb +46 -9
  139. data/lib/action_view/paths.rb +28 -84
  140. data/lib/action_view/reloadable_template.rb +117 -0
  141. data/lib/action_view/renderable.rb +28 -35
  142. data/lib/action_view/renderable_partial.rb +3 -4
  143. data/lib/action_view/template.rb +172 -31
  144. data/lib/action_view/template_error.rb +8 -9
  145. data/lib/action_view/template_handler.rb +1 -1
  146. data/lib/action_view/template_handlers.rb +9 -6
  147. data/lib/action_view/template_handlers/erb.rb +2 -39
  148. data/lib/action_view/template_handlers/rjs.rb +1 -0
  149. data/lib/action_view/test_case.rb +27 -1
  150. data/test/abstract_unit.rb +23 -17
  151. data/test/active_record_unit.rb +5 -4
  152. data/test/activerecord/active_record_store_test.rb +139 -106
  153. data/test/activerecord/render_partial_with_record_identification_test.rb +5 -21
  154. data/test/controller/action_pack_assertions_test.rb +25 -23
  155. data/test/controller/addresses_render_test.rb +3 -6
  156. data/test/controller/assert_select_test.rb +83 -70
  157. data/test/controller/base_test.rb +11 -13
  158. data/test/controller/benchmark_test.rb +3 -3
  159. data/test/controller/caching_test.rb +34 -24
  160. data/test/controller/capture_test.rb +3 -6
  161. data/test/controller/content_type_test.rb +3 -6
  162. data/test/controller/cookie_test.rb +31 -66
  163. data/test/controller/deprecation/deprecated_base_methods_test.rb +9 -11
  164. data/test/controller/dispatcher_test.rb +23 -28
  165. data/test/controller/fake_models.rb +8 -0
  166. data/test/controller/filters_test.rb +6 -2
  167. data/test/controller/flash_test.rb +2 -6
  168. data/test/controller/helper_test.rb +15 -1
  169. data/test/controller/html-scanner/document_test.rb +1 -1
  170. data/test/controller/html-scanner/sanitizer_test.rb +1 -1
  171. data/test/controller/http_basic_authentication_test.rb +88 -0
  172. data/test/controller/http_digest_authentication_test.rb +178 -0
  173. data/test/controller/integration_test.rb +56 -52
  174. data/test/controller/layout_test.rb +46 -44
  175. data/test/controller/middleware_stack_test.rb +90 -0
  176. data/test/controller/mime_responds_test.rb +7 -11
  177. data/test/controller/mime_type_test.rb +9 -0
  178. data/test/controller/polymorphic_routes_test.rb +235 -151
  179. data/test/controller/rack_test.rb +52 -81
  180. data/test/controller/redirect_test.rb +6 -14
  181. data/test/controller/render_test.rb +273 -60
  182. data/test/controller/request/json_params_parsing_test.rb +45 -0
  183. data/test/controller/request/multipart_params_parsing_test.rb +223 -0
  184. data/test/controller/request/query_string_parsing_test.rb +120 -0
  185. data/test/controller/request/url_encoded_params_parsing_test.rb +184 -0
  186. data/test/controller/request/xml_params_parsing_test.rb +88 -0
  187. data/test/controller/request_forgery_protection_test.rb +17 -98
  188. data/test/controller/request_test.rb +45 -530
  189. data/test/controller/rescue_test.rb +45 -22
  190. data/test/controller/resources_test.rb +112 -37
  191. data/test/controller/routing_test.rb +1442 -1384
  192. data/test/controller/selector_test.rb +3 -3
  193. data/test/controller/send_file_test.rb +30 -3
  194. data/test/controller/session/cookie_store_test.rb +169 -240
  195. data/test/controller/session/mem_cache_store_test.rb +94 -148
  196. data/test/controller/session/test_session_test.rb +58 -0
  197. data/test/controller/test_test.rb +32 -13
  198. data/test/controller/url_rewriter_test.rb +54 -4
  199. data/test/controller/verification_test.rb +1 -1
  200. data/test/controller/view_paths_test.rb +15 -15
  201. data/test/controller/webservice_test.rb +178 -147
  202. data/test/fixtures/alternate_helpers/foo_helper.rb +3 -0
  203. data/test/fixtures/layout_tests/alt/layouts/alt.rhtml +0 -0
  204. data/test/fixtures/layouts/default_html.html.erb +1 -0
  205. data/test/fixtures/layouts/xhr.html.erb +2 -0
  206. data/test/fixtures/multipart/empty +10 -0
  207. data/test/fixtures/multipart/hello.txt +1 -0
  208. data/test/fixtures/multipart/none +9 -0
  209. data/test/fixtures/public/500.da.html +1 -0
  210. data/test/fixtures/quiz/questions/_question.html.erb +1 -0
  211. data/test/fixtures/replies.yml +1 -1
  212. data/test/fixtures/test/_one.html.erb +1 -0
  213. data/test/fixtures/test/_two.html.erb +1 -0
  214. data/test/fixtures/test/dont_pick_me +1 -0
  215. data/test/fixtures/test/hello.builder +1 -1
  216. data/test/fixtures/test/hello_world.da.html.erb +1 -0
  217. data/test/fixtures/test/hello_world.erb~ +1 -0
  218. data/test/fixtures/test/hello_world.pt-BR.html.erb +1 -0
  219. data/test/fixtures/test/malformed/malformed.en.html.erb~ +1 -0
  220. data/test/fixtures/test/malformed/malformed.erb~ +1 -0
  221. data/test/fixtures/test/malformed/malformed.html.erb~ +1 -0
  222. data/test/fixtures/test/render_explicit_html_template.js.rjs +1 -0
  223. data/test/fixtures/test/render_implicit_html_template.js.rjs +1 -0
  224. data/test/fixtures/test/render_implicit_html_template_from_xhr_request.da.html.erb +1 -0
  225. data/test/fixtures/test/render_implicit_html_template_from_xhr_request.html.erb +1 -0
  226. data/test/fixtures/test/render_implicit_js_template_without_layout.js.erb +1 -0
  227. data/test/fixtures/test/utf8.html.erb +2 -0
  228. data/test/template/active_record_helper_i18n_test.rb +31 -33
  229. data/test/template/active_record_helper_test.rb +34 -0
  230. data/test/template/asset_tag_helper_test.rb +52 -14
  231. data/test/template/atom_feed_helper_test.rb +3 -5
  232. data/test/template/benchmark_helper_test.rb +50 -24
  233. data/test/template/compiled_templates_test.rb +177 -33
  234. data/test/template/date_helper_i18n_test.rb +88 -81
  235. data/test/template/date_helper_test.rb +427 -43
  236. data/test/template/form_helper_test.rb +243 -44
  237. data/test/template/form_options_helper_test.rb +631 -565
  238. data/test/template/form_tag_helper_test.rb +9 -2
  239. data/test/template/javascript_helper_test.rb +0 -5
  240. data/test/template/number_helper_i18n_test.rb +60 -48
  241. data/test/template/number_helper_test.rb +1 -0
  242. data/test/template/render_test.rb +117 -35
  243. data/test/template/test_test.rb +4 -6
  244. data/test/template/text_helper_test.rb +129 -50
  245. data/test/template/translation_helper_test.rb +23 -19
  246. data/test/template/url_helper_test.rb +35 -2
  247. data/test/view/test_case_test.rb +8 -0
  248. metadata +197 -23
  249. data/lib/action_controller/assertions.rb +0 -69
  250. data/lib/action_controller/caching/sql_cache.rb +0 -18
  251. data/lib/action_controller/cgi_ext/session.rb +0 -53
  252. data/lib/action_controller/components.rb +0 -169
  253. data/lib/action_controller/rack_process.rb +0 -297
  254. data/lib/action_controller/request_profiler.rb +0 -169
  255. data/lib/action_controller/session/active_record_store.rb +0 -340
  256. data/lib/action_controller/session/drb_server.rb +0 -32
  257. data/lib/action_controller/session/drb_store.rb +0 -35
  258. data/test/controller/cgi_test.rb +0 -269
  259. data/test/controller/components_test.rb +0 -156
  260. data/test/controller/http_authentication_test.rb +0 -54
  261. data/test/controller/integration_upload_test.rb +0 -43
  262. data/test/controller/session_fixation_test.rb +0 -89
  263. data/test/controller/session_management_test.rb +0 -178
  264. data/test/fixtures/test/hello_world.js +0 -1
@@ -30,7 +30,7 @@ module ActionView
30
30
  # app/views/posts/index.atom.builder:
31
31
  # atom_feed do |feed|
32
32
  # feed.title("My great blog!")
33
- # feed.updated((@posts.first.created_at))
33
+ # feed.updated(@posts.first.created_at)
34
34
  #
35
35
  # for post in @posts
36
36
  # feed.entry(post) do |entry|
@@ -18,16 +18,37 @@ module ActionView
18
18
  # That would add something like "Process data files (345.2ms)" to the log,
19
19
  # which you can then use to compare timings when optimizing your code.
20
20
  #
21
- # You may give an optional logger level as the second argument
21
+ # You may give an optional logger level as the :level option.
22
22
  # (:debug, :info, :warn, :error); the default value is :info.
23
- def benchmark(message = "Benchmarking", level = :info)
23
+ #
24
+ # <% benchmark "Low-level files", :level => :debug do %>
25
+ # <%= lowlevel_files_operation %>
26
+ # <% end %>
27
+ #
28
+ # Finally, you can pass true as the third argument to silence all log activity
29
+ # inside the block. This is great for boiling down a noisy block to just a single statement:
30
+ #
31
+ # <% benchmark "Process data files", :level => :info, :silence => true do %>
32
+ # <%= expensive_and_chatty_files_operation %>
33
+ # <% end %>
34
+ def benchmark(message = "Benchmarking", options = {})
24
35
  if controller.logger
25
- seconds = Benchmark.realtime { yield }
26
- controller.logger.send(level, "#{message} (#{'%.1f' % (seconds * 1000)}ms)")
36
+ if options.is_a?(Symbol)
37
+ ActiveSupport::Deprecation.warn("use benchmark('#{message}', :level => :#{options}) instead", caller)
38
+ options = { :level => options, :silence => false }
39
+ else
40
+ options.assert_valid_keys(:level, :silence)
41
+ options[:level] ||= :info
42
+ end
43
+
44
+ result = nil
45
+ ms = Benchmark.ms { result = options[:silence] ? controller.logger.silence { yield } : yield }
46
+ controller.logger.send(options[:level], '%s (%.1fms)' % [ message, ms ])
47
+ result
27
48
  else
28
49
  yield
29
50
  end
30
51
  end
31
52
  end
32
53
  end
33
- end
54
+ end
@@ -111,7 +111,7 @@ module ActionView
111
111
  #
112
112
  # ==== Options
113
113
  # * <tt>:use_month_numbers</tt> - Set to true if you want to use month numbers rather than month names (e.g.
114
- # "2" instead of "February").
114
+ # "2" instead of "February").
115
115
  # * <tt>:use_short_month</tt> - Set to true if you want to use the abbreviated month name instead of the full
116
116
  # name (e.g. "Feb" instead of "February").
117
117
  # * <tt>:add_month_number</tt> - Set to true if you want to show both, the month's number and name (e.g.
@@ -136,6 +136,10 @@ module ActionView
136
136
  # dates.
137
137
  # * <tt>:default</tt> - Set a default date if the affected date isn't set or is nil.
138
138
  # * <tt>:disabled</tt> - Set to true if you want show the select fields as disabled.
139
+ # * <tt>:prompt</tt> - Set to true (for a generic prompt), a prompt string or a hash of prompt strings
140
+ # for <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt> and <tt>:second</tt>.
141
+ # Setting this option prepends a select option with a generic prompt (Day, Month, Year, Hour, Minute, Seconds)
142
+ # or the given prompt string.
139
143
  #
140
144
  # If anything is passed in the +html_options+ hash it will be applied to every select tag in the set.
141
145
  #
@@ -171,6 +175,9 @@ module ActionView
171
175
  # # that will have a default day of 20.
172
176
  # date_select("credit_card", "bill_due", :default => { :day => 20 })
173
177
  #
178
+ # # Generates a date select with custom prompts
179
+ # date_select("post", "written_on", :prompt => { :day => 'Select day', :month => 'Select month', :year => 'Select year' })
180
+ #
174
181
  # The selects are prepared for multi-parameter assignment to an Active Record object.
175
182
  #
176
183
  # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
@@ -210,6 +217,11 @@ module ActionView
210
217
  # # You can set the :minute_step to 15 which will give you: 00, 15, 30 and 45.
211
218
  # time_select 'game', 'game_time', {:minute_step => 15}
212
219
  #
220
+ # # Creates a time select tag with a custom prompt. Use :prompt => true for generic prompts.
221
+ # time_select("post", "written_on", :prompt => {:hour => 'Choose hour', :minute => 'Choose minute', :second => 'Choose seconds'})
222
+ # time_select("post", "written_on", :prompt => {:hour => true}) # generic prompt for hours
223
+ # time_select("post", "written_on", :prompt => true) # generic prompts for all
224
+ #
213
225
  # The selects are prepared for multi-parameter assignment to an Active Record object.
214
226
  #
215
227
  # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
@@ -241,6 +253,11 @@ module ActionView
241
253
  # # as the written_on attribute.
242
254
  # datetime_select("post", "written_on", :discard_type => true)
243
255
  #
256
+ # # Generates a datetime select with a custom prompt. Use :prompt=>true for generic prompts.
257
+ # datetime_select("post", "written_on", :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
258
+ # datetime_select("post", "written_on", :prompt => {:hour => true}) # generic prompt for hours
259
+ # datetime_select("post", "written_on", :prompt => true) # generic prompts for all
260
+ #
244
261
  # The selects are prepared for multi-parameter assignment to an Active Record object.
245
262
  def datetime_select(object_name, method, options = {}, html_options = {})
246
263
  InstanceTag.new(object_name, method, self, options.delete(:object)).to_datetime_select_tag(options, html_options)
@@ -285,6 +302,11 @@ module ActionView
285
302
  # # prefixed with 'payday' rather than 'date'
286
303
  # select_datetime(my_date_time, :prefix => 'payday')
287
304
  #
305
+ # # Generates a datetime select with a custom prompt. Use :prompt=>true for generic prompts.
306
+ # select_datetime(my_date_time, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
307
+ # select_datetime(my_date_time, :prompt => {:hour => true}) # generic prompt for hours
308
+ # select_datetime(my_date_time, :prompt => true) # generic prompts for all
309
+ #
288
310
  def select_datetime(datetime = Time.current, options = {}, html_options = {})
289
311
  DateTimeSelector.new(datetime, options, html_options).select_datetime
290
312
  end
@@ -321,6 +343,11 @@ module ActionView
321
343
  # # prefixed with 'payday' rather than 'date'
322
344
  # select_date(my_date, :prefix => 'payday')
323
345
  #
346
+ # # Generates a date select with a custom prompt. Use :prompt=>true for generic prompts.
347
+ # select_date(my_date, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
348
+ # select_date(my_date, :prompt => {:hour => true}) # generic prompt for hours
349
+ # select_date(my_date, :prompt => true) # generic prompts for all
350
+ #
324
351
  def select_date(date = Date.current, options = {}, html_options = {})
325
352
  DateTimeSelector.new(date, options, html_options).select_date
326
353
  end
@@ -352,6 +379,11 @@ module ActionView
352
379
  # # separated by ':' and includes an input for seconds
353
380
  # select_time(my_time, :time_separator => ':', :include_seconds => true)
354
381
  #
382
+ # # Generates a time select with a custom prompt. Use :prompt=>true for generic prompts.
383
+ # select_time(my_time, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
384
+ # select_time(my_time, :prompt => {:hour => true}) # generic prompt for hours
385
+ # select_time(my_time, :prompt => true) # generic prompts for all
386
+ #
355
387
  def select_time(datetime = Time.current, options = {}, html_options = {})
356
388
  DateTimeSelector.new(datetime, options, html_options).select_time
357
389
  end
@@ -373,6 +405,10 @@ module ActionView
373
405
  # # that is named 'interval' rather than 'second'
374
406
  # select_second(my_time, :field_name => 'interval')
375
407
  #
408
+ # # Generates a select field for seconds with a custom prompt. Use :prompt=>true for a
409
+ # # generic prompt.
410
+ # select_minute(14, :prompt => 'Choose seconds')
411
+ #
376
412
  def select_second(datetime, options = {}, html_options = {})
377
413
  DateTimeSelector.new(datetime, options, html_options).select_second
378
414
  end
@@ -395,6 +431,10 @@ module ActionView
395
431
  # # that is named 'stride' rather than 'second'
396
432
  # select_minute(my_time, :field_name => 'stride')
397
433
  #
434
+ # # Generates a select field for minutes with a custom prompt. Use :prompt=>true for a
435
+ # # generic prompt.
436
+ # select_minute(14, :prompt => 'Choose minutes')
437
+ #
398
438
  def select_minute(datetime, options = {}, html_options = {})
399
439
  DateTimeSelector.new(datetime, options, html_options).select_minute
400
440
  end
@@ -416,6 +456,10 @@ module ActionView
416
456
  # # that is named 'stride' rather than 'second'
417
457
  # select_hour(my_time, :field_name => 'stride')
418
458
  #
459
+ # # Generates a select field for hours with a custom prompt. Use :prompt => true for a
460
+ # # generic prompt.
461
+ # select_hour(13, :prompt =>'Choose hour')
462
+ #
419
463
  def select_hour(datetime, options = {}, html_options = {})
420
464
  DateTimeSelector.new(datetime, options, html_options).select_hour
421
465
  end
@@ -437,6 +481,10 @@ module ActionView
437
481
  # # that is named 'due' rather than 'day'
438
482
  # select_day(my_time, :field_name => 'due')
439
483
  #
484
+ # # Generates a select field for days with a custom prompt. Use :prompt => true for a
485
+ # # generic prompt.
486
+ # select_day(5, :prompt => 'Choose day')
487
+ #
440
488
  def select_day(date, options = {}, html_options = {})
441
489
  DateTimeSelector.new(date, options, html_options).select_day
442
490
  end
@@ -475,6 +523,10 @@ module ActionView
475
523
  # # will use keys like "Januar", "Marts."
476
524
  # select_month(Date.today, :use_month_names => %w(Januar Februar Marts ...))
477
525
  #
526
+ # # Generates a select field for months with a custom prompt. Use :prompt => true for a
527
+ # # generic prompt.
528
+ # select_month(14, :prompt => 'Choose month')
529
+ #
478
530
  def select_month(date, options = {}, html_options = {})
479
531
  DateTimeSelector.new(date, options, html_options).select_month
480
532
  end
@@ -502,6 +554,10 @@ module ActionView
502
554
  # # has ascending year values
503
555
  # select_year(2006, :start_year => 2000, :end_year => 2010)
504
556
  #
557
+ # # Generates a select field for years with a custom prompt. Use :prompt => true for a
558
+ # # generic prompt.
559
+ # select_year(14, :prompt => 'Choose year')
560
+ #
505
561
  def select_year(date, options = {}, html_options = {})
506
562
  DateTimeSelector.new(date, options, html_options).select_year
507
563
  end
@@ -764,11 +820,30 @@ module ActionView
764
820
 
765
821
  select_html = "\n"
766
822
  select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank]
823
+ select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt]
767
824
  select_html << select_options_as_html.to_s
768
825
 
769
826
  content_tag(:select, select_html, select_options) + "\n"
770
827
  end
771
828
 
829
+ # Builds a prompt option tag with supplied options or from default options
830
+ # prompt_option_tag(:month, :prompt => 'Select month')
831
+ # => "<option value="">Select month</option>"
832
+ def prompt_option_tag(type, options)
833
+ default_options = {:year => false, :month => false, :day => false, :hour => false, :minute => false, :second => false}
834
+
835
+ case options
836
+ when Hash
837
+ prompt = default_options.merge(options)[type.to_sym]
838
+ when String
839
+ prompt = options
840
+ else
841
+ prompt = I18n.translate(('datetime.prompts.' + type.to_s).to_sym, :locale => @options[:locale])
842
+ end
843
+
844
+ prompt ? content_tag(:option, prompt, :value => '') : ''
845
+ end
846
+
772
847
  # Builds hidden input tag for date part and value
773
848
  # build_hidden(:year, 2008)
774
849
  # => "<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="2008" />"
@@ -785,7 +860,7 @@ module ActionView
785
860
  # => post[written_on(1i)]
786
861
  def input_name_from_type(type)
787
862
  prefix = @options[:prefix] || ActionView::Helpers::DateTimeSelector::DEFAULT_PREFIX
788
- prefix += "[#{@options[:index]}]" if @options[:index]
863
+ prefix += "[#{@options[:index]}]" if @options.has_key?(:index)
789
864
 
790
865
  field_name = @options[:field_name] || type
791
866
  if @options[:include_position]
@@ -848,7 +923,7 @@ module ActionView
848
923
  options[:field_name] = @method_name
849
924
  options[:include_position] = true
850
925
  options[:prefix] ||= @object_name
851
- options[:index] ||= @auto_index
926
+ options[:index] = @auto_index if @auto_index && !options.has_key?(:index)
852
927
  options[:datetime_separator] ||= ' &mdash; '
853
928
  options[:time_separator] ||= ' : '
854
929
 
@@ -856,7 +931,7 @@ module ActionView
856
931
  end
857
932
 
858
933
  def default_datetime(options)
859
- return if options[:include_blank]
934
+ return if options[:include_blank] || options[:prompt]
860
935
 
861
936
  case options[:default]
862
937
  when nil
@@ -886,15 +961,15 @@ module ActionView
886
961
 
887
962
  class FormBuilder
888
963
  def date_select(method, options = {}, html_options = {})
889
- @template.date_select(@object_name, method, options.merge(:object => @object), html_options)
964
+ @template.date_select(@object_name, method, objectify_options(options), html_options)
890
965
  end
891
966
 
892
967
  def time_select(method, options = {}, html_options = {})
893
- @template.time_select(@object_name, method, options.merge(:object => @object), html_options)
968
+ @template.time_select(@object_name, method, objectify_options(options), html_options)
894
969
  end
895
970
 
896
971
  def datetime_select(method, options = {}, html_options = {})
897
- @template.datetime_select(@object_name, method, options.merge(:object => @object), html_options)
972
+ @template.datetime_select(@object_name, method, objectify_options(options), html_options)
898
973
  end
899
974
  end
900
975
  end
@@ -5,17 +5,24 @@ require 'action_view/helpers/form_tag_helper'
5
5
 
6
6
  module ActionView
7
7
  module Helpers
8
- # Form helpers are designed to make working with models much easier compared to using just standard HTML
9
- # elements by providing a set of methods for creating forms based on your models. This helper generates the HTML
10
- # for forms, providing a method for each sort of input (e.g., text, password, select, and so on). When the form
11
- # is submitted (i.e., when the user hits the submit button or <tt>form.submit</tt> is called via JavaScript), the form inputs will be bundled into the <tt>params</tt> object and passed back to the controller.
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.
12
16
  #
13
- # There are two types of form helpers: those that specifically work with model attributes and those that don't.
14
- # This helper deals with those that work with model attributes; to see an example of form helpers that don't work
15
- # with model attributes, check the ActionView::Helpers::FormTagHelper documentation.
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.
16
22
  #
17
- # The core method of this helper, form_for, gives you the ability to create a form for a model instance;
18
- # for example, let's say that you have a model <tt>Person</tt> and want to create a new instance of it:
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:
19
26
  #
20
27
  # # Note: a @person variable will have been created in the controller.
21
28
  # # For example: @person = Person.new
@@ -40,17 +47,22 @@ module ActionView
40
47
  # <%= submit_tag 'Create' %>
41
48
  # <% end %>
42
49
  #
43
- # This example will render the <tt>people/_form</tt> partial, setting a local variable called <tt>form</tt> which references the yielded FormBuilder.
44
- #
45
- # The <tt>params</tt> object created when this form is submitted would look like:
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:
46
54
  #
47
55
  # {"action"=>"create", "controller"=>"persons", "person"=>{"first_name"=>"William", "last_name"=>"Smith"}}
48
56
  #
49
- # The params hash has a nested <tt>person</tt> value, which can therefore be accessed with <tt>params[:person]</tt> in the controller.
50
- # If were editing/updating an instance (e.g., <tt>Person.find(1)</tt> rather than <tt>Person.new</tt> in the controller), the objects
51
- # attribute values are filled into the form (e.g., the <tt>person_first_name</tt> field would have that person's first name in it).
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).
52
63
  #
53
- # If the object name contains square brackets the id for the object will be inserted. For example:
64
+ # If the object name contains square brackets the id for the object will be
65
+ # inserted. For example:
54
66
  #
55
67
  # <%= text_field "person[]", "name" %>
56
68
  #
@@ -58,8 +70,10 @@ module ActionView
58
70
  #
59
71
  # <input type="text" id="person_<%= @person.id %>_name" name="person[<%= @person.id %>][name]" value="<%= @person.name %>" />
60
72
  #
61
- # If the helper is being used to generate a repetitive sequence of similar form elements, for example in a partial
62
- # used by <tt>render_collection_of_partials</tt>, the <tt>index</tt> option may come in handy. Example:
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:
63
77
  #
64
78
  # <%= text_field "person", "name", "index" => 1 %>
65
79
  #
@@ -67,14 +81,17 @@ module ActionView
67
81
  #
68
82
  # <input type="text" id="person_1_name" name="person[1][name]" value="<%= @person.name %>" />
69
83
  #
70
- # An <tt>index</tt> option may also be passed to <tt>form_for</tt> and <tt>fields_for</tt>. This automatically applies
71
- # the <tt>index</tt> to all the nested fields.
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.
72
87
  #
73
- # There are also methods for helping to build form tags in link:classes/ActionView/Helpers/FormOptionsHelper.html,
74
- # link:classes/ActionView/Helpers/DateHelper.html, and link:classes/ActionView/Helpers/ActiveRecordHelper.html
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
75
92
  module FormHelper
76
- # Creates a form and a scope around a specific model object that is used as
77
- # a base for questioning about values for the fields.
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.
78
95
  #
79
96
  # Rails provides succinct resource-oriented form generation with +form_for+
80
97
  # like this:
@@ -86,13 +103,15 @@ module ActionView
86
103
  # <%= f.text_field :author %><br />
87
104
  # <% end %>
88
105
  #
89
- # There, +form_for+ is able to generate the rest of RESTful form parameters
90
- # based on introspection on the record, but to understand what it does we
91
- # need to dig first into the alternative generic usage it is based upon.
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.
92
110
  #
93
111
  # === Generic form_for
94
112
  #
95
- # The generic way to call +form_for+ yields a form builder around a model:
113
+ # The generic way to call +form_for+ yields a form builder around a
114
+ # model:
96
115
  #
97
116
  # <% form_for :person, :url => { :action => "update" } do |f| %>
98
117
  # <%= f.error_messages %>
@@ -103,8 +122,8 @@ module ActionView
103
122
  # <% end %>
104
123
  #
105
124
  # There, the first argument is a symbol or string with the name of the
106
- # object the form is about, and also the name of the instance variable the
107
- # object is stored in.
125
+ # object the form is about, and also the name of the instance variable
126
+ # the object is stored in.
108
127
  #
109
128
  # The form builder acts as a regular form helper that somehow carries the
110
129
  # model. Thus, the idea is that
@@ -137,17 +156,18 @@ module ActionView
137
156
  # In any of its variants, the rightmost argument to +form_for+ is an
138
157
  # optional hash of options:
139
158
  #
140
- # * <tt>:url</tt> - The URL the form is submitted to. It takes the same fields
141
- # you pass to +url_for+ or +link_to+. In particular you may pass here a
142
- # named route directly as well. Defaults to the current action.
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.
143
162
  # * <tt>:html</tt> - Optional HTML attributes for the form tag.
144
163
  #
145
- # Worth noting is that the +form_for+ tag is called in a ERb evaluation block,
146
- # not an ERb output block. So that's <tt><% %></tt>, not <tt><%= %></tt>.
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>.
147
167
  #
148
168
  # Also note that +form_for+ doesn't create an exclusive scope. It's still
149
- # possible to use both the stand-alone FormHelper methods and methods from
150
- # FormTagHelper. For example:
169
+ # possible to use both the stand-alone FormHelper methods and methods
170
+ # from FormTagHelper. For example:
151
171
  #
152
172
  # <% form_for :person, @person, :url => { :action => "update" } do |f| %>
153
173
  # First name: <%= f.text_field :first_name %>
@@ -156,16 +176,16 @@ module ActionView
156
176
  # Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %>
157
177
  # <% end %>
158
178
  #
159
- # This also works for the methods in FormOptionHelper and DateHelper that are
160
- # designed to work with an object as base, like FormOptionHelper#collection_select
161
- # and DateHelper#datetime_select.
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.
162
182
  #
163
183
  # === Resource-oriented style
164
184
  #
165
- # As we said above, in addition to manually configuring the +form_for+ call,
166
- # you can rely on automated resource identification, which will use the conventions
167
- # and named routes of that approach. This is the preferred way to use +form_for+
168
- # nowadays.
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.
169
189
  #
170
190
  # For example, if <tt>@post</tt> is an existing record you want to edit
171
191
  #
@@ -205,8 +225,10 @@ module ActionView
205
225
  #
206
226
  # === Customized form builders
207
227
  #
208
- # You can also build forms using a customized FormBuilder class. Subclass FormBuilder and override or define some more helpers,
209
- # then use your custom builder. For example, let's say you made a helper to automatically add labels to form inputs.
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.
210
232
  #
211
233
  # <% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %>
212
234
  # <%= f.text_field :first_name %>
@@ -219,16 +241,23 @@ module ActionView
219
241
  #
220
242
  # <%= render :partial => f %>
221
243
  #
222
- # The rendered template is <tt>people/_labelling_form</tt> and the local variable referencing the form builder is called <tt>labelling_form</tt>.
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.
223
250
  #
224
- # In many cases you will want to wrap the above in another helper, so you could do something like the following:
251
+ # In many cases you will want to wrap the above in another helper, so you
252
+ # could do something like the following:
225
253
  #
226
254
  # def labelled_form_for(record_or_name_or_array, *args, &proc)
227
255
  # options = args.extract_options!
228
256
  # form_for(record_or_name_or_array, *(args << options.merge(:builder => LabellingFormBuilder)), &proc)
229
257
  # end
230
258
  #
231
- # If you don't need to attach a form to a model instance, then check out FormTagHelper#form_tag.
259
+ # If you don't need to attach a form to a model instance, then check out
260
+ # FormTagHelper#form_tag.
232
261
  def form_for(record_or_name_or_array, *args, &proc)
233
262
  raise ArgumentError, "Missing block" unless block_given?
234
263
 
@@ -269,10 +298,12 @@ module ActionView
269
298
  options[:url] ||= polymorphic_path(object_or_array)
270
299
  end
271
300
 
272
- # Creates a scope around a specific model object like form_for, but doesn't create the form tags themselves. This makes
273
- # fields_for suitable for specifying additional model objects in the same form:
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
274
306
  #
275
- # ==== Examples
276
307
  # <% form_for @person, :url => { :action => "update" } do |person_form| %>
277
308
  # First name: <%= person_form.text_field :first_name %>
278
309
  # Last name : <%= person_form.text_field :last_name %>
@@ -282,20 +313,166 @@ module ActionView
282
313
  # <% end %>
283
314
  # <% end %>
284
315
  #
285
- # ...or if you have an object that needs to be represented as a different parameter, like a Client that acts as a Person:
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:
286
318
  #
287
319
  # <% fields_for :person, @client do |permission_fields| %>
288
320
  # Admin?: <%= permission_fields.check_box :admin %>
289
321
  # <% end %>
290
322
  #
291
- # ...or if you don't have an object, just a name of the parameter
323
+ # ...or if you don't have an object, just a name of the parameter:
292
324
  #
293
325
  # <% fields_for :person do |permission_fields| %>
294
326
  # Admin?: <%= permission_fields.check_box :admin %>
295
327
  # <% end %>
296
328
  #
297
- # Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base,
298
- # like FormOptionHelper#collection_select and DateHelper#datetime_select.
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
+ # When projects is already an association on Person you can use
449
+ # +accepts_nested_attributes_for+ to define the writer method for you:
450
+ #
451
+ # class Person < ActiveRecord::Base
452
+ # has_many :projects
453
+ # accepts_nested_attributes_for :projects
454
+ # end
455
+ #
456
+ # If you want to destroy any of the associated models through the
457
+ # form, you have to enable it first using the <tt>:allow_destroy</tt>
458
+ # option for +accepts_nested_attributes_for+:
459
+ #
460
+ # class Person < ActiveRecord::Base
461
+ # has_many :projects
462
+ # accepts_nested_attributes_for :projects, :allow_destroy => true
463
+ # end
464
+ #
465
+ # This will allow you to specify which models to destroy in the
466
+ # attributes hash by adding a form element for the <tt>_delete</tt>
467
+ # parameter with a value that evaluates to +true+
468
+ # (eg. 1, '1', true, or 'true'):
469
+ #
470
+ # <% form_for @person, :url => { :action => "update" } do |person_form| %>
471
+ # ...
472
+ # <% person_form.fields_for :projects do |project_fields| %>
473
+ # Delete: <%= project_fields.check_box :_delete %>
474
+ # <% end %>
475
+ # <% end %>
299
476
  def fields_for(record_or_name_or_array, *args, &block)
300
477
  raise ArgumentError, "Missing block" unless block_given?
301
478
  options = args.extract_options!
@@ -498,8 +675,10 @@ module ActionView
498
675
 
499
676
  # Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
500
677
  # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the
501
- # radio button will be checked. Additional options on the input tag can be passed as a
502
- # hash with +options+.
678
+ # radio button will be checked.
679
+ #
680
+ # To force the radio button to be checked pass <tt>:checked => true</tt> in the
681
+ # +options+ hash. You may pass HTML options there as well.
503
682
  #
504
683
  # ==== Examples
505
684
  # # Let's say that @post.category returns "rails":
@@ -605,7 +784,9 @@ module ActionView
605
784
  end
606
785
  options["checked"] = "checked" if checked
607
786
  add_default_name_and_id(options)
608
- tag("input", options) << tag("input", "name" => options["name"], "type" => "hidden", "value" => options['disabled'] && checked ? checked_value : unchecked_value)
787
+ hidden = tag("input", "name" => options["name"], "type" => "hidden", "value" => options['disabled'] && checked ? checked_value : unchecked_value)
788
+ checkbox = tag("input", options)
789
+ hidden + checkbox
609
790
  end
610
791
 
611
792
  def to_boolean_select_tag(options = {})
@@ -737,9 +918,13 @@ module ActionView
737
918
 
738
919
  (field_helpers - %w(label check_box radio_button fields_for)).each do |selector|
739
920
  src = <<-end_src
740
- def #{selector}(method, options = {})
741
- @template.send(#{selector.inspect}, @object_name, method, objectify_options(options))
742
- end
921
+ def #{selector}(method, options = {}) # def text_field(method, options = {})
922
+ @template.send( # @template.send(
923
+ #{selector.inspect}, # "text_field",
924
+ @object_name, # @object_name,
925
+ method, # method,
926
+ objectify_options(options)) # objectify_options(options))
927
+ end # end
743
928
  end_src
744
929
  class_eval src, __FILE__, __LINE__
745
930
  end
@@ -754,9 +939,18 @@ module ActionView
754
939
  index = ""
755
940
  end
756
941
 
942
+ if options[:builder]
943
+ args << {} unless args.last.is_a?(Hash)
944
+ args.last[:builder] ||= options[:builder]
945
+ end
946
+
757
947
  case record_or_name_or_array
758
948
  when String, Symbol
759
- name = "#{object_name}#{index}[#{record_or_name_or_array}]"
949
+ if nested_attributes_association?(record_or_name_or_array)
950
+ return fields_for_with_nested_attributes(record_or_name_or_array, args, block)
951
+ else
952
+ name = "#{object_name}#{index}[#{record_or_name_or_array}]"
953
+ end
760
954
  when Array
761
955
  object = record_or_name_or_array.last
762
956
  name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
@@ -798,6 +992,43 @@ module ActionView
798
992
  def objectify_options(options)
799
993
  @default_options.merge(options.merge(:object => @object))
800
994
  end
995
+
996
+ def nested_attributes_association?(association_name)
997
+ @object.respond_to?("#{association_name}_attributes=")
998
+ end
999
+
1000
+ def fields_for_with_nested_attributes(association_name, args, block)
1001
+ name = "#{object_name}[#{association_name}_attributes]"
1002
+ association = @object.send(association_name)
1003
+ explicit_object = args.first if args.first.respond_to?(:new_record?)
1004
+
1005
+ if association.is_a?(Array)
1006
+ children = explicit_object ? [explicit_object] : association
1007
+ explicit_child_index = args.last[:child_index] if args.last.is_a?(Hash)
1008
+
1009
+ children.map do |child|
1010
+ fields_for_nested_model("#{name}[#{explicit_child_index || nested_child_index}]", child, args, block)
1011
+ end.join
1012
+ else
1013
+ fields_for_nested_model(name, explicit_object || association, args, block)
1014
+ end
1015
+ end
1016
+
1017
+ def fields_for_nested_model(name, object, args, block)
1018
+ if object.new_record?
1019
+ @template.fields_for(name, object, *args, &block)
1020
+ else
1021
+ @template.fields_for(name, object, *args) do |builder|
1022
+ @template.concat builder.hidden_field(:id)
1023
+ block.call(builder)
1024
+ end
1025
+ end
1026
+ end
1027
+
1028
+ def nested_child_index
1029
+ @nested_child_index ||= -1
1030
+ @nested_child_index += 1
1031
+ end
801
1032
  end
802
1033
  end
803
1034
 
@@ -805,4 +1036,4 @@ module ActionView
805
1036
  cattr_accessor :default_form_builder
806
1037
  self.default_form_builder = ::ActionView::Helpers::FormBuilder
807
1038
  end
808
- end
1039
+ end