actionpack 3.2.22.5 → 5.2.4

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 (271) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +279 -603
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +13 -297
  5. data/lib/abstract_controller/asset_paths.rb +4 -2
  6. data/lib/abstract_controller/base.rb +82 -52
  7. data/lib/abstract_controller/caching/fragments.rb +166 -0
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/abstract_controller/callbacks.rb +117 -103
  10. data/lib/abstract_controller/collector.rb +18 -7
  11. data/lib/abstract_controller/error.rb +6 -0
  12. data/lib/abstract_controller/helpers.rb +65 -38
  13. data/lib/abstract_controller/logger.rb +3 -2
  14. data/lib/abstract_controller/railties/routes_helpers.rb +5 -3
  15. data/lib/abstract_controller/rendering.rb +77 -129
  16. data/lib/abstract_controller/translation.rb +21 -3
  17. data/lib/abstract_controller/url_for.rb +9 -7
  18. data/lib/abstract_controller.rb +12 -13
  19. data/lib/action_controller/api/api_rendering.rb +16 -0
  20. data/lib/action_controller/api.rb +149 -0
  21. data/lib/action_controller/base.rb +81 -40
  22. data/lib/action_controller/caching.rb +22 -62
  23. data/lib/action_controller/form_builder.rb +50 -0
  24. data/lib/action_controller/log_subscriber.rb +30 -18
  25. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  26. data/lib/action_controller/metal/conditional_get.rb +190 -47
  27. data/lib/action_controller/metal/content_security_policy.rb +52 -0
  28. data/lib/action_controller/metal/cookies.rb +3 -3
  29. data/lib/action_controller/metal/data_streaming.rb +40 -65
  30. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  31. data/lib/action_controller/metal/etag_with_template_digest.rb +57 -0
  32. data/lib/action_controller/metal/exceptions.rb +19 -12
  33. data/lib/action_controller/metal/flash.rb +42 -9
  34. data/lib/action_controller/metal/force_ssl.rb +79 -19
  35. data/lib/action_controller/metal/head.rb +35 -10
  36. data/lib/action_controller/metal/helpers.rb +31 -21
  37. data/lib/action_controller/metal/http_authentication.rb +182 -134
  38. data/lib/action_controller/metal/implicit_render.rb +62 -8
  39. data/lib/action_controller/metal/instrumentation.rb +28 -26
  40. data/lib/action_controller/metal/live.rb +312 -0
  41. data/lib/action_controller/metal/mime_responds.rb +159 -163
  42. data/lib/action_controller/metal/parameter_encoding.rb +51 -0
  43. data/lib/action_controller/metal/params_wrapper.rb +146 -93
  44. data/lib/action_controller/metal/redirecting.rb +80 -56
  45. data/lib/action_controller/metal/renderers.rb +119 -47
  46. data/lib/action_controller/metal/rendering.rb +89 -32
  47. data/lib/action_controller/metal/request_forgery_protection.rb +373 -41
  48. data/lib/action_controller/metal/rescue.rb +9 -16
  49. data/lib/action_controller/metal/streaming.rb +39 -45
  50. data/lib/action_controller/metal/strong_parameters.rb +1086 -0
  51. data/lib/action_controller/metal/testing.rb +8 -29
  52. data/lib/action_controller/metal/url_for.rb +43 -32
  53. data/lib/action_controller/metal.rb +112 -106
  54. data/lib/action_controller/railtie.rb +56 -18
  55. data/lib/action_controller/railties/helpers.rb +24 -0
  56. data/lib/action_controller/renderer.rb +117 -0
  57. data/lib/action_controller/template_assertions.rb +11 -0
  58. data/lib/action_controller/test_case.rb +402 -347
  59. data/lib/action_controller.rb +31 -30
  60. data/lib/action_dispatch/http/cache.rb +133 -34
  61. data/lib/action_dispatch/http/content_security_policy.rb +272 -0
  62. data/lib/action_dispatch/http/filter_parameters.rb +40 -24
  63. data/lib/action_dispatch/http/filter_redirect.rb +37 -0
  64. data/lib/action_dispatch/http/headers.rb +117 -16
  65. data/lib/action_dispatch/http/mime_negotiation.rb +98 -33
  66. data/lib/action_dispatch/http/mime_type.rb +198 -146
  67. data/lib/action_dispatch/http/mime_types.rb +22 -7
  68. data/lib/action_dispatch/http/parameter_filter.rb +61 -49
  69. data/lib/action_dispatch/http/parameters.rb +94 -51
  70. data/lib/action_dispatch/http/rack_cache.rb +4 -3
  71. data/lib/action_dispatch/http/request.rb +262 -117
  72. data/lib/action_dispatch/http/response.rb +400 -86
  73. data/lib/action_dispatch/http/upload.rb +66 -29
  74. data/lib/action_dispatch/http/url.rb +232 -60
  75. data/lib/action_dispatch/journey/formatter.rb +189 -0
  76. data/lib/action_dispatch/journey/gtg/builder.rb +164 -0
  77. data/lib/action_dispatch/journey/gtg/simulator.rb +41 -0
  78. data/lib/action_dispatch/journey/gtg/transition_table.rb +158 -0
  79. data/lib/action_dispatch/journey/nfa/builder.rb +78 -0
  80. data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
  81. data/lib/action_dispatch/journey/nfa/simulator.rb +49 -0
  82. data/lib/action_dispatch/journey/nfa/transition_table.rb +120 -0
  83. data/lib/action_dispatch/journey/nodes/node.rb +140 -0
  84. data/lib/action_dispatch/journey/parser.rb +199 -0
  85. data/lib/action_dispatch/journey/parser.y +50 -0
  86. data/lib/action_dispatch/journey/parser_extras.rb +31 -0
  87. data/lib/action_dispatch/journey/path/pattern.rb +199 -0
  88. data/lib/action_dispatch/journey/route.rb +203 -0
  89. data/lib/action_dispatch/journey/router/utils.rb +102 -0
  90. data/lib/action_dispatch/journey/router.rb +156 -0
  91. data/lib/action_dispatch/journey/routes.rb +82 -0
  92. data/lib/action_dispatch/journey/scanner.rb +64 -0
  93. data/lib/action_dispatch/journey/visitors.rb +268 -0
  94. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  95. data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
  96. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  97. data/lib/action_dispatch/journey.rb +7 -0
  98. data/lib/action_dispatch/middleware/callbacks.rb +17 -13
  99. data/lib/action_dispatch/middleware/cookies.rb +494 -162
  100. data/lib/action_dispatch/middleware/debug_exceptions.rb +176 -53
  101. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  102. data/lib/action_dispatch/middleware/exception_wrapper.rb +103 -38
  103. data/lib/action_dispatch/middleware/executor.rb +21 -0
  104. data/lib/action_dispatch/middleware/flash.rb +128 -91
  105. data/lib/action_dispatch/middleware/public_exceptions.rb +43 -16
  106. data/lib/action_dispatch/middleware/reloader.rb +6 -83
  107. data/lib/action_dispatch/middleware/remote_ip.rb +151 -49
  108. data/lib/action_dispatch/middleware/request_id.rb +19 -15
  109. data/lib/action_dispatch/middleware/session/abstract_store.rb +38 -34
  110. data/lib/action_dispatch/middleware/session/cache_store.rb +14 -9
  111. data/lib/action_dispatch/middleware/session/cookie_store.rb +94 -44
  112. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +15 -4
  113. data/lib/action_dispatch/middleware/show_exceptions.rb +36 -61
  114. data/lib/action_dispatch/middleware/ssl.rb +150 -0
  115. data/lib/action_dispatch/middleware/stack.rb +33 -41
  116. data/lib/action_dispatch/middleware/static.rb +92 -48
  117. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +22 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +27 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +52 -0
  122. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  123. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +16 -0
  124. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  125. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
  126. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +134 -5
  128. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  136. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
  137. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +200 -0
  138. data/lib/action_dispatch/railtie.rb +29 -8
  139. data/lib/action_dispatch/request/session.rb +234 -0
  140. data/lib/action_dispatch/request/utils.rb +78 -0
  141. data/lib/action_dispatch/routing/endpoint.rb +17 -0
  142. data/lib/action_dispatch/routing/inspector.rb +225 -0
  143. data/lib/action_dispatch/routing/mapper.rb +1329 -582
  144. data/lib/action_dispatch/routing/polymorphic_routes.rb +237 -94
  145. data/lib/action_dispatch/routing/redirection.rb +120 -50
  146. data/lib/action_dispatch/routing/route_set.rb +545 -322
  147. data/lib/action_dispatch/routing/routes_proxy.rb +37 -7
  148. data/lib/action_dispatch/routing/url_for.rb +103 -34
  149. data/lib/action_dispatch/routing.rb +66 -99
  150. data/lib/action_dispatch/system_test_case.rb +147 -0
  151. data/lib/action_dispatch/system_testing/browser.rb +49 -0
  152. data/lib/action_dispatch/system_testing/driver.rb +59 -0
  153. data/lib/action_dispatch/system_testing/server.rb +31 -0
  154. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
  155. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
  156. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
  157. data/lib/action_dispatch/testing/assertion_response.rb +47 -0
  158. data/lib/action_dispatch/testing/assertions/response.rb +53 -42
  159. data/lib/action_dispatch/testing/assertions/routing.rb +79 -74
  160. data/lib/action_dispatch/testing/assertions.rb +15 -9
  161. data/lib/action_dispatch/testing/integration.rb +361 -207
  162. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  163. data/lib/action_dispatch/testing/test_process.rb +28 -19
  164. data/lib/action_dispatch/testing/test_request.rb +30 -33
  165. data/lib/action_dispatch/testing/test_response.rb +35 -11
  166. data/lib/action_dispatch.rb +42 -32
  167. data/lib/action_pack/gem_version.rb +17 -0
  168. data/lib/action_pack/version.rb +7 -7
  169. data/lib/action_pack.rb +4 -2
  170. metadata +116 -175
  171. data/lib/abstract_controller/layouts.rb +0 -423
  172. data/lib/abstract_controller/view_paths.rb +0 -96
  173. data/lib/action_controller/caching/actions.rb +0 -185
  174. data/lib/action_controller/caching/fragments.rb +0 -127
  175. data/lib/action_controller/caching/pages.rb +0 -187
  176. data/lib/action_controller/caching/sweeping.rb +0 -97
  177. data/lib/action_controller/deprecated/integration_test.rb +0 -2
  178. data/lib/action_controller/deprecated/performance_test.rb +0 -1
  179. data/lib/action_controller/deprecated.rb +0 -3
  180. data/lib/action_controller/metal/compatibility.rb +0 -65
  181. data/lib/action_controller/metal/hide_actions.rb +0 -41
  182. data/lib/action_controller/metal/rack_delegation.rb +0 -26
  183. data/lib/action_controller/metal/responder.rb +0 -286
  184. data/lib/action_controller/metal/session_management.rb +0 -14
  185. data/lib/action_controller/middleware.rb +0 -39
  186. data/lib/action_controller/railties/paths.rb +0 -25
  187. data/lib/action_controller/record_identifier.rb +0 -85
  188. data/lib/action_controller/vendor/html-scanner/html/document.rb +0 -68
  189. data/lib/action_controller/vendor/html-scanner/html/node.rb +0 -532
  190. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +0 -177
  191. data/lib/action_controller/vendor/html-scanner/html/selector.rb +0 -830
  192. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +0 -107
  193. data/lib/action_controller/vendor/html-scanner/html/version.rb +0 -11
  194. data/lib/action_controller/vendor/html-scanner.rb +0 -20
  195. data/lib/action_dispatch/middleware/best_standards_support.rb +0 -30
  196. data/lib/action_dispatch/middleware/body_proxy.rb +0 -30
  197. data/lib/action_dispatch/middleware/head.rb +0 -18
  198. data/lib/action_dispatch/middleware/params_parser.rb +0 -75
  199. data/lib/action_dispatch/middleware/rescue.rb +0 -26
  200. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +0 -31
  201. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +0 -26
  202. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +0 -10
  203. data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +0 -2
  204. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +0 -15
  205. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +0 -17
  206. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +0 -2
  207. data/lib/action_dispatch/testing/assertions/dom.rb +0 -37
  208. data/lib/action_dispatch/testing/assertions/selector.rb +0 -435
  209. data/lib/action_dispatch/testing/assertions/tag.rb +0 -138
  210. data/lib/action_dispatch/testing/performance_test.rb +0 -10
  211. data/lib/action_view/asset_paths.rb +0 -142
  212. data/lib/action_view/base.rb +0 -220
  213. data/lib/action_view/buffers.rb +0 -43
  214. data/lib/action_view/context.rb +0 -36
  215. data/lib/action_view/flows.rb +0 -79
  216. data/lib/action_view/helpers/active_model_helper.rb +0 -50
  217. data/lib/action_view/helpers/asset_paths.rb +0 -7
  218. data/lib/action_view/helpers/asset_tag_helper.rb +0 -457
  219. data/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +0 -146
  220. data/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +0 -93
  221. data/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +0 -193
  222. data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +0 -148
  223. data/lib/action_view/helpers/atom_feed_helper.rb +0 -200
  224. data/lib/action_view/helpers/cache_helper.rb +0 -64
  225. data/lib/action_view/helpers/capture_helper.rb +0 -203
  226. data/lib/action_view/helpers/controller_helper.rb +0 -25
  227. data/lib/action_view/helpers/csrf_helper.rb +0 -32
  228. data/lib/action_view/helpers/date_helper.rb +0 -1062
  229. data/lib/action_view/helpers/debug_helper.rb +0 -40
  230. data/lib/action_view/helpers/form_helper.rb +0 -1486
  231. data/lib/action_view/helpers/form_options_helper.rb +0 -658
  232. data/lib/action_view/helpers/form_tag_helper.rb +0 -685
  233. data/lib/action_view/helpers/javascript_helper.rb +0 -110
  234. data/lib/action_view/helpers/number_helper.rb +0 -622
  235. data/lib/action_view/helpers/output_safety_helper.rb +0 -38
  236. data/lib/action_view/helpers/record_tag_helper.rb +0 -111
  237. data/lib/action_view/helpers/rendering_helper.rb +0 -92
  238. data/lib/action_view/helpers/sanitize_helper.rb +0 -259
  239. data/lib/action_view/helpers/tag_helper.rb +0 -167
  240. data/lib/action_view/helpers/text_helper.rb +0 -426
  241. data/lib/action_view/helpers/translation_helper.rb +0 -91
  242. data/lib/action_view/helpers/url_helper.rb +0 -693
  243. data/lib/action_view/helpers.rb +0 -60
  244. data/lib/action_view/locale/en.yml +0 -160
  245. data/lib/action_view/log_subscriber.rb +0 -28
  246. data/lib/action_view/lookup_context.rb +0 -258
  247. data/lib/action_view/path_set.rb +0 -101
  248. data/lib/action_view/railtie.rb +0 -55
  249. data/lib/action_view/renderer/abstract_renderer.rb +0 -41
  250. data/lib/action_view/renderer/partial_renderer.rb +0 -415
  251. data/lib/action_view/renderer/renderer.rb +0 -61
  252. data/lib/action_view/renderer/streaming_template_renderer.rb +0 -106
  253. data/lib/action_view/renderer/template_renderer.rb +0 -95
  254. data/lib/action_view/template/error.rb +0 -128
  255. data/lib/action_view/template/handlers/builder.rb +0 -26
  256. data/lib/action_view/template/handlers/erb.rb +0 -125
  257. data/lib/action_view/template/handlers.rb +0 -50
  258. data/lib/action_view/template/resolver.rb +0 -298
  259. data/lib/action_view/template/text.rb +0 -30
  260. data/lib/action_view/template.rb +0 -337
  261. data/lib/action_view/test_case.rb +0 -246
  262. data/lib/action_view/testing/resolvers.rb +0 -49
  263. data/lib/action_view.rb +0 -84
  264. data/lib/sprockets/assets.rake +0 -99
  265. data/lib/sprockets/bootstrap.rb +0 -37
  266. data/lib/sprockets/compressors.rb +0 -83
  267. data/lib/sprockets/helpers/isolated_helper.rb +0 -13
  268. data/lib/sprockets/helpers/rails_helper.rb +0 -182
  269. data/lib/sprockets/helpers.rb +0 -6
  270. data/lib/sprockets/railtie.rb +0 -62
  271. data/lib/sprockets/static_compiler.rb +0 -56
@@ -1,1486 +0,0 @@
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
- require 'action_view/helpers/active_model_helper'
6
- require 'active_support/core_ext/class/attribute'
7
- require 'active_support/core_ext/hash/slice'
8
- require 'active_support/core_ext/module/method_names'
9
- require 'active_support/core_ext/object/blank'
10
- require 'active_support/core_ext/string/output_safety'
11
- require 'active_support/core_ext/array/extract_options'
12
- require 'active_support/core_ext/string/inflections'
13
-
14
- module ActionView
15
- # = Action View Form Helpers
16
- module Helpers
17
- # Form helpers are designed to make working with resources much easier
18
- # compared to using vanilla HTML.
19
- #
20
- # Forms for models are created with +form_for+. That method yields a form
21
- # builder that knows the model the form is about. The form builder is thus
22
- # able to generate default values for input fields that correspond to model
23
- # attributes, and also convenient names, IDs, endpoints, etc.
24
- #
25
- # Conventions in the generated field names allow controllers to receive form
26
- # data nicely structured in +params+ with no effort on your side.
27
- #
28
- # For example, to create a new person you typically set up a new instance of
29
- # +Person+ in the <tt>PeopleController#new</tt> action, <tt>@person</tt>, and
30
- # pass it to +form_for+:
31
- #
32
- # <%= form_for @person do |f| %>
33
- # <%= f.label :first_name %>:
34
- # <%= f.text_field :first_name %><br />
35
- #
36
- # <%= f.label :last_name %>:
37
- # <%= f.text_field :last_name %><br />
38
- #
39
- # <%= f.submit %>
40
- # <% end %>
41
- #
42
- # The HTML generated for this would be (modulus formatting):
43
- #
44
- # <form action="/people" class="new_person" id="new_person" method="post">
45
- # <div style="margin:0;padding:0;display:inline">
46
- # <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
47
- # </div>
48
- # <label for="person_first_name">First name</label>:
49
- # <input id="person_first_name" name="person[first_name]" size="30" type="text" /><br />
50
- #
51
- # <label for="person_last_name">Last name</label>:
52
- # <input id="person_last_name" name="person[last_name]" size="30" type="text" /><br />
53
- #
54
- # <input name="commit" type="submit" value="Create Person" />
55
- # </form>
56
- #
57
- # As you see, the HTML reflects knowledge about the resource in several spots,
58
- # like the path the form should be submitted to, or the names of the input fields.
59
- #
60
- # In particular, thanks to the conventions followed in the generated field names, the
61
- # controller gets a nested hash <tt>params[:person]</tt> with the person attributes
62
- # set in the form. That hash is ready to be passed to <tt>Person.create</tt>:
63
- #
64
- # if @person = Person.create(params[:person])
65
- # # success
66
- # else
67
- # # error handling
68
- # end
69
- #
70
- # Interestingly, the exact same view code in the previous example can be used to edit
71
- # a person. If <tt>@person</tt> is an existing record with name "John Smith" and ID 256,
72
- # the code above as is would yield instead:
73
- #
74
- # <form action="/people/256" class="edit_person" id="edit_person_256" method="post">
75
- # <div style="margin:0;padding:0;display:inline">
76
- # <input name="_method" type="hidden" value="put" />
77
- # <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
78
- # </div>
79
- # <label for="person_first_name">First name</label>:
80
- # <input id="person_first_name" name="person[first_name]" size="30" type="text" value="John" /><br />
81
- #
82
- # <label for="person_last_name">Last name</label>:
83
- # <input id="person_last_name" name="person[last_name]" size="30" type="text" value="Smith" /><br />
84
- #
85
- # <input name="commit" type="submit" value="Update Person" />
86
- # </form>
87
- #
88
- # Note that the endpoint, default values, and submit button label are tailored for <tt>@person</tt>.
89
- # That works that way because the involved helpers know whether the resource is a new record or not,
90
- # and generate HTML accordingly.
91
- #
92
- # The controller would receive the form data again in <tt>params[:person]</tt>, ready to be
93
- # passed to <tt>Person#update_attributes</tt>:
94
- #
95
- # if @person.update_attributes(params[:person])
96
- # # success
97
- # else
98
- # # error handling
99
- # end
100
- #
101
- # That's how you typically work with resources.
102
- module FormHelper
103
- extend ActiveSupport::Concern
104
-
105
- include FormTagHelper
106
- include UrlHelper
107
-
108
- # Converts the given object to an ActiveModel compliant one.
109
- def convert_to_model(object)
110
- object.respond_to?(:to_model) ? object.to_model : object
111
- end
112
-
113
- # Creates a form and a scope around a specific model object that is used
114
- # as a base for questioning about values for the fields.
115
- #
116
- # Rails provides succinct resource-oriented form generation with +form_for+
117
- # like this:
118
- #
119
- # <%= form_for @offer do |f| %>
120
- # <%= f.label :version, 'Version' %>:
121
- # <%= f.text_field :version %><br />
122
- # <%= f.label :author, 'Author' %>:
123
- # <%= f.text_field :author %><br />
124
- # <%= f.submit %>
125
- # <% end %>
126
- #
127
- # There, +form_for+ is able to generate the rest of RESTful form
128
- # parameters based on introspection on the record, but to understand what
129
- # it does we need to dig first into the alternative generic usage it is
130
- # based upon.
131
- #
132
- # === Generic form_for
133
- #
134
- # The generic way to call +form_for+ yields a form builder around a
135
- # model:
136
- #
137
- # <%= form_for :person do |f| %>
138
- # First name: <%= f.text_field :first_name %><br />
139
- # Last name : <%= f.text_field :last_name %><br />
140
- # Biography : <%= f.text_area :biography %><br />
141
- # Admin? : <%= f.check_box :admin %><br />
142
- # <%= f.submit %>
143
- # <% end %>
144
- #
145
- # There, the argument is a symbol or string with the name of the
146
- # object the form is about.
147
- #
148
- # The form builder acts as a regular form helper that somehow carries the
149
- # model. Thus, the idea is that
150
- #
151
- # <%= f.text_field :first_name %>
152
- #
153
- # gets expanded to
154
- #
155
- # <%= text_field :person, :first_name %>
156
- #
157
- # The rightmost argument to +form_for+ is an
158
- # optional hash of options:
159
- #
160
- # * <tt>:url</tt> - The URL the form is submitted to. It takes the same
161
- # fields you pass to +url_for+ or +link_to+. In particular you may pass
162
- # here a named route directly as well. Defaults to the current action.
163
- # * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of
164
- # id attributes on form elements. The namespace attribute will be prefixed
165
- # with underscore on the generated HTML id.
166
- # * <tt>:html</tt> - Optional HTML attributes for the form tag.
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 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
- # <%= f.submit %>
178
- # <% end %>
179
- #
180
- # This also works for the methods in FormOptionHelper and DateHelper that
181
- # are designed to work with an object as base, like
182
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
183
- #
184
- # === Resource-oriented style
185
- #
186
- # As we said above, in addition to manually configuring the +form_for+
187
- # call, you can rely on automated resource identification, which will use
188
- # the conventions and named routes of that approach. This is the
189
- # preferred way to use +form_for+ nowadays.
190
- #
191
- # For example, if <tt>@post</tt> is an existing record you want to edit
192
- #
193
- # <%= form_for @post do |f| %>
194
- # ...
195
- # <% end %>
196
- #
197
- # is equivalent to something like:
198
- #
199
- # <%= form_for @post, :as => :post, :url => post_path(@post), :method => :put, :html => { :class => "edit_post", :id => "edit_post_45" } do |f| %>
200
- # ...
201
- # <% end %>
202
- #
203
- # And for new records
204
- #
205
- # <%= form_for(Post.new) do |f| %>
206
- # ...
207
- # <% end %>
208
- #
209
- # is equivalent to something like:
210
- #
211
- # <%= form_for @post, :as => :post, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %>
212
- # ...
213
- # <% end %>
214
- #
215
- # You can also overwrite the individual conventions, like this:
216
- #
217
- # <%= form_for(@post, :url => super_posts_path) do |f| %>
218
- # ...
219
- # <% end %>
220
- #
221
- # You can also set the answer format, like this:
222
- #
223
- # <%= form_for(@post, :format => :json) do |f| %>
224
- # ...
225
- # <% end %>
226
- #
227
- # If you have an object that needs to be represented as a different
228
- # parameter, like a Person that acts as a Client:
229
- #
230
- # <%= form_for(@person, :as => :client) do |f| %>
231
- # ...
232
- # <% end %>
233
- #
234
- # For namespaced routes, like +admin_post_url+:
235
- #
236
- # <%= form_for([:admin, @post]) do |f| %>
237
- # ...
238
- # <% end %>
239
- #
240
- # If your resource has associations defined, for example, you want to add comments
241
- # to the document given that the routes are set correctly:
242
- #
243
- # <%= form_for([@document, @comment]) do |f| %>
244
- # ...
245
- # <% end %>
246
- #
247
- # Where <tt>@document = Document.find(params[:id])</tt> and
248
- # <tt>@comment = Comment.new</tt>.
249
- #
250
- # === Setting the method
251
- #
252
- # You can force the form to use the full array of HTTP verbs by setting
253
- #
254
- # :method => (:get|:post|:put|:delete)
255
- #
256
- # in the options hash. If the verb is not GET or POST, which are natively supported by HTML forms, the
257
- # form will be set to POST and a hidden input called _method will carry the intended verb for the server
258
- # to interpret.
259
- #
260
- # === Unobtrusive JavaScript
261
- #
262
- # Specifying:
263
- #
264
- # :remote => true
265
- #
266
- # in the options hash creates a form that will allow the unobtrusive JavaScript drivers to modify its
267
- # behavior. The expected default behavior is an XMLHttpRequest in the background instead of the regular
268
- # POST arrangement, but ultimately the behavior is the choice of the JavaScript driver implementor.
269
- # Even though it's using JavaScript to serialize the form elements, the form submission will work just like
270
- # a regular submission as viewed by the receiving side (all elements available in <tt>params</tt>).
271
- #
272
- # Example:
273
- #
274
- # <%= form_for(@post, :remote => true) do |f| %>
275
- # ...
276
- # <% end %>
277
- #
278
- # The HTML generated for this would be:
279
- #
280
- # <form action='http://www.example.com' method='post' data-remote='true'>
281
- # <div style='margin:0;padding:0;display:inline'>
282
- # <input name='_method' type='hidden' value='put' />
283
- # </div>
284
- # ...
285
- # </form>
286
- #
287
- # === Removing hidden model id's
288
- #
289
- # The form_for method automatically includes the model id as a hidden field in the form.
290
- # This is used to maintain the correlation between the form data and its associated model.
291
- # Some ORM systems do not use IDs on nested models so in this case you want to be able
292
- # to disable the hidden id.
293
- #
294
- # In the following example the Post model has many Comments stored within it in a NoSQL database,
295
- # thus there is no primary key for comments.
296
- #
297
- # Example:
298
- #
299
- # <%= form_for(@post) do |f| %>
300
- # <% f.fields_for(:comments, :include_id => false) do |cf| %>
301
- # ...
302
- # <% end %>
303
- # <% end %>
304
- #
305
- # === Customized form builders
306
- #
307
- # You can also build forms using a customized FormBuilder class. Subclass
308
- # FormBuilder and override or define some more helpers, then use your
309
- # custom builder. For example, let's say you made a helper to
310
- # automatically add labels to form inputs.
311
- #
312
- # <%= form_for @person, :url => { :action => "create" }, :builder => LabellingFormBuilder do |f| %>
313
- # <%= f.text_field :first_name %>
314
- # <%= f.text_field :last_name %>
315
- # <%= f.text_area :biography %>
316
- # <%= f.check_box :admin %>
317
- # <%= f.submit %>
318
- # <% end %>
319
- #
320
- # In this case, if you use this:
321
- #
322
- # <%= render f %>
323
- #
324
- # The rendered template is <tt>people/_labelling_form</tt> and the local
325
- # variable referencing the form builder is called
326
- # <tt>labelling_form</tt>.
327
- #
328
- # The custom FormBuilder class is automatically merged with the options
329
- # of a nested fields_for call, unless it's explicitly set.
330
- #
331
- # In many cases you will want to wrap the above in another helper, so you
332
- # could do something like the following:
333
- #
334
- # def labelled_form_for(record_or_name_or_array, *args, &block)
335
- # options = args.extract_options!
336
- # form_for(record_or_name_or_array, *(args << options.merge(:builder => LabellingFormBuilder)), &block)
337
- # end
338
- #
339
- # If you don't need to attach a form to a model instance, then check out
340
- # FormTagHelper#form_tag.
341
- #
342
- # === Form to external resources
343
- #
344
- # When you build forms to external resources sometimes you need to set an authenticity token or just render a form
345
- # without it, for example when you submit data to a payment gateway number and types of fields could be limited.
346
- #
347
- # To set an authenticity token you need to pass an <tt>:authenticity_token</tt> parameter
348
- #
349
- # <%= form_for @invoice, :url => external_url, :authenticity_token => 'external_token' do |f|
350
- # ...
351
- # <% end %>
352
- #
353
- # If you don't want to an authenticity token field be rendered at all just pass <tt>false</tt>:
354
- #
355
- # <%= form_for @invoice, :url => external_url, :authenticity_token => false do |f|
356
- # ...
357
- # <% end %>
358
- def form_for(record, options = {}, &block)
359
- raise ArgumentError, "Missing block" unless block_given?
360
-
361
- options[:html] ||= {}
362
-
363
- case record
364
- when String, Symbol
365
- object_name = record
366
- object = nil
367
- else
368
- object = record.is_a?(Array) ? record.last : record
369
- object_name = options[:as] || ActiveModel::Naming.param_key(object)
370
- apply_form_for_options!(record, options)
371
- end
372
-
373
- options[:html][:remote] = options.delete(:remote) if options.has_key?(:remote)
374
- options[:html][:method] = options.delete(:method) if options.has_key?(:method)
375
- options[:html][:authenticity_token] = options.delete(:authenticity_token)
376
-
377
- builder = options[:parent_builder] = instantiate_builder(object_name, object, options, &block)
378
- output = capture(builder, &block)
379
- default_options = builder.multipart? ? { :multipart => true } : {}
380
- form_tag(options.delete(:url) || {}, default_options.merge!(options.delete(:html))) { output }
381
- end
382
-
383
- def apply_form_for_options!(object_or_array, options) #:nodoc:
384
- object = object_or_array.is_a?(Array) ? object_or_array.last : object_or_array
385
- object = convert_to_model(object)
386
-
387
- as = options[:as]
388
- action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :put] : [:new, :post]
389
- options[:html].reverse_merge!(
390
- :class => as ? "#{action}_#{as}" : dom_class(object, action),
391
- :id => as ? "#{action}_#{as}" : [options[:namespace], dom_id(object, action)].compact.join("_").presence,
392
- :method => method
393
- )
394
-
395
- options[:url] ||= polymorphic_path(object_or_array, :format => options.delete(:format))
396
- end
397
- private :apply_form_for_options!
398
-
399
- # Creates a scope around a specific model object like form_for, but
400
- # doesn't create the form tags themselves. This makes fields_for suitable
401
- # for specifying additional model objects in the same form.
402
- #
403
- # === Generic Examples
404
- #
405
- # <%= form_for @person do |person_form| %>
406
- # First name: <%= person_form.text_field :first_name %>
407
- # Last name : <%= person_form.text_field :last_name %>
408
- #
409
- # <%= fields_for @person.permission do |permission_fields| %>
410
- # Admin? : <%= permission_fields.check_box :admin %>
411
- # <% end %>
412
- #
413
- # <%= f.submit %>
414
- # <% end %>
415
- #
416
- # ...or if you have an object that needs to be represented as a different
417
- # parameter, like a Client that acts as a Person:
418
- #
419
- # <%= fields_for :person, @client do |permission_fields| %>
420
- # Admin?: <%= permission_fields.check_box :admin %>
421
- # <% end %>
422
- #
423
- # ...or if you don't have an object, just a name of the parameter:
424
- #
425
- # <%= fields_for :person do |permission_fields| %>
426
- # Admin?: <%= permission_fields.check_box :admin %>
427
- # <% end %>
428
- #
429
- # Note: This also works for the methods in FormOptionHelper and
430
- # DateHelper that are designed to work with an object as base, like
431
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
432
- #
433
- # === Nested Attributes Examples
434
- #
435
- # When the object belonging to the current scope has a nested attribute
436
- # writer for a certain attribute, fields_for will yield a new scope
437
- # for that attribute. This allows you to create forms that set or change
438
- # the attributes of a parent object and its associations in one go.
439
- #
440
- # Nested attribute writers are normal setter methods named after an
441
- # association. The most common way of defining these writers is either
442
- # with +accepts_nested_attributes_for+ in a model definition or by
443
- # defining a method with the proper name. For example: the attribute
444
- # writer for the association <tt>:address</tt> is called
445
- # <tt>address_attributes=</tt>.
446
- #
447
- # Whether a one-to-one or one-to-many style form builder will be yielded
448
- # depends on whether the normal reader method returns a _single_ object
449
- # or an _array_ of objects.
450
- #
451
- # ==== One-to-one
452
- #
453
- # Consider a Person class which returns a _single_ Address from the
454
- # <tt>address</tt> reader method and responds to the
455
- # <tt>address_attributes=</tt> writer method:
456
- #
457
- # class Person
458
- # def address
459
- # @address
460
- # end
461
- #
462
- # def address_attributes=(attributes)
463
- # # Process the attributes hash
464
- # end
465
- # end
466
- #
467
- # This model can now be used with a nested fields_for, like so:
468
- #
469
- # <%= form_for @person do |person_form| %>
470
- # ...
471
- # <%= person_form.fields_for :address do |address_fields| %>
472
- # Street : <%= address_fields.text_field :street %>
473
- # Zip code: <%= address_fields.text_field :zip_code %>
474
- # <% end %>
475
- # ...
476
- # <% end %>
477
- #
478
- # When address is already an association on a Person you can use
479
- # +accepts_nested_attributes_for+ to define the writer method for you:
480
- #
481
- # class Person < ActiveRecord::Base
482
- # has_one :address
483
- # accepts_nested_attributes_for :address
484
- # end
485
- #
486
- # If you want to destroy the associated model through the form, you have
487
- # to enable it first using the <tt>:allow_destroy</tt> option for
488
- # +accepts_nested_attributes_for+:
489
- #
490
- # class Person < ActiveRecord::Base
491
- # has_one :address
492
- # accepts_nested_attributes_for :address, :allow_destroy => true
493
- # end
494
- #
495
- # Now, when you use a form element with the <tt>_destroy</tt> parameter,
496
- # with a value that evaluates to +true+, you will destroy the associated
497
- # model (eg. 1, '1', true, or 'true'):
498
- #
499
- # <%= form_for @person do |person_form| %>
500
- # ...
501
- # <%= person_form.fields_for :address do |address_fields| %>
502
- # ...
503
- # Delete: <%= address_fields.check_box :_destroy %>
504
- # <% end %>
505
- # ...
506
- # <% end %>
507
- #
508
- # ==== One-to-many
509
- #
510
- # Consider a Person class which returns an _array_ of Project instances
511
- # from the <tt>projects</tt> reader method and responds to the
512
- # <tt>projects_attributes=</tt> writer method:
513
- #
514
- # class Person
515
- # def projects
516
- # [@project1, @project2]
517
- # end
518
- #
519
- # def projects_attributes=(attributes)
520
- # # Process the attributes hash
521
- # end
522
- # end
523
- #
524
- # Note that the <tt>projects_attributes=</tt> writer method is in fact
525
- # required for fields_for to correctly identify <tt>:projects</tt> as a
526
- # collection, and the correct indices to be set in the form markup.
527
- #
528
- # When projects is already an association on Person you can use
529
- # +accepts_nested_attributes_for+ to define the writer method for you:
530
- #
531
- # class Person < ActiveRecord::Base
532
- # has_many :projects
533
- # accepts_nested_attributes_for :projects
534
- # end
535
- #
536
- # This model can now be used with a nested fields_for. The block given to
537
- # the nested fields_for call will be repeated for each instance in the
538
- # collection:
539
- #
540
- # <%= form_for @person do |person_form| %>
541
- # ...
542
- # <%= person_form.fields_for :projects do |project_fields| %>
543
- # <% if project_fields.object.active? %>
544
- # Name: <%= project_fields.text_field :name %>
545
- # <% end %>
546
- # <% end %>
547
- # ...
548
- # <% end %>
549
- #
550
- # It's also possible to specify the instance to be used:
551
- #
552
- # <%= form_for @person do |person_form| %>
553
- # ...
554
- # <% @person.projects.each do |project| %>
555
- # <% if project.active? %>
556
- # <%= person_form.fields_for :projects, project do |project_fields| %>
557
- # Name: <%= project_fields.text_field :name %>
558
- # <% end %>
559
- # <% end %>
560
- # <% end %>
561
- # ...
562
- # <% end %>
563
- #
564
- # Or a collection to be used:
565
- #
566
- # <%= form_for @person do |person_form| %>
567
- # ...
568
- # <%= person_form.fields_for :projects, @active_projects do |project_fields| %>
569
- # Name: <%= project_fields.text_field :name %>
570
- # <% end %>
571
- # ...
572
- # <% end %>
573
- #
574
- # When projects is already an association on Person you can use
575
- # +accepts_nested_attributes_for+ to define the writer method for you:
576
- #
577
- # class Person < ActiveRecord::Base
578
- # has_many :projects
579
- # accepts_nested_attributes_for :projects
580
- # end
581
- #
582
- # If you want to destroy any of the associated models through the
583
- # form, you have to enable it first using the <tt>:allow_destroy</tt>
584
- # option for +accepts_nested_attributes_for+:
585
- #
586
- # class Person < ActiveRecord::Base
587
- # has_many :projects
588
- # accepts_nested_attributes_for :projects, :allow_destroy => true
589
- # end
590
- #
591
- # This will allow you to specify which models to destroy in the
592
- # attributes hash by adding a form element for the <tt>_destroy</tt>
593
- # parameter with a value that evaluates to +true+
594
- # (eg. 1, '1', true, or 'true'):
595
- #
596
- # <%= form_for @person do |person_form| %>
597
- # ...
598
- # <%= person_form.fields_for :projects do |project_fields| %>
599
- # Delete: <%= project_fields.check_box :_destroy %>
600
- # <% end %>
601
- # ...
602
- # <% end %>
603
- def fields_for(record_name, record_object = nil, options = {}, &block)
604
- builder = instantiate_builder(record_name, record_object, options, &block)
605
- output = capture(builder, &block)
606
- output.concat builder.hidden_field(:id) if output && options[:hidden_field_id] && !builder.emitted_hidden_id?
607
- output
608
- end
609
-
610
- # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
611
- # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
612
- # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
613
- # Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
614
- # onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to
615
- # target labels for radio_button tags (where the value is used in the ID of the input tag).
616
- #
617
- # ==== Examples
618
- # label(:post, :title)
619
- # # => <label for="post_title">Title</label>
620
- #
621
- # You can localize your labels based on model and attribute names.
622
- # For example you can define the following in your locale (e.g. en.yml)
623
- #
624
- # helpers:
625
- # label:
626
- # post:
627
- # body: "Write your entire text here"
628
- #
629
- # Which then will result in
630
- #
631
- # label(:post, :body)
632
- # # => <label for="post_body">Write your entire text here</label>
633
- #
634
- # Localization can also be based purely on the translation of the attribute-name
635
- # (if you are using ActiveRecord):
636
- #
637
- # activerecord:
638
- # attributes:
639
- # post:
640
- # cost: "Total cost"
641
- #
642
- # label(:post, :cost)
643
- # # => <label for="post_cost">Total cost</label>
644
- #
645
- # label(:post, :title, "A short title")
646
- # # => <label for="post_title">A short title</label>
647
- #
648
- # label(:post, :title, "A short title", :class => "title_label")
649
- # # => <label for="post_title" class="title_label">A short title</label>
650
- #
651
- # label(:post, :privacy, "Public Post", :value => "public")
652
- # # => <label for="post_privacy_public">Public Post</label>
653
- #
654
- # label(:post, :terms) do
655
- # 'Accept <a href="/terms">Terms</a>.'.html_safe
656
- # end
657
- def label(object_name, method, content_or_options = nil, options = nil, &block)
658
- options ||= {}
659
-
660
- content_is_options = content_or_options.is_a?(Hash)
661
- if content_is_options || block_given?
662
- options.merge!(content_or_options) if content_is_options
663
- text = nil
664
- else
665
- text = content_or_options
666
- end
667
-
668
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options, &block)
669
- end
670
-
671
- # Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
672
- # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
673
- # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
674
- # shown.
675
- #
676
- # ==== Examples
677
- # text_field(:post, :title, :size => 20)
678
- # # => <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" />
679
- #
680
- # text_field(:post, :title, :class => "create_input")
681
- # # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
682
- #
683
- # text_field(:session, :user, :onchange => "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }")
684
- # # => <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!'); }"/>
685
- #
686
- # text_field(:snippet, :code, :size => 20, :class => 'code_input')
687
- # # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
688
- #
689
- def text_field(object_name, method, options = {})
690
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("text", options)
691
- end
692
-
693
- # Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
694
- # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
695
- # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
696
- # shown.
697
- #
698
- # ==== Examples
699
- # password_field(:login, :pass, :size => 20)
700
- # # => <input type="password" id="login_pass" name="login[pass]" size="20" />
701
- #
702
- # password_field(:account, :secret, :class => "form_input", :value => @account.secret)
703
- # # => <input type="password" id="account_secret" name="account[secret]" value="#{@account.secret}" class="form_input" />
704
- #
705
- # password_field(:user, :password, :onchange => "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }")
706
- # # => <input type="password" id="user_password" name="user[password]" onchange = "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }"/>
707
- #
708
- # password_field(:account, :pin, :size => 20, :class => 'form_input')
709
- # # => <input type="password" id="account_pin" name="account[pin]" size="20" class="form_input" />
710
- #
711
- def password_field(object_name, method, options = {})
712
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("password", { :value => nil }.merge!(options))
713
- end
714
-
715
- # Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
716
- # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
717
- # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
718
- # shown.
719
- #
720
- # ==== Examples
721
- # hidden_field(:signup, :pass_confirm)
722
- # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
723
- #
724
- # hidden_field(:post, :tag_list)
725
- # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
726
- #
727
- # hidden_field(:user, :token)
728
- # # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
729
- def hidden_field(object_name, method, options = {})
730
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("hidden", options)
731
- end
732
-
733
- # Returns a file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
734
- # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
735
- # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
736
- # shown.
737
- #
738
- # Using this method inside a +form_for+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>.
739
- #
740
- # ==== Examples
741
- # file_field(:user, :avatar)
742
- # # => <input type="file" id="user_avatar" name="user[avatar]" />
743
- #
744
- # file_field(:post, :attached, :accept => 'text/html')
745
- # # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
746
- #
747
- # file_field(:attachment, :file, :class => 'file_input')
748
- # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
749
- #
750
- def file_field(object_name, method, options = {})
751
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("file", options.update({:size => nil}))
752
- end
753
-
754
- # Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
755
- # on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
756
- # hash with +options+.
757
- #
758
- # ==== Examples
759
- # text_area(:post, :body, :cols => 20, :rows => 40)
760
- # # => <textarea cols="20" rows="40" id="post_body" name="post[body]">
761
- # # #{@post.body}
762
- # # </textarea>
763
- #
764
- # text_area(:comment, :text, :size => "20x30")
765
- # # => <textarea cols="20" rows="30" id="comment_text" name="comment[text]">
766
- # # #{@comment.text}
767
- # # </textarea>
768
- #
769
- # text_area(:application, :notes, :cols => 40, :rows => 15, :class => 'app_input')
770
- # # => <textarea cols="40" rows="15" id="application_notes" name="application[notes]" class="app_input">
771
- # # #{@application.notes}
772
- # # </textarea>
773
- #
774
- # text_area(:entry, :body, :size => "20x20", :disabled => 'disabled')
775
- # # => <textarea cols="20" rows="20" id="entry_body" name="entry[body]" disabled="disabled">
776
- # # #{@entry.body}
777
- # # </textarea>
778
- def text_area(object_name, method, options = {})
779
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_text_area_tag(options)
780
- end
781
-
782
- # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
783
- # assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object.
784
- # It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked.
785
- # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
786
- # while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
787
- #
788
- # ==== Gotcha
789
- #
790
- # The HTML specification says unchecked check boxes are not successful, and
791
- # thus web browsers do not send them. Unfortunately this introduces a gotcha:
792
- # if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid
793
- # invoice the user unchecks its check box, no +paid+ parameter is sent. So,
794
- # any mass-assignment idiom like
795
- #
796
- # @invoice.update_attributes(params[:invoice])
797
- #
798
- # wouldn't update the flag.
799
- #
800
- # To prevent this the helper generates an auxiliary hidden field before
801
- # the very check box. The hidden field has the same name and its
802
- # attributes mimic an unchecked check box.
803
- #
804
- # This way, the client either sends only the hidden field (representing
805
- # the check box is unchecked), or both fields. Since the HTML specification
806
- # says key/value pairs have to be sent in the same order they appear in the
807
- # form, and parameters extraction gets the last occurrence of any repeated
808
- # key in the query string, that works for ordinary forms.
809
- #
810
- # Unfortunately that workaround does not work when the check box goes
811
- # within an array-like parameter, as in
812
- #
813
- # <%= fields_for "project[invoice_attributes][]", invoice, :index => nil do |form| %>
814
- # <%= form.check_box :paid %>
815
- # ...
816
- # <% end %>
817
- #
818
- # because parameter name repetition is precisely what Rails seeks to distinguish
819
- # the elements of the array. For each item with a checked check box you
820
- # get an extra ghost item with only that attribute, assigned to "0".
821
- #
822
- # In that case it is preferable to either use +check_box_tag+ or to use
823
- # hashes instead of arrays.
824
- #
825
- # ==== Examples
826
- # # Let's say that @post.validated? is 1:
827
- # check_box("post", "validated")
828
- # # => <input name="post[validated]" type="hidden" value="0" />
829
- # # <input type="checkbox" id="post_validated" name="post[validated]" value="1" />
830
- #
831
- # # Let's say that @puppy.gooddog is "no":
832
- # check_box("puppy", "gooddog", {}, "yes", "no")
833
- # # => <input name="puppy[gooddog]" type="hidden" value="no" />
834
- # # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
835
- #
836
- # check_box("eula", "accepted", { :class => 'eula_check' }, "yes", "no")
837
- # # => <input name="eula[accepted]" type="hidden" value="no" />
838
- # # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
839
- #
840
- def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
841
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
842
- end
843
-
844
- # Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
845
- # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the
846
- # radio button will be checked.
847
- #
848
- # To force the radio button to be checked pass <tt>:checked => true</tt> in the
849
- # +options+ hash. You may pass HTML options there as well.
850
- #
851
- # ==== Examples
852
- # # Let's say that @post.category returns "rails":
853
- # radio_button("post", "category", "rails")
854
- # radio_button("post", "category", "java")
855
- # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
856
- # # <input type="radio" id="post_category_java" name="post[category]" value="java" />
857
- #
858
- # radio_button("user", "receive_newsletter", "yes")
859
- # radio_button("user", "receive_newsletter", "no")
860
- # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
861
- # # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
862
- def radio_button(object_name, method, tag_value, options = {})
863
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_radio_button_tag(tag_value, options)
864
- end
865
-
866
- # Returns an input of type "search" for accessing a specified attribute (identified by +method+) on an object
867
- # assigned to the template (identified by +object_name+). Inputs of type "search" may be styled differently by
868
- # some browsers.
869
- #
870
- # ==== Examples
871
- #
872
- # search_field(:user, :name)
873
- # # => <input id="user_name" name="user[name]" size="30" type="search" />
874
- # search_field(:user, :name, :autosave => false)
875
- # # => <input autosave="false" id="user_name" name="user[name]" size="30" type="search" />
876
- # search_field(:user, :name, :results => 3)
877
- # # => <input id="user_name" name="user[name]" results="3" size="30" type="search" />
878
- # # Assume request.host returns "www.example.com"
879
- # search_field(:user, :name, :autosave => true)
880
- # # => <input autosave="com.example.www" id="user_name" name="user[name]" results="10" size="30" type="search" />
881
- # search_field(:user, :name, :onsearch => true)
882
- # # => <input id="user_name" incremental="true" name="user[name]" onsearch="true" size="30" type="search" />
883
- # search_field(:user, :name, :autosave => false, :onsearch => true)
884
- # # => <input autosave="false" id="user_name" incremental="true" name="user[name]" onsearch="true" size="30" type="search" />
885
- # search_field(:user, :name, :autosave => true, :onsearch => true)
886
- # # => <input autosave="com.example.www" id="user_name" incremental="true" name="user[name]" onsearch="true" results="10" size="30" type="search" />
887
- #
888
- def search_field(object_name, method, options = {})
889
- options = options.stringify_keys
890
-
891
- if options["autosave"]
892
- if options["autosave"] == true
893
- options["autosave"] = request.host.split(".").reverse.join(".")
894
- end
895
- options["results"] ||= 10
896
- end
897
-
898
- if options["onsearch"]
899
- options["incremental"] = true unless options.has_key?("incremental")
900
- end
901
-
902
- InstanceTag.new(object_name, method, self, options.delete("object")).to_input_field_tag("search", options)
903
- end
904
-
905
- # Returns a text_field of type "tel".
906
- #
907
- # telephone_field("user", "phone")
908
- # # => <input id="user_phone" name="user[phone]" size="30" type="tel" />
909
- #
910
- def telephone_field(object_name, method, options = {})
911
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("tel", options)
912
- end
913
- alias phone_field telephone_field
914
-
915
- # Returns a text_field of type "url".
916
- #
917
- # url_field("user", "homepage")
918
- # # => <input id="user_homepage" size="30" name="user[homepage]" type="url" />
919
- #
920
- def url_field(object_name, method, options = {})
921
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("url", options)
922
- end
923
-
924
- # Returns a text_field of type "email".
925
- #
926
- # email_field("user", "address")
927
- # # => <input id="user_address" size="30" name="user[address]" type="email" />
928
- #
929
- def email_field(object_name, method, options = {})
930
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("email", options)
931
- end
932
-
933
- # Returns an input tag of type "number".
934
- #
935
- # ==== Options
936
- # * Accepts same options as number_field_tag
937
- def number_field(object_name, method, options = {})
938
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_number_field_tag("number", options)
939
- end
940
-
941
- # Returns an input tag of type "range".
942
- #
943
- # ==== Options
944
- # * Accepts same options as range_field_tag
945
- def range_field(object_name, method, options = {})
946
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_number_field_tag("range", options)
947
- end
948
-
949
- private
950
-
951
- def instantiate_builder(record_name, record_object, options, &block)
952
- case record_name
953
- when String, Symbol
954
- object = record_object
955
- object_name = record_name
956
- else
957
- object = record_name
958
- object_name = ActiveModel::Naming.param_key(object)
959
- end
960
-
961
- builder = options[:builder] || default_form_builder
962
- builder.new(object_name, object, self, options, block)
963
- end
964
-
965
- def default_form_builder
966
- builder = ActionView::Base.default_form_builder
967
- builder.respond_to?(:constantize) ? builder.constantize : builder
968
- end
969
- end
970
-
971
- class InstanceTag
972
- include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper
973
-
974
- attr_reader :object, :method_name, :object_name
975
-
976
- DEFAULT_FIELD_OPTIONS = { "size" => 30 }
977
- DEFAULT_RADIO_OPTIONS = { }
978
- DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }
979
-
980
- def initialize(object_name, method_name, template_object, object = nil)
981
- @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
982
- @template_object = template_object
983
-
984
- @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
985
- @object = retrieve_object(object)
986
- @auto_index = retrieve_autoindex(Regexp.last_match.pre_match) if Regexp.last_match
987
- end
988
-
989
- def to_label_tag(text = nil, options = {}, &block)
990
- options = options.stringify_keys
991
- tag_value = options.delete("value")
992
- name_and_id = options.dup
993
-
994
- if name_and_id["for"]
995
- name_and_id["id"] = name_and_id["for"]
996
- else
997
- name_and_id.delete("id")
998
- end
999
-
1000
- add_default_name_and_id_for_value(tag_value, name_and_id)
1001
- options.delete("index")
1002
- options.delete("namespace")
1003
- options["for"] ||= name_and_id["id"]
1004
-
1005
- if block_given?
1006
- @template_object.label_tag(name_and_id["id"], options, &block)
1007
- else
1008
- content = if text.blank?
1009
- object_name.gsub!(/\[(.*)_attributes\]\[\d\]/, '.\1')
1010
- method_and_value = tag_value.present? ? "#{method_name}.#{tag_value}" : method_name
1011
-
1012
- if object.respond_to?(:to_model)
1013
- key = object.class.model_name.i18n_key
1014
- i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
1015
- end
1016
-
1017
- i18n_default ||= ""
1018
- I18n.t("#{object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence
1019
- else
1020
- text.to_s
1021
- end
1022
-
1023
- content ||= if object && object.class.respond_to?(:human_attribute_name)
1024
- object.class.human_attribute_name(method_name)
1025
- end
1026
-
1027
- content ||= method_name.humanize
1028
-
1029
- label_tag(name_and_id["id"], content, options)
1030
- end
1031
- end
1032
-
1033
- def to_input_field_tag(field_type, options = {})
1034
- options = options.stringify_keys
1035
- options["size"] = options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] unless options.key?("size")
1036
- options = DEFAULT_FIELD_OPTIONS.merge(options)
1037
- if field_type == "hidden"
1038
- options.delete("size")
1039
- end
1040
- options["type"] ||= field_type
1041
- options["value"] = options.fetch("value"){ value_before_type_cast(object) } unless field_type == "file"
1042
- options["value"] &&= ERB::Util.html_escape(options["value"])
1043
- add_default_name_and_id(options)
1044
- tag("input", options)
1045
- end
1046
-
1047
- def to_number_field_tag(field_type, options = {})
1048
- options = options.stringify_keys
1049
- options['size'] ||= nil
1050
-
1051
- if range = options.delete("in") || options.delete("within")
1052
- options.update("min" => range.min, "max" => range.max)
1053
- end
1054
- to_input_field_tag(field_type, options)
1055
- end
1056
-
1057
- def to_radio_button_tag(tag_value, options = {})
1058
- options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
1059
- options["type"] = "radio"
1060
- options["value"] = tag_value
1061
- if options.has_key?("checked")
1062
- cv = options.delete "checked"
1063
- checked = cv == true || cv == "checked"
1064
- else
1065
- checked = self.class.radio_button_checked?(value(object), tag_value)
1066
- end
1067
- options["checked"] = "checked" if checked
1068
- add_default_name_and_id_for_value(tag_value, options)
1069
- tag("input", options)
1070
- end
1071
-
1072
- def to_text_area_tag(options = {})
1073
- options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys)
1074
- add_default_name_and_id(options)
1075
-
1076
- if size = options.delete("size")
1077
- options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
1078
- end
1079
-
1080
- content_tag("textarea", options.delete('value') || value_before_type_cast(object), options)
1081
- end
1082
-
1083
- def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
1084
- options = options.stringify_keys
1085
- options["type"] = "checkbox"
1086
- options["value"] = checked_value
1087
- if options.has_key?("checked")
1088
- cv = options.delete "checked"
1089
- checked = cv == true || cv == "checked"
1090
- else
1091
- checked = self.class.check_box_checked?(value(object), checked_value)
1092
- end
1093
- options["checked"] = "checked" if checked
1094
- if options["multiple"]
1095
- add_default_name_and_id_for_value(checked_value, options)
1096
- options.delete("multiple")
1097
- else
1098
- add_default_name_and_id(options)
1099
- end
1100
- hidden = unchecked_value ? tag("input", "name" => options["name"], "type" => "hidden", "value" => unchecked_value, "disabled" => options["disabled"]) : ""
1101
- checkbox = tag("input", options)
1102
- (hidden + checkbox).html_safe
1103
- end
1104
-
1105
- def to_boolean_select_tag(options = {})
1106
- options = options.stringify_keys
1107
- add_default_name_and_id(options)
1108
- value = value(object)
1109
- tag_text = "<select"
1110
- tag_text << tag_options(options)
1111
- tag_text << "><option value=\"false\""
1112
- tag_text << " selected" if value == false
1113
- tag_text << ">False</option><option value=\"true\""
1114
- tag_text << " selected" if value
1115
- tag_text << ">True</option></select>"
1116
- end
1117
-
1118
- def to_content_tag(tag_name, options = {})
1119
- content_tag(tag_name, value(object), options)
1120
- end
1121
-
1122
- def retrieve_object(object)
1123
- if object
1124
- object
1125
- elsif @template_object.instance_variable_defined?("@#{@object_name}")
1126
- @template_object.instance_variable_get("@#{@object_name}")
1127
- end
1128
- rescue NameError
1129
- # As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil.
1130
- nil
1131
- end
1132
-
1133
- def retrieve_autoindex(pre_match)
1134
- object = self.object || @template_object.instance_variable_get("@#{pre_match}")
1135
- if object && object.respond_to?(:to_param)
1136
- object.to_param
1137
- else
1138
- raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
1139
- end
1140
- end
1141
-
1142
- def value(object)
1143
- self.class.value(object, @method_name)
1144
- end
1145
-
1146
- def value_before_type_cast(object)
1147
- self.class.value_before_type_cast(object, @method_name)
1148
- end
1149
-
1150
- class << self
1151
- def value(object, method_name)
1152
- object.send method_name if object
1153
- end
1154
-
1155
- def value_before_type_cast(object, method_name)
1156
- unless object.nil?
1157
- object.respond_to?(method_name + "_before_type_cast") ?
1158
- object.send(method_name + "_before_type_cast") :
1159
- object.send(method_name)
1160
- end
1161
- end
1162
-
1163
- def check_box_checked?(value, checked_value)
1164
- case value
1165
- when TrueClass, FalseClass
1166
- value
1167
- when NilClass
1168
- false
1169
- when Integer
1170
- value != 0
1171
- when String
1172
- value == checked_value
1173
- when Array
1174
- value.include?(checked_value)
1175
- else
1176
- value.to_i != 0
1177
- end
1178
- end
1179
-
1180
- def radio_button_checked?(value, checked_value)
1181
- value.to_s == checked_value.to_s
1182
- end
1183
- end
1184
-
1185
- private
1186
- def add_default_name_and_id_for_value(tag_value, options)
1187
- unless tag_value.nil?
1188
- pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase
1189
- specified_id = options["id"]
1190
- add_default_name_and_id(options)
1191
- options["id"] += "_#{pretty_tag_value}" if specified_id.blank? && options["id"].present?
1192
- else
1193
- add_default_name_and_id(options)
1194
- end
1195
- end
1196
-
1197
- def add_default_name_and_id(options)
1198
- if options.has_key?("index")
1199
- options["name"] ||= tag_name_with_index(options["index"], options["multiple"])
1200
- options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) }
1201
- options.delete("index")
1202
- elsif defined?(@auto_index)
1203
- options["name"] ||= tag_name_with_index(@auto_index, options["multiple"])
1204
- options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
1205
- else
1206
- options["name"] ||= tag_name(options["multiple"])
1207
- options["id"] = options.fetch("id"){ tag_id }
1208
- end
1209
-
1210
- options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence
1211
- end
1212
-
1213
- def tag_name(multiple = false)
1214
- "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}"
1215
- end
1216
-
1217
- def tag_name_with_index(index, multiple = false)
1218
- "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}"
1219
- end
1220
-
1221
- def tag_id
1222
- "#{sanitized_object_name}_#{sanitized_method_name}"
1223
- end
1224
-
1225
- def tag_id_with_index(index)
1226
- "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
1227
- end
1228
-
1229
- def sanitized_object_name
1230
- @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
1231
- end
1232
-
1233
- def sanitized_method_name
1234
- @sanitized_method_name ||= @method_name.sub(/\?$/,"")
1235
- end
1236
- end
1237
-
1238
- class FormBuilder
1239
- # The methods which wrap a form helper call.
1240
- class_attribute :field_helpers
1241
- self.field_helpers = FormHelper.instance_method_names - %w(form_for convert_to_model)
1242
-
1243
- attr_accessor :object_name, :object, :options
1244
-
1245
- attr_reader :multipart, :parent_builder
1246
- alias :multipart? :multipart
1247
-
1248
- def multipart=(multipart)
1249
- @multipart = multipart
1250
- parent_builder.multipart = multipart if parent_builder
1251
- end
1252
-
1253
- def self._to_partial_path
1254
- @_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, '')
1255
- end
1256
-
1257
- def to_partial_path
1258
- self.class._to_partial_path
1259
- end
1260
-
1261
- def to_model
1262
- self
1263
- end
1264
-
1265
- def initialize(object_name, object, template, options, proc)
1266
- @nested_child_index = {}
1267
- @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
1268
- @parent_builder = options[:parent_builder]
1269
- @default_options = @options ? @options.slice(:index, :namespace) : {}
1270
- if @object_name.to_s.match(/\[\]$/)
1271
- if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
1272
- @auto_index = object.to_param
1273
- else
1274
- raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
1275
- end
1276
- end
1277
- @multipart = nil
1278
- end
1279
-
1280
- (field_helpers - %w(label check_box radio_button fields_for hidden_field file_field)).each do |selector|
1281
- class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
1282
- def #{selector}(method, options = {}) # def text_field(method, options = {})
1283
- @template.send( # @template.send(
1284
- #{selector.inspect}, # "text_field",
1285
- @object_name, # @object_name,
1286
- method, # method,
1287
- objectify_options(options)) # objectify_options(options))
1288
- end # end
1289
- RUBY_EVAL
1290
- end
1291
-
1292
- def fields_for(record_name, record_object = nil, fields_options = {}, &block)
1293
- fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
1294
- fields_options[:builder] ||= options[:builder]
1295
- fields_options[:parent_builder] = self
1296
- fields_options[:namespace] = fields_options[:parent_builder].options[:namespace]
1297
-
1298
- case record_name
1299
- when String, Symbol
1300
- if nested_attributes_association?(record_name)
1301
- return fields_for_with_nested_attributes(record_name, record_object, fields_options, block)
1302
- end
1303
- else
1304
- record_object = record_name.is_a?(Array) ? record_name.last : record_name
1305
- record_name = ActiveModel::Naming.param_key(record_object)
1306
- end
1307
-
1308
- index = if options.has_key?(:index)
1309
- "[#{options[:index]}]"
1310
- elsif defined?(@auto_index)
1311
- self.object_name = @object_name.to_s.sub(/\[\]$/,"")
1312
- "[#{@auto_index}]"
1313
- end
1314
- record_name = "#{object_name}#{index}[#{record_name}]"
1315
-
1316
- @template.fields_for(record_name, record_object, fields_options, &block)
1317
- end
1318
-
1319
- def label(method, text = nil, options = {}, &block)
1320
- @template.label(@object_name, method, text, objectify_options(options), &block)
1321
- end
1322
-
1323
- def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
1324
- @template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value)
1325
- end
1326
-
1327
- def radio_button(method, tag_value, options = {})
1328
- @template.radio_button(@object_name, method, tag_value, objectify_options(options))
1329
- end
1330
-
1331
- def hidden_field(method, options = {})
1332
- @emitted_hidden_id = true if method == :id
1333
- @template.hidden_field(@object_name, method, objectify_options(options))
1334
- end
1335
-
1336
- def file_field(method, options = {})
1337
- self.multipart = true
1338
- @template.file_field(@object_name, method, objectify_options(options))
1339
- end
1340
-
1341
- # Add the submit button for the given form. When no value is given, it checks
1342
- # if the object is a new resource or not to create the proper label:
1343
- #
1344
- # <%= form_for @post do |f| %>
1345
- # <%= f.submit %>
1346
- # <% end %>
1347
- #
1348
- # In the example above, if @post is a new record, it will use "Create Post" as
1349
- # submit button label, otherwise, it uses "Update Post".
1350
- #
1351
- # Those labels can be customized using I18n, under the helpers.submit key and accept
1352
- # the %{model} as translation interpolation:
1353
- #
1354
- # en:
1355
- # helpers:
1356
- # submit:
1357
- # create: "Create a %{model}"
1358
- # update: "Confirm changes to %{model}"
1359
- #
1360
- # It also searches for a key specific for the given object:
1361
- #
1362
- # en:
1363
- # helpers:
1364
- # submit:
1365
- # post:
1366
- # create: "Add %{model}"
1367
- #
1368
- def submit(value=nil, options={})
1369
- value, options = nil, value if value.is_a?(Hash)
1370
- value ||= submit_default_value
1371
- @template.submit_tag(value, options)
1372
- end
1373
-
1374
- # Add the submit button for the given form. When no value is given, it checks
1375
- # if the object is a new resource or not to create the proper label:
1376
- #
1377
- # <%= form_for @post do |f| %>
1378
- # <%= f.button %>
1379
- # <% end %>
1380
- #
1381
- # In the example above, if @post is a new record, it will use "Create Post" as
1382
- # submit button label, otherwise, it uses "Update Post".
1383
- #
1384
- # Those labels can be customized using I18n, under the helpers.submit key and accept
1385
- # the %{model} as translation interpolation:
1386
- #
1387
- # en:
1388
- # helpers:
1389
- # button:
1390
- # create: "Create a %{model}"
1391
- # update: "Confirm changes to %{model}"
1392
- #
1393
- # It also searches for a key specific for the given object:
1394
- #
1395
- # en:
1396
- # helpers:
1397
- # button:
1398
- # post:
1399
- # create: "Add %{model}"
1400
- #
1401
- def button(value=nil, options={})
1402
- value, options = nil, value if value.is_a?(Hash)
1403
- value ||= submit_default_value
1404
- @template.button_tag(value, options)
1405
- end
1406
-
1407
- def emitted_hidden_id?
1408
- @emitted_hidden_id ||= nil
1409
- end
1410
-
1411
- private
1412
- def objectify_options(options)
1413
- @default_options.merge(options.merge(:object => @object))
1414
- end
1415
-
1416
- def submit_default_value
1417
- object = convert_to_model(@object)
1418
- key = object ? (object.persisted? ? :update : :create) : :submit
1419
-
1420
- model = if object.class.respond_to?(:model_name)
1421
- object.class.model_name.human
1422
- else
1423
- @object_name.to_s.humanize
1424
- end
1425
-
1426
- defaults = []
1427
- defaults << :"helpers.submit.#{object_name}.#{key}"
1428
- defaults << :"helpers.submit.#{key}"
1429
- defaults << "#{key.to_s.humanize} #{model}"
1430
-
1431
- I18n.t(defaults.shift, :model => model, :default => defaults)
1432
- end
1433
-
1434
- def nested_attributes_association?(association_name)
1435
- @object.respond_to?("#{association_name}_attributes=")
1436
- end
1437
-
1438
- def fields_for_with_nested_attributes(association_name, association, options, block)
1439
- name = "#{object_name}[#{association_name}_attributes]"
1440
- association = convert_to_model(association)
1441
-
1442
- if association.respond_to?(:persisted?)
1443
- association = [association] if @object.send(association_name).is_a?(Array)
1444
- elsif !association.respond_to?(:to_ary)
1445
- association = @object.send(association_name)
1446
- end
1447
-
1448
- if association.respond_to?(:to_ary)
1449
- explicit_child_index = options[:child_index]
1450
- output = ActiveSupport::SafeBuffer.new
1451
- association.each do |child|
1452
- output << fields_for_nested_model("#{name}[#{explicit_child_index || nested_child_index(name)}]", child, options, block)
1453
- end
1454
- output
1455
- elsif association
1456
- fields_for_nested_model(name, association, options, block)
1457
- end
1458
- end
1459
-
1460
- def fields_for_nested_model(name, object, options, block)
1461
- object = convert_to_model(object)
1462
-
1463
- parent_include_id = self.options.fetch(:include_id, true)
1464
- include_id = options.fetch(:include_id, parent_include_id)
1465
- options[:hidden_field_id] = object.persisted? && include_id
1466
- @template.fields_for(name, object, options, &block)
1467
- end
1468
-
1469
- def nested_child_index(name)
1470
- @nested_child_index[name] ||= -1
1471
- @nested_child_index[name] += 1
1472
- end
1473
-
1474
- def convert_to_model(object)
1475
- object.respond_to?(:to_model) ? object.to_model : object
1476
- end
1477
- end
1478
- end
1479
-
1480
- ActiveSupport.on_load(:action_view) do
1481
- class ActionView::Base
1482
- cattr_accessor :default_form_builder
1483
- @@default_form_builder = ::ActionView::Helpers::FormBuilder
1484
- end
1485
- end
1486
- end