actionview 6.0.0

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

Potentially problematic release.


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

Files changed (113) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +271 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +40 -0
  5. data/lib/action_view.rb +98 -0
  6. data/lib/action_view/base.rb +312 -0
  7. data/lib/action_view/buffers.rb +67 -0
  8. data/lib/action_view/cache_expiry.rb +54 -0
  9. data/lib/action_view/context.rb +32 -0
  10. data/lib/action_view/dependency_tracker.rb +175 -0
  11. data/lib/action_view/digestor.rb +126 -0
  12. data/lib/action_view/flows.rb +76 -0
  13. data/lib/action_view/gem_version.rb +17 -0
  14. data/lib/action_view/helpers.rb +66 -0
  15. data/lib/action_view/helpers/active_model_helper.rb +55 -0
  16. data/lib/action_view/helpers/asset_tag_helper.rb +488 -0
  17. data/lib/action_view/helpers/asset_url_helper.rb +470 -0
  18. data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
  19. data/lib/action_view/helpers/cache_helper.rb +271 -0
  20. data/lib/action_view/helpers/capture_helper.rb +216 -0
  21. data/lib/action_view/helpers/controller_helper.rb +36 -0
  22. data/lib/action_view/helpers/csp_helper.rb +26 -0
  23. data/lib/action_view/helpers/csrf_helper.rb +35 -0
  24. data/lib/action_view/helpers/date_helper.rb +1200 -0
  25. data/lib/action_view/helpers/debug_helper.rb +36 -0
  26. data/lib/action_view/helpers/form_helper.rb +2569 -0
  27. data/lib/action_view/helpers/form_options_helper.rb +896 -0
  28. data/lib/action_view/helpers/form_tag_helper.rb +920 -0
  29. data/lib/action_view/helpers/javascript_helper.rb +95 -0
  30. data/lib/action_view/helpers/number_helper.rb +456 -0
  31. data/lib/action_view/helpers/output_safety_helper.rb +70 -0
  32. data/lib/action_view/helpers/rendering_helper.rb +101 -0
  33. data/lib/action_view/helpers/sanitize_helper.rb +171 -0
  34. data/lib/action_view/helpers/tag_helper.rb +314 -0
  35. data/lib/action_view/helpers/tags.rb +44 -0
  36. data/lib/action_view/helpers/tags/base.rb +196 -0
  37. data/lib/action_view/helpers/tags/check_box.rb +66 -0
  38. data/lib/action_view/helpers/tags/checkable.rb +18 -0
  39. data/lib/action_view/helpers/tags/collection_check_boxes.rb +36 -0
  40. data/lib/action_view/helpers/tags/collection_helpers.rb +119 -0
  41. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
  42. data/lib/action_view/helpers/tags/collection_select.rb +30 -0
  43. data/lib/action_view/helpers/tags/color_field.rb +27 -0
  44. data/lib/action_view/helpers/tags/date_field.rb +15 -0
  45. data/lib/action_view/helpers/tags/date_select.rb +74 -0
  46. data/lib/action_view/helpers/tags/datetime_field.rb +32 -0
  47. data/lib/action_view/helpers/tags/datetime_local_field.rb +21 -0
  48. data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
  49. data/lib/action_view/helpers/tags/email_field.rb +10 -0
  50. data/lib/action_view/helpers/tags/file_field.rb +10 -0
  51. data/lib/action_view/helpers/tags/grouped_collection_select.rb +31 -0
  52. data/lib/action_view/helpers/tags/hidden_field.rb +10 -0
  53. data/lib/action_view/helpers/tags/label.rb +81 -0
  54. data/lib/action_view/helpers/tags/month_field.rb +15 -0
  55. data/lib/action_view/helpers/tags/number_field.rb +20 -0
  56. data/lib/action_view/helpers/tags/password_field.rb +14 -0
  57. data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
  58. data/lib/action_view/helpers/tags/radio_button.rb +33 -0
  59. data/lib/action_view/helpers/tags/range_field.rb +10 -0
  60. data/lib/action_view/helpers/tags/search_field.rb +27 -0
  61. data/lib/action_view/helpers/tags/select.rb +43 -0
  62. data/lib/action_view/helpers/tags/tel_field.rb +10 -0
  63. data/lib/action_view/helpers/tags/text_area.rb +24 -0
  64. data/lib/action_view/helpers/tags/text_field.rb +34 -0
  65. data/lib/action_view/helpers/tags/time_field.rb +15 -0
  66. data/lib/action_view/helpers/tags/time_select.rb +10 -0
  67. data/lib/action_view/helpers/tags/time_zone_select.rb +22 -0
  68. data/lib/action_view/helpers/tags/translator.rb +39 -0
  69. data/lib/action_view/helpers/tags/url_field.rb +10 -0
  70. data/lib/action_view/helpers/tags/week_field.rb +15 -0
  71. data/lib/action_view/helpers/text_helper.rb +486 -0
  72. data/lib/action_view/helpers/translation_helper.rb +145 -0
  73. data/lib/action_view/helpers/url_helper.rb +676 -0
  74. data/lib/action_view/layouts.rb +433 -0
  75. data/lib/action_view/locale/en.yml +56 -0
  76. data/lib/action_view/log_subscriber.rb +96 -0
  77. data/lib/action_view/lookup_context.rb +316 -0
  78. data/lib/action_view/model_naming.rb +14 -0
  79. data/lib/action_view/path_set.rb +95 -0
  80. data/lib/action_view/railtie.rb +105 -0
  81. data/lib/action_view/record_identifier.rb +112 -0
  82. data/lib/action_view/renderer/abstract_renderer.rb +108 -0
  83. data/lib/action_view/renderer/partial_renderer.rb +563 -0
  84. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +103 -0
  85. data/lib/action_view/renderer/renderer.rb +68 -0
  86. data/lib/action_view/renderer/streaming_template_renderer.rb +105 -0
  87. data/lib/action_view/renderer/template_renderer.rb +108 -0
  88. data/lib/action_view/rendering.rb +171 -0
  89. data/lib/action_view/routing_url_for.rb +146 -0
  90. data/lib/action_view/tasks/cache_digests.rake +25 -0
  91. data/lib/action_view/template.rb +393 -0
  92. data/lib/action_view/template/error.rb +161 -0
  93. data/lib/action_view/template/handlers.rb +92 -0
  94. data/lib/action_view/template/handlers/builder.rb +25 -0
  95. data/lib/action_view/template/handlers/erb.rb +84 -0
  96. data/lib/action_view/template/handlers/erb/erubi.rb +87 -0
  97. data/lib/action_view/template/handlers/html.rb +11 -0
  98. data/lib/action_view/template/handlers/raw.rb +11 -0
  99. data/lib/action_view/template/html.rb +43 -0
  100. data/lib/action_view/template/inline.rb +22 -0
  101. data/lib/action_view/template/raw_file.rb +28 -0
  102. data/lib/action_view/template/resolver.rb +394 -0
  103. data/lib/action_view/template/sources.rb +13 -0
  104. data/lib/action_view/template/sources/file.rb +17 -0
  105. data/lib/action_view/template/text.rb +35 -0
  106. data/lib/action_view/template/types.rb +57 -0
  107. data/lib/action_view/test_case.rb +300 -0
  108. data/lib/action_view/testing/resolvers.rb +67 -0
  109. data/lib/action_view/unbound_template.rb +32 -0
  110. data/lib/action_view/version.rb +10 -0
  111. data/lib/action_view/view_paths.rb +129 -0
  112. data/lib/assets/compiled/rails-ujs.js +746 -0
  113. metadata +260 -0
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ # = Action View Debug Helper
5
+ #
6
+ # Provides a set of methods for making it easier to debug Rails objects.
7
+ module Helpers #:nodoc:
8
+ module DebugHelper
9
+ include TagHelper
10
+
11
+ # Returns a YAML representation of +object+ wrapped with <pre> and </pre>.
12
+ # If the object cannot be converted to YAML using +to_yaml+, +inspect+ will be called instead.
13
+ # Useful for inspecting an object at the time of rendering.
14
+ #
15
+ # @user = User.new({ username: 'testing', password: 'xyz', age: 42})
16
+ # debug(@user)
17
+ # # =>
18
+ # <pre class='debug_dump'>--- !ruby/object:User
19
+ # attributes:
20
+ # updated_at:
21
+ # username: testing
22
+ # age: 42
23
+ # password: xyz
24
+ # created_at:
25
+ # </pre>
26
+ def debug(object)
27
+ Marshal.dump(object)
28
+ object = ERB::Util.html_escape(object.to_yaml)
29
+ content_tag(:pre, object, class: "debug_dump")
30
+ rescue # errors from Marshal or YAML
31
+ # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
32
+ content_tag(:code, object.inspect, class: "debug_dump")
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,2569 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cgi"
4
+ require "action_view/helpers/date_helper"
5
+ require "action_view/helpers/tag_helper"
6
+ require "action_view/helpers/form_tag_helper"
7
+ require "action_view/helpers/active_model_helper"
8
+ require "action_view/model_naming"
9
+ require "action_view/record_identifier"
10
+ require "active_support/core_ext/module/attribute_accessors"
11
+ require "active_support/core_ext/hash/slice"
12
+ require "active_support/core_ext/string/output_safety"
13
+ require "active_support/core_ext/string/inflections"
14
+
15
+ module ActionView
16
+ # = Action View Form Helpers
17
+ module Helpers #:nodoc:
18
+ # Form helpers are designed to make working with resources much easier
19
+ # compared to using vanilla HTML.
20
+ #
21
+ # Typically, a form designed to create or update a resource reflects the
22
+ # identity of the resource in several ways: (i) the URL that the form is
23
+ # sent to (the form element's +action+ attribute) should result in a request
24
+ # being routed to the appropriate controller action (with the appropriate <tt>:id</tt>
25
+ # parameter in the case of an existing resource), (ii) input fields should
26
+ # be named in such a way that in the controller their values appear in the
27
+ # appropriate places within the +params+ hash, and (iii) for an existing record,
28
+ # when the form is initially displayed, input fields corresponding to attributes
29
+ # of the resource should show the current values of those attributes.
30
+ #
31
+ # In Rails, this is usually achieved by creating the form using +form_for+ and
32
+ # a number of related helper methods. +form_for+ generates an appropriate <tt>form</tt>
33
+ # tag and yields a form builder object that knows the model the form is about.
34
+ # Input fields are created by calling methods defined on the form builder, which
35
+ # means they are able to generate the appropriate names and default values
36
+ # corresponding to the model attributes, as well as convenient IDs, etc.
37
+ # Conventions in the generated field names allow controllers to receive form data
38
+ # nicely structured in +params+ with no effort on your side.
39
+ #
40
+ # For example, to create a new person you typically set up a new instance of
41
+ # +Person+ in the <tt>PeopleController#new</tt> action, <tt>@person</tt>, and
42
+ # in the view template pass that object to +form_for+:
43
+ #
44
+ # <%= form_for @person do |f| %>
45
+ # <%= f.label :first_name %>:
46
+ # <%= f.text_field :first_name %><br />
47
+ #
48
+ # <%= f.label :last_name %>:
49
+ # <%= f.text_field :last_name %><br />
50
+ #
51
+ # <%= f.submit %>
52
+ # <% end %>
53
+ #
54
+ # The HTML generated for this would be (modulus formatting):
55
+ #
56
+ # <form action="/people" class="new_person" id="new_person" method="post">
57
+ # <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
58
+ # <label for="person_first_name">First name</label>:
59
+ # <input id="person_first_name" name="person[first_name]" type="text" /><br />
60
+ #
61
+ # <label for="person_last_name">Last name</label>:
62
+ # <input id="person_last_name" name="person[last_name]" type="text" /><br />
63
+ #
64
+ # <input name="commit" type="submit" value="Create Person" />
65
+ # </form>
66
+ #
67
+ # As you see, the HTML reflects knowledge about the resource in several spots,
68
+ # like the path the form should be submitted to, or the names of the input fields.
69
+ #
70
+ # In particular, thanks to the conventions followed in the generated field names, the
71
+ # controller gets a nested hash <tt>params[:person]</tt> with the person attributes
72
+ # set in the form. That hash is ready to be passed to <tt>Person.new</tt>:
73
+ #
74
+ # @person = Person.new(params[:person])
75
+ # if @person.save
76
+ # # success
77
+ # else
78
+ # # error handling
79
+ # end
80
+ #
81
+ # Interestingly, the exact same view code in the previous example can be used to edit
82
+ # a person. If <tt>@person</tt> is an existing record with name "John Smith" and ID 256,
83
+ # the code above as is would yield instead:
84
+ #
85
+ # <form action="/people/256" class="edit_person" id="edit_person_256" method="post">
86
+ # <input name="_method" type="hidden" value="patch" />
87
+ # <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
88
+ # <label for="person_first_name">First name</label>:
89
+ # <input id="person_first_name" name="person[first_name]" type="text" value="John" /><br />
90
+ #
91
+ # <label for="person_last_name">Last name</label>:
92
+ # <input id="person_last_name" name="person[last_name]" type="text" value="Smith" /><br />
93
+ #
94
+ # <input name="commit" type="submit" value="Update Person" />
95
+ # </form>
96
+ #
97
+ # Note that the endpoint, default values, and submit button label are tailored for <tt>@person</tt>.
98
+ # That works that way because the involved helpers know whether the resource is a new record or not,
99
+ # and generate HTML accordingly.
100
+ #
101
+ # The controller would receive the form data again in <tt>params[:person]</tt>, ready to be
102
+ # passed to <tt>Person#update</tt>:
103
+ #
104
+ # if @person.update(params[:person])
105
+ # # success
106
+ # else
107
+ # # error handling
108
+ # end
109
+ #
110
+ # That's how you typically work with resources.
111
+ module FormHelper
112
+ extend ActiveSupport::Concern
113
+
114
+ include FormTagHelper
115
+ include UrlHelper
116
+ include ModelNaming
117
+ include RecordIdentifier
118
+
119
+ attr_internal :default_form_builder
120
+
121
+ # Creates a form that allows the user to create or update the attributes
122
+ # of a specific model object.
123
+ #
124
+ # The method can be used in several slightly different ways, depending on
125
+ # how much you wish to rely on Rails to infer automatically from the model
126
+ # how the form should be constructed. For a generic model object, a form
127
+ # can be created by passing +form_for+ a string or symbol representing
128
+ # the object we are concerned with:
129
+ #
130
+ # <%= form_for :person do |f| %>
131
+ # First name: <%= f.text_field :first_name %><br />
132
+ # Last name : <%= f.text_field :last_name %><br />
133
+ # Biography : <%= f.text_area :biography %><br />
134
+ # Admin? : <%= f.check_box :admin %><br />
135
+ # <%= f.submit %>
136
+ # <% end %>
137
+ #
138
+ # The variable +f+ yielded to the block is a FormBuilder object that
139
+ # incorporates the knowledge about the model object represented by
140
+ # <tt>:person</tt> passed to +form_for+. Methods defined on the FormBuilder
141
+ # are used to generate fields bound to this model. Thus, for example,
142
+ #
143
+ # <%= f.text_field :first_name %>
144
+ #
145
+ # will get expanded to
146
+ #
147
+ # <%= text_field :person, :first_name %>
148
+ #
149
+ # which results in an HTML <tt><input></tt> tag whose +name+ attribute is
150
+ # <tt>person[first_name]</tt>. This means that when the form is submitted,
151
+ # the value entered by the user will be available in the controller as
152
+ # <tt>params[:person][:first_name]</tt>.
153
+ #
154
+ # For fields generated in this way using the FormBuilder,
155
+ # if <tt>:person</tt> also happens to be the name of an instance variable
156
+ # <tt>@person</tt>, the default value of the field shown when the form is
157
+ # initially displayed (e.g. in the situation where you are editing an
158
+ # existing record) will be the value of the corresponding attribute of
159
+ # <tt>@person</tt>.
160
+ #
161
+ # The rightmost argument to +form_for+ is an
162
+ # optional hash of options -
163
+ #
164
+ # * <tt>:url</tt> - The URL the form is to be submitted to. This may be
165
+ # represented in the same way as values passed to +url_for+ or +link_to+.
166
+ # So for example you may use a named route directly. When the model is
167
+ # represented by a string or symbol, as in the example above, if the
168
+ # <tt>:url</tt> option is not specified, by default the form will be
169
+ # sent back to the current URL (We will describe below an alternative
170
+ # resource-oriented usage of +form_for+ in which the URL does not need
171
+ # to be specified explicitly).
172
+ # * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of
173
+ # id attributes on form elements. The namespace attribute will be prefixed
174
+ # with underscore on the generated HTML id.
175
+ # * <tt>:method</tt> - The method to use when submitting the form, usually
176
+ # either "get" or "post". If "patch", "put", "delete", or another verb
177
+ # is used, a hidden input with name <tt>_method</tt> is added to
178
+ # simulate the verb over post.
179
+ # * <tt>:authenticity_token</tt> - Authenticity token to use in the form.
180
+ # Use only if you need to pass custom authenticity token string, or to
181
+ # not add authenticity_token field at all (by passing <tt>false</tt>).
182
+ # Remote forms may omit the embedded authenticity token by setting
183
+ # <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>.
184
+ # This is helpful when you're fragment-caching the form. Remote forms
185
+ # get the authenticity token from the <tt>meta</tt> tag, so embedding is
186
+ # unnecessary unless you support browsers without JavaScript.
187
+ # * <tt>:remote</tt> - If set to true, will allow the Unobtrusive
188
+ # JavaScript drivers to control the submit behavior. By default this
189
+ # behavior is an ajax submit.
190
+ # * <tt>:enforce_utf8</tt> - If set to false, a hidden input with name
191
+ # utf8 is not output.
192
+ # * <tt>:html</tt> - Optional HTML attributes for the form tag.
193
+ #
194
+ # Also note that +form_for+ doesn't create an exclusive scope. It's still
195
+ # possible to use both the stand-alone FormHelper methods and methods
196
+ # from FormTagHelper. For example:
197
+ #
198
+ # <%= form_for :person do |f| %>
199
+ # First name: <%= f.text_field :first_name %>
200
+ # Last name : <%= f.text_field :last_name %>
201
+ # Biography : <%= text_area :person, :biography %>
202
+ # Admin? : <%= check_box_tag "person[admin]", "1", @person.company.admin? %>
203
+ # <%= f.submit %>
204
+ # <% end %>
205
+ #
206
+ # This also works for the methods in FormOptionsHelper and DateHelper that
207
+ # are designed to work with an object as base, like
208
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
209
+ #
210
+ # === #form_for with a model object
211
+ #
212
+ # In the examples above, the object to be created or edited was
213
+ # represented by a symbol passed to +form_for+, and we noted that
214
+ # a string can also be used equivalently. It is also possible, however,
215
+ # to pass a model object itself to +form_for+. For example, if <tt>@post</tt>
216
+ # is an existing record you wish to edit, you can create the form using
217
+ #
218
+ # <%= form_for @post do |f| %>
219
+ # ...
220
+ # <% end %>
221
+ #
222
+ # This behaves in almost the same way as outlined previously, with a
223
+ # couple of small exceptions. First, the prefix used to name the input
224
+ # elements within the form (hence the key that denotes them in the +params+
225
+ # hash) is actually derived from the object's _class_, e.g. <tt>params[:post]</tt>
226
+ # if the object's class is +Post+. However, this can be overwritten using
227
+ # the <tt>:as</tt> option, e.g. -
228
+ #
229
+ # <%= form_for(@person, as: :client) do |f| %>
230
+ # ...
231
+ # <% end %>
232
+ #
233
+ # would result in <tt>params[:client]</tt>.
234
+ #
235
+ # Secondly, the field values shown when the form is initially displayed
236
+ # are taken from the attributes of the object passed to +form_for+,
237
+ # regardless of whether the object is an instance
238
+ # variable. So, for example, if we had a _local_ variable +post+
239
+ # representing an existing record,
240
+ #
241
+ # <%= form_for post do |f| %>
242
+ # ...
243
+ # <% end %>
244
+ #
245
+ # would produce a form with fields whose initial state reflect the current
246
+ # values of the attributes of +post+.
247
+ #
248
+ # === Resource-oriented style
249
+ #
250
+ # In the examples just shown, although not indicated explicitly, we still
251
+ # need to use the <tt>:url</tt> option in order to specify where the
252
+ # form is going to be sent. However, further simplification is possible
253
+ # if the record passed to +form_for+ is a _resource_, i.e. it corresponds
254
+ # to a set of RESTful routes, e.g. defined using the +resources+ method
255
+ # in <tt>config/routes.rb</tt>. In this case Rails will simply infer the
256
+ # appropriate URL from the record itself. For example,
257
+ #
258
+ # <%= form_for @post do |f| %>
259
+ # ...
260
+ # <% end %>
261
+ #
262
+ # is then equivalent to something like:
263
+ #
264
+ # <%= form_for @post, as: :post, url: post_path(@post), method: :patch, html: { class: "edit_post", id: "edit_post_45" } do |f| %>
265
+ # ...
266
+ # <% end %>
267
+ #
268
+ # And for a new record
269
+ #
270
+ # <%= form_for(Post.new) do |f| %>
271
+ # ...
272
+ # <% end %>
273
+ #
274
+ # is equivalent to something like:
275
+ #
276
+ # <%= form_for @post, as: :post, url: posts_path, html: { class: "new_post", id: "new_post" } do |f| %>
277
+ # ...
278
+ # <% end %>
279
+ #
280
+ # However you can still overwrite individual conventions, such as:
281
+ #
282
+ # <%= form_for(@post, url: super_posts_path) do |f| %>
283
+ # ...
284
+ # <% end %>
285
+ #
286
+ # You can also set the answer format, like this:
287
+ #
288
+ # <%= form_for(@post, format: :json) do |f| %>
289
+ # ...
290
+ # <% end %>
291
+ #
292
+ # For namespaced routes, like +admin_post_url+:
293
+ #
294
+ # <%= form_for([:admin, @post]) do |f| %>
295
+ # ...
296
+ # <% end %>
297
+ #
298
+ # If your resource has associations defined, for example, you want to add comments
299
+ # to the document given that the routes are set correctly:
300
+ #
301
+ # <%= form_for([@document, @comment]) do |f| %>
302
+ # ...
303
+ # <% end %>
304
+ #
305
+ # Where <tt>@document = Document.find(params[:id])</tt> and
306
+ # <tt>@comment = Comment.new</tt>.
307
+ #
308
+ # === Setting the method
309
+ #
310
+ # You can force the form to use the full array of HTTP verbs by setting
311
+ #
312
+ # method: (:get|:post|:patch|:put|:delete)
313
+ #
314
+ # in the options hash. If the verb is not GET or POST, which are natively
315
+ # supported by HTML forms, the form will be set to POST and a hidden input
316
+ # called _method will carry the intended verb for the server to interpret.
317
+ #
318
+ # === Unobtrusive JavaScript
319
+ #
320
+ # Specifying:
321
+ #
322
+ # remote: true
323
+ #
324
+ # in the options hash creates a form that will allow the unobtrusive JavaScript drivers to modify its
325
+ # behavior. The expected default behavior is an XMLHttpRequest in the background instead of the regular
326
+ # POST arrangement, but ultimately the behavior is the choice of the JavaScript driver implementor.
327
+ # Even though it's using JavaScript to serialize the form elements, the form submission will work just like
328
+ # a regular submission as viewed by the receiving side (all elements available in <tt>params</tt>).
329
+ #
330
+ # Example:
331
+ #
332
+ # <%= form_for(@post, remote: true) do |f| %>
333
+ # ...
334
+ # <% end %>
335
+ #
336
+ # The HTML generated for this would be:
337
+ #
338
+ # <form action='http://www.example.com' method='post' data-remote='true'>
339
+ # <input name='_method' type='hidden' value='patch' />
340
+ # ...
341
+ # </form>
342
+ #
343
+ # === Setting HTML options
344
+ #
345
+ # You can set data attributes directly by passing in a data hash, but all other HTML options must be wrapped in
346
+ # the HTML key. Example:
347
+ #
348
+ # <%= form_for(@post, data: { behavior: "autosave" }, html: { name: "go" }) do |f| %>
349
+ # ...
350
+ # <% end %>
351
+ #
352
+ # The HTML generated for this would be:
353
+ #
354
+ # <form action='http://www.example.com' method='post' data-behavior='autosave' name='go'>
355
+ # <input name='_method' type='hidden' value='patch' />
356
+ # ...
357
+ # </form>
358
+ #
359
+ # === Removing hidden model id's
360
+ #
361
+ # The form_for method automatically includes the model id as a hidden field in the form.
362
+ # This is used to maintain the correlation between the form data and its associated model.
363
+ # Some ORM systems do not use IDs on nested models so in this case you want to be able
364
+ # to disable the hidden id.
365
+ #
366
+ # In the following example the Post model has many Comments stored within it in a NoSQL database,
367
+ # thus there is no primary key for comments.
368
+ #
369
+ # Example:
370
+ #
371
+ # <%= form_for(@post) do |f| %>
372
+ # <%= f.fields_for(:comments, include_id: false) do |cf| %>
373
+ # ...
374
+ # <% end %>
375
+ # <% end %>
376
+ #
377
+ # === Customized form builders
378
+ #
379
+ # You can also build forms using a customized FormBuilder class. Subclass
380
+ # FormBuilder and override or define some more helpers, then use your
381
+ # custom builder. For example, let's say you made a helper to
382
+ # automatically add labels to form inputs.
383
+ #
384
+ # <%= form_for @person, url: { action: "create" }, builder: LabellingFormBuilder do |f| %>
385
+ # <%= f.text_field :first_name %>
386
+ # <%= f.text_field :last_name %>
387
+ # <%= f.text_area :biography %>
388
+ # <%= f.check_box :admin %>
389
+ # <%= f.submit %>
390
+ # <% end %>
391
+ #
392
+ # In this case, if you use this:
393
+ #
394
+ # <%= render f %>
395
+ #
396
+ # The rendered template is <tt>people/_labelling_form</tt> and the local
397
+ # variable referencing the form builder is called
398
+ # <tt>labelling_form</tt>.
399
+ #
400
+ # The custom FormBuilder class is automatically merged with the options
401
+ # of a nested fields_for call, unless it's explicitly set.
402
+ #
403
+ # In many cases you will want to wrap the above in another helper, so you
404
+ # could do something like the following:
405
+ #
406
+ # def labelled_form_for(record_or_name_or_array, *args, &block)
407
+ # options = args.extract_options!
408
+ # form_for(record_or_name_or_array, *(args << options.merge(builder: LabellingFormBuilder)), &block)
409
+ # end
410
+ #
411
+ # If you don't need to attach a form to a model instance, then check out
412
+ # FormTagHelper#form_tag.
413
+ #
414
+ # === Form to external resources
415
+ #
416
+ # When you build forms to external resources sometimes you need to set an authenticity token or just render a form
417
+ # without it, for example when you submit data to a payment gateway number and types of fields could be limited.
418
+ #
419
+ # To set an authenticity token you need to pass an <tt>:authenticity_token</tt> parameter
420
+ #
421
+ # <%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f| %>
422
+ # ...
423
+ # <% end %>
424
+ #
425
+ # If you don't want to an authenticity token field be rendered at all just pass <tt>false</tt>:
426
+ #
427
+ # <%= form_for @invoice, url: external_url, authenticity_token: false do |f| %>
428
+ # ...
429
+ # <% end %>
430
+ def form_for(record, options = {}, &block)
431
+ raise ArgumentError, "Missing block" unless block_given?
432
+ html_options = options[:html] ||= {}
433
+
434
+ case record
435
+ when String, Symbol
436
+ object_name = record
437
+ object = nil
438
+ else
439
+ object = record.is_a?(Array) ? record.last : record
440
+ raise ArgumentError, "First argument in form cannot contain nil or be empty" unless object
441
+ object_name = options[:as] || model_name_from_record_or_class(object).param_key
442
+ apply_form_for_options!(record, object, options)
443
+ end
444
+
445
+ html_options[:data] = options.delete(:data) if options.has_key?(:data)
446
+ html_options[:remote] = options.delete(:remote) if options.has_key?(:remote)
447
+ html_options[:method] = options.delete(:method) if options.has_key?(:method)
448
+ html_options[:enforce_utf8] = options.delete(:enforce_utf8) if options.has_key?(:enforce_utf8)
449
+ html_options[:authenticity_token] = options.delete(:authenticity_token)
450
+
451
+ builder = instantiate_builder(object_name, object, options)
452
+ output = capture(builder, &block)
453
+ html_options[:multipart] ||= builder.multipart?
454
+
455
+ html_options = html_options_for_form(options[:url] || {}, html_options)
456
+ form_tag_with_body(html_options, output)
457
+ end
458
+
459
+ def apply_form_for_options!(record, object, options) #:nodoc:
460
+ object = convert_to_model(object)
461
+
462
+ as = options[:as]
463
+ namespace = options[:namespace]
464
+ action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :patch] : [:new, :post]
465
+ options[:html].reverse_merge!(
466
+ class: as ? "#{action}_#{as}" : dom_class(object, action),
467
+ id: (as ? [namespace, action, as] : [namespace, dom_id(object, action)]).compact.join("_").presence,
468
+ method: method
469
+ )
470
+
471
+ options[:url] ||= if options.key?(:format)
472
+ polymorphic_path(record, format: options.delete(:format))
473
+ else
474
+ polymorphic_path(record, {})
475
+ end
476
+ end
477
+ private :apply_form_for_options!
478
+
479
+ mattr_accessor :form_with_generates_remote_forms, default: true
480
+
481
+ mattr_accessor :form_with_generates_ids, default: false
482
+
483
+ # Creates a form tag based on mixing URLs, scopes, or models.
484
+ #
485
+ # # Using just a URL:
486
+ # <%= form_with url: posts_path do |form| %>
487
+ # <%= form.text_field :title %>
488
+ # <% end %>
489
+ # # =>
490
+ # <form action="/posts" method="post" data-remote="true">
491
+ # <input type="text" name="title">
492
+ # </form>
493
+ #
494
+ # # Adding a scope prefixes the input field names:
495
+ # <%= form_with scope: :post, url: posts_path do |form| %>
496
+ # <%= form.text_field :title %>
497
+ # <% end %>
498
+ # # =>
499
+ # <form action="/posts" method="post" data-remote="true">
500
+ # <input type="text" name="post[title]">
501
+ # </form>
502
+ #
503
+ # # Using a model infers both the URL and scope:
504
+ # <%= form_with model: Post.new do |form| %>
505
+ # <%= form.text_field :title %>
506
+ # <% end %>
507
+ # # =>
508
+ # <form action="/posts" method="post" data-remote="true">
509
+ # <input type="text" name="post[title]">
510
+ # </form>
511
+ #
512
+ # # An existing model makes an update form and fills out field values:
513
+ # <%= form_with model: Post.first do |form| %>
514
+ # <%= form.text_field :title %>
515
+ # <% end %>
516
+ # # =>
517
+ # <form action="/posts/1" method="post" data-remote="true">
518
+ # <input type="hidden" name="_method" value="patch">
519
+ # <input type="text" name="post[title]" value="<the title of the post>">
520
+ # </form>
521
+ #
522
+ # # Though the fields don't have to correspond to model attributes:
523
+ # <%= form_with model: Cat.new do |form| %>
524
+ # <%= form.text_field :cats_dont_have_gills %>
525
+ # <%= form.text_field :but_in_forms_they_can %>
526
+ # <% end %>
527
+ # # =>
528
+ # <form action="/cats" method="post" data-remote="true">
529
+ # <input type="text" name="cat[cats_dont_have_gills]">
530
+ # <input type="text" name="cat[but_in_forms_they_can]">
531
+ # </form>
532
+ #
533
+ # The parameters in the forms are accessible in controllers according to
534
+ # their name nesting. So inputs named +title+ and <tt>post[title]</tt> are
535
+ # accessible as <tt>params[:title]</tt> and <tt>params[:post][:title]</tt>
536
+ # respectively.
537
+ #
538
+ # By default +form_with+ attaches the <tt>data-remote</tt> attribute
539
+ # submitting the form via an XMLHTTPRequest in the background if an
540
+ # Unobtrusive JavaScript driver, like rails-ujs, is used. See the
541
+ # <tt>:local</tt> option for more.
542
+ #
543
+ # For ease of comparison the examples above left out the submit button,
544
+ # as well as the auto generated hidden fields that enable UTF-8 support
545
+ # and adds an authenticity token needed for cross site request forgery
546
+ # protection.
547
+ #
548
+ # === Resource-oriented style
549
+ #
550
+ # In many of the examples just shown, the +:model+ passed to +form_with+
551
+ # is a _resource_. It corresponds to a set of RESTful routes, most likely
552
+ # defined via +resources+ in <tt>config/routes.rb</tt>.
553
+ #
554
+ # So when passing such a model record, Rails infers the URL and method.
555
+ #
556
+ # <%= form_with model: @post do |form| %>
557
+ # ...
558
+ # <% end %>
559
+ #
560
+ # is then equivalent to something like:
561
+ #
562
+ # <%= form_with scope: :post, url: post_path(@post), method: :patch do |form| %>
563
+ # ...
564
+ # <% end %>
565
+ #
566
+ # And for a new record
567
+ #
568
+ # <%= form_with model: Post.new do |form| %>
569
+ # ...
570
+ # <% end %>
571
+ #
572
+ # is equivalent to something like:
573
+ #
574
+ # <%= form_with scope: :post, url: posts_path do |form| %>
575
+ # ...
576
+ # <% end %>
577
+ #
578
+ # ==== +form_with+ options
579
+ #
580
+ # * <tt>:url</tt> - The URL the form submits to. Akin to values passed to
581
+ # +url_for+ or +link_to+. For example, you may use a named route
582
+ # directly. When a <tt>:scope</tt> is passed without a <tt>:url</tt> the
583
+ # form just submits to the current URL.
584
+ # * <tt>:method</tt> - The method to use when submitting the form, usually
585
+ # either "get" or "post". If "patch", "put", "delete", or another verb
586
+ # is used, a hidden input named <tt>_method</tt> is added to
587
+ # simulate the verb over post.
588
+ # * <tt>:format</tt> - The format of the route the form submits to.
589
+ # Useful when submitting to another resource type, like <tt>:json</tt>.
590
+ # Skipped if a <tt>:url</tt> is passed.
591
+ # * <tt>:scope</tt> - The scope to prefix input field names with and
592
+ # thereby how the submitted parameters are grouped in controllers.
593
+ # * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of
594
+ # id attributes on form elements. The namespace attribute will be prefixed
595
+ # with underscore on the generated HTML id.
596
+ # * <tt>:model</tt> - A model object to infer the <tt>:url</tt> and
597
+ # <tt>:scope</tt> by, plus fill out input field values.
598
+ # So if a +title+ attribute is set to "Ahoy!" then a +title+ input
599
+ # field's value would be "Ahoy!".
600
+ # If the model is a new record a create form is generated, if an
601
+ # existing record, however, an update form is generated.
602
+ # Pass <tt>:scope</tt> or <tt>:url</tt> to override the defaults.
603
+ # E.g. turn <tt>params[:post]</tt> into <tt>params[:article]</tt>.
604
+ # * <tt>:authenticity_token</tt> - Authenticity token to use in the form.
605
+ # Override with a custom authenticity token or pass <tt>false</tt> to
606
+ # skip the authenticity token field altogether.
607
+ # Useful when submitting to an external resource like a payment gateway
608
+ # that might limit the valid fields.
609
+ # Remote forms may omit the embedded authenticity token by setting
610
+ # <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>.
611
+ # This is helpful when fragment-caching the form. Remote forms
612
+ # get the authenticity token from the <tt>meta</tt> tag, so embedding is
613
+ # unnecessary unless you support browsers without JavaScript.
614
+ # * <tt>:local</tt> - By default form submits are remote and unobtrusive XHRs.
615
+ # Disable remote submits with <tt>local: true</tt>.
616
+ # * <tt>:skip_enforcing_utf8</tt> - If set to true, a hidden input with name
617
+ # utf8 is not output.
618
+ # * <tt>:builder</tt> - Override the object used to build the form.
619
+ # * <tt>:id</tt> - Optional HTML id attribute.
620
+ # * <tt>:class</tt> - Optional HTML class attribute.
621
+ # * <tt>:data</tt> - Optional HTML data attributes.
622
+ # * <tt>:html</tt> - Other optional HTML attributes for the form tag.
623
+ #
624
+ # === Examples
625
+ #
626
+ # When not passing a block, +form_with+ just generates an opening form tag.
627
+ #
628
+ # <%= form_with(model: @post, url: super_posts_path) %>
629
+ # <%= form_with(model: @post, scope: :article) %>
630
+ # <%= form_with(model: @post, format: :json) %>
631
+ # <%= form_with(model: @post, authenticity_token: false) %> # Disables the token.
632
+ #
633
+ # For namespaced routes, like +admin_post_url+:
634
+ #
635
+ # <%= form_with(model: [ :admin, @post ]) do |form| %>
636
+ # ...
637
+ # <% end %>
638
+ #
639
+ # If your resource has associations defined, for example, you want to add comments
640
+ # to the document given that the routes are set correctly:
641
+ #
642
+ # <%= form_with(model: [ @document, Comment.new ]) do |form| %>
643
+ # ...
644
+ # <% end %>
645
+ #
646
+ # Where <tt>@document = Document.find(params[:id])</tt>.
647
+ #
648
+ # === Mixing with other form helpers
649
+ #
650
+ # While +form_with+ uses a FormBuilder object it's possible to mix and
651
+ # match the stand-alone FormHelper methods and methods
652
+ # from FormTagHelper:
653
+ #
654
+ # <%= form_with scope: :person do |form| %>
655
+ # <%= form.text_field :first_name %>
656
+ # <%= form.text_field :last_name %>
657
+ #
658
+ # <%= text_area :person, :biography %>
659
+ # <%= check_box_tag "person[admin]", "1", @person.company.admin? %>
660
+ #
661
+ # <%= form.submit %>
662
+ # <% end %>
663
+ #
664
+ # Same goes for the methods in FormOptionsHelper and DateHelper designed
665
+ # to work with an object as a base, like
666
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
667
+ #
668
+ # === Setting the method
669
+ #
670
+ # You can force the form to use the full array of HTTP verbs by setting
671
+ #
672
+ # method: (:get|:post|:patch|:put|:delete)
673
+ #
674
+ # in the options hash. If the verb is not GET or POST, which are natively
675
+ # supported by HTML forms, the form will be set to POST and a hidden input
676
+ # called _method will carry the intended verb for the server to interpret.
677
+ #
678
+ # === Setting HTML options
679
+ #
680
+ # You can set data attributes directly in a data hash, but HTML options
681
+ # besides id and class must be wrapped in an HTML key:
682
+ #
683
+ # <%= form_with(model: @post, data: { behavior: "autosave" }, html: { name: "go" }) do |form| %>
684
+ # ...
685
+ # <% end %>
686
+ #
687
+ # generates
688
+ #
689
+ # <form action="/posts/123" method="post" data-behavior="autosave" name="go">
690
+ # <input name="_method" type="hidden" value="patch" />
691
+ # ...
692
+ # </form>
693
+ #
694
+ # === Removing hidden model id's
695
+ #
696
+ # The +form_with+ method automatically includes the model id as a hidden field in the form.
697
+ # This is used to maintain the correlation between the form data and its associated model.
698
+ # Some ORM systems do not use IDs on nested models so in this case you want to be able
699
+ # to disable the hidden id.
700
+ #
701
+ # In the following example the Post model has many Comments stored within it in a NoSQL database,
702
+ # thus there is no primary key for comments.
703
+ #
704
+ # <%= form_with(model: @post) do |form| %>
705
+ # <%= form.fields(:comments, skip_id: true) do |fields| %>
706
+ # ...
707
+ # <% end %>
708
+ # <% end %>
709
+ #
710
+ # === Customized form builders
711
+ #
712
+ # You can also build forms using a customized FormBuilder class. Subclass
713
+ # FormBuilder and override or define some more helpers, then use your
714
+ # custom builder. For example, let's say you made a helper to
715
+ # automatically add labels to form inputs.
716
+ #
717
+ # <%= form_with model: @person, url: { action: "create" }, builder: LabellingFormBuilder do |form| %>
718
+ # <%= form.text_field :first_name %>
719
+ # <%= form.text_field :last_name %>
720
+ # <%= form.text_area :biography %>
721
+ # <%= form.check_box :admin %>
722
+ # <%= form.submit %>
723
+ # <% end %>
724
+ #
725
+ # In this case, if you use:
726
+ #
727
+ # <%= render form %>
728
+ #
729
+ # The rendered template is <tt>people/_labelling_form</tt> and the local
730
+ # variable referencing the form builder is called
731
+ # <tt>labelling_form</tt>.
732
+ #
733
+ # The custom FormBuilder class is automatically merged with the options
734
+ # of a nested +fields+ call, unless it's explicitly set.
735
+ #
736
+ # In many cases you will want to wrap the above in another helper, so you
737
+ # could do something like the following:
738
+ #
739
+ # def labelled_form_with(**options, &block)
740
+ # form_with(**options.merge(builder: LabellingFormBuilder), &block)
741
+ # end
742
+ def form_with(model: nil, scope: nil, url: nil, format: nil, **options, &block)
743
+ options[:allow_method_names_outside_object] = true
744
+ options[:skip_default_ids] = !form_with_generates_ids
745
+
746
+ if model
747
+ url ||= polymorphic_path(model, format: format)
748
+
749
+ model = model.last if model.is_a?(Array)
750
+ scope ||= model_name_from_record_or_class(model).param_key
751
+ end
752
+
753
+ if block_given?
754
+ builder = instantiate_builder(scope, model, options)
755
+ output = capture(builder, &block)
756
+ options[:multipart] ||= builder.multipart?
757
+
758
+ html_options = html_options_for_form_with(url, model, options)
759
+ form_tag_with_body(html_options, output)
760
+ else
761
+ html_options = html_options_for_form_with(url, model, options)
762
+ form_tag_html(html_options)
763
+ end
764
+ end
765
+
766
+ # Creates a scope around a specific model object like form_for, but
767
+ # doesn't create the form tags themselves. This makes fields_for suitable
768
+ # for specifying additional model objects in the same form.
769
+ #
770
+ # Although the usage and purpose of +fields_for+ is similar to +form_for+'s,
771
+ # its method signature is slightly different. Like +form_for+, it yields
772
+ # a FormBuilder object associated with a particular model object to a block,
773
+ # and within the block allows methods to be called on the builder to
774
+ # generate fields associated with the model object. Fields may reflect
775
+ # a model object in two ways - how they are named (hence how submitted
776
+ # values appear within the +params+ hash in the controller) and what
777
+ # default values are shown when the form the fields appear in is first
778
+ # displayed. In order for both of these features to be specified independently,
779
+ # both an object name (represented by either a symbol or string) and the
780
+ # object itself can be passed to the method separately -
781
+ #
782
+ # <%= form_for @person do |person_form| %>
783
+ # First name: <%= person_form.text_field :first_name %>
784
+ # Last name : <%= person_form.text_field :last_name %>
785
+ #
786
+ # <%= fields_for :permission, @person.permission do |permission_fields| %>
787
+ # Admin? : <%= permission_fields.check_box :admin %>
788
+ # <% end %>
789
+ #
790
+ # <%= person_form.submit %>
791
+ # <% end %>
792
+ #
793
+ # In this case, the checkbox field will be represented by an HTML +input+
794
+ # tag with the +name+ attribute <tt>permission[admin]</tt>, and the submitted
795
+ # value will appear in the controller as <tt>params[:permission][:admin]</tt>.
796
+ # If <tt>@person.permission</tt> is an existing record with an attribute
797
+ # +admin+, the initial state of the checkbox when first displayed will
798
+ # reflect the value of <tt>@person.permission.admin</tt>.
799
+ #
800
+ # Often this can be simplified by passing just the name of the model
801
+ # object to +fields_for+ -
802
+ #
803
+ # <%= fields_for :permission do |permission_fields| %>
804
+ # Admin?: <%= permission_fields.check_box :admin %>
805
+ # <% end %>
806
+ #
807
+ # ...in which case, if <tt>:permission</tt> also happens to be the name of an
808
+ # instance variable <tt>@permission</tt>, the initial state of the input
809
+ # field will reflect the value of that variable's attribute <tt>@permission.admin</tt>.
810
+ #
811
+ # Alternatively, you can pass just the model object itself (if the first
812
+ # argument isn't a string or symbol +fields_for+ will realize that the
813
+ # name has been omitted) -
814
+ #
815
+ # <%= fields_for @person.permission do |permission_fields| %>
816
+ # Admin?: <%= permission_fields.check_box :admin %>
817
+ # <% end %>
818
+ #
819
+ # and +fields_for+ will derive the required name of the field from the
820
+ # _class_ of the model object, e.g. if <tt>@person.permission</tt>, is
821
+ # of class +Permission+, the field will still be named <tt>permission[admin]</tt>.
822
+ #
823
+ # Note: This also works for the methods in FormOptionsHelper and
824
+ # DateHelper that are designed to work with an object as base, like
825
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
826
+ #
827
+ # === Nested Attributes Examples
828
+ #
829
+ # When the object belonging to the current scope has a nested attribute
830
+ # writer for a certain attribute, fields_for will yield a new scope
831
+ # for that attribute. This allows you to create forms that set or change
832
+ # the attributes of a parent object and its associations in one go.
833
+ #
834
+ # Nested attribute writers are normal setter methods named after an
835
+ # association. The most common way of defining these writers is either
836
+ # with +accepts_nested_attributes_for+ in a model definition or by
837
+ # defining a method with the proper name. For example: the attribute
838
+ # writer for the association <tt>:address</tt> is called
839
+ # <tt>address_attributes=</tt>.
840
+ #
841
+ # Whether a one-to-one or one-to-many style form builder will be yielded
842
+ # depends on whether the normal reader method returns a _single_ object
843
+ # or an _array_ of objects.
844
+ #
845
+ # ==== One-to-one
846
+ #
847
+ # Consider a Person class which returns a _single_ Address from the
848
+ # <tt>address</tt> reader method and responds to the
849
+ # <tt>address_attributes=</tt> writer method:
850
+ #
851
+ # class Person
852
+ # def address
853
+ # @address
854
+ # end
855
+ #
856
+ # def address_attributes=(attributes)
857
+ # # Process the attributes hash
858
+ # end
859
+ # end
860
+ #
861
+ # This model can now be used with a nested fields_for, like so:
862
+ #
863
+ # <%= form_for @person do |person_form| %>
864
+ # ...
865
+ # <%= person_form.fields_for :address do |address_fields| %>
866
+ # Street : <%= address_fields.text_field :street %>
867
+ # Zip code: <%= address_fields.text_field :zip_code %>
868
+ # <% end %>
869
+ # ...
870
+ # <% end %>
871
+ #
872
+ # When address is already an association on a Person you can use
873
+ # +accepts_nested_attributes_for+ to define the writer method for you:
874
+ #
875
+ # class Person < ActiveRecord::Base
876
+ # has_one :address
877
+ # accepts_nested_attributes_for :address
878
+ # end
879
+ #
880
+ # If you want to destroy the associated model through the form, you have
881
+ # to enable it first using the <tt>:allow_destroy</tt> option for
882
+ # +accepts_nested_attributes_for+:
883
+ #
884
+ # class Person < ActiveRecord::Base
885
+ # has_one :address
886
+ # accepts_nested_attributes_for :address, allow_destroy: true
887
+ # end
888
+ #
889
+ # Now, when you use a form element with the <tt>_destroy</tt> parameter,
890
+ # with a value that evaluates to +true+, you will destroy the associated
891
+ # model (eg. 1, '1', true, or 'true'):
892
+ #
893
+ # <%= form_for @person do |person_form| %>
894
+ # ...
895
+ # <%= person_form.fields_for :address do |address_fields| %>
896
+ # ...
897
+ # Delete: <%= address_fields.check_box :_destroy %>
898
+ # <% end %>
899
+ # ...
900
+ # <% end %>
901
+ #
902
+ # ==== One-to-many
903
+ #
904
+ # Consider a Person class which returns an _array_ of Project instances
905
+ # from the <tt>projects</tt> reader method and responds to the
906
+ # <tt>projects_attributes=</tt> writer method:
907
+ #
908
+ # class Person
909
+ # def projects
910
+ # [@project1, @project2]
911
+ # end
912
+ #
913
+ # def projects_attributes=(attributes)
914
+ # # Process the attributes hash
915
+ # end
916
+ # end
917
+ #
918
+ # Note that the <tt>projects_attributes=</tt> writer method is in fact
919
+ # required for fields_for to correctly identify <tt>:projects</tt> as a
920
+ # collection, and the correct indices to be set in the form markup.
921
+ #
922
+ # When projects is already an association on Person you can use
923
+ # +accepts_nested_attributes_for+ to define the writer method for you:
924
+ #
925
+ # class Person < ActiveRecord::Base
926
+ # has_many :projects
927
+ # accepts_nested_attributes_for :projects
928
+ # end
929
+ #
930
+ # This model can now be used with a nested fields_for. The block given to
931
+ # the nested fields_for call will be repeated for each instance in the
932
+ # collection:
933
+ #
934
+ # <%= form_for @person do |person_form| %>
935
+ # ...
936
+ # <%= person_form.fields_for :projects do |project_fields| %>
937
+ # <% if project_fields.object.active? %>
938
+ # Name: <%= project_fields.text_field :name %>
939
+ # <% end %>
940
+ # <% end %>
941
+ # ...
942
+ # <% end %>
943
+ #
944
+ # It's also possible to specify the instance to be used:
945
+ #
946
+ # <%= form_for @person do |person_form| %>
947
+ # ...
948
+ # <% @person.projects.each do |project| %>
949
+ # <% if project.active? %>
950
+ # <%= person_form.fields_for :projects, project do |project_fields| %>
951
+ # Name: <%= project_fields.text_field :name %>
952
+ # <% end %>
953
+ # <% end %>
954
+ # <% end %>
955
+ # ...
956
+ # <% end %>
957
+ #
958
+ # Or a collection to be used:
959
+ #
960
+ # <%= form_for @person do |person_form| %>
961
+ # ...
962
+ # <%= person_form.fields_for :projects, @active_projects do |project_fields| %>
963
+ # Name: <%= project_fields.text_field :name %>
964
+ # <% end %>
965
+ # ...
966
+ # <% end %>
967
+ #
968
+ # If you want to destroy any of the associated models through the
969
+ # form, you have to enable it first using the <tt>:allow_destroy</tt>
970
+ # option for +accepts_nested_attributes_for+:
971
+ #
972
+ # class Person < ActiveRecord::Base
973
+ # has_many :projects
974
+ # accepts_nested_attributes_for :projects, allow_destroy: true
975
+ # end
976
+ #
977
+ # This will allow you to specify which models to destroy in the
978
+ # attributes hash by adding a form element for the <tt>_destroy</tt>
979
+ # parameter with a value that evaluates to +true+
980
+ # (eg. 1, '1', true, or 'true'):
981
+ #
982
+ # <%= form_for @person do |person_form| %>
983
+ # ...
984
+ # <%= person_form.fields_for :projects do |project_fields| %>
985
+ # Delete: <%= project_fields.check_box :_destroy %>
986
+ # <% end %>
987
+ # ...
988
+ # <% end %>
989
+ #
990
+ # When a collection is used you might want to know the index of each
991
+ # object into the array. For this purpose, the <tt>index</tt> method
992
+ # is available in the FormBuilder object.
993
+ #
994
+ # <%= form_for @person do |person_form| %>
995
+ # ...
996
+ # <%= person_form.fields_for :projects do |project_fields| %>
997
+ # Project #<%= project_fields.index %>
998
+ # ...
999
+ # <% end %>
1000
+ # ...
1001
+ # <% end %>
1002
+ #
1003
+ # Note that fields_for will automatically generate a hidden field
1004
+ # to store the ID of the record. There are circumstances where this
1005
+ # hidden field is not needed and you can pass <tt>include_id: false</tt>
1006
+ # to prevent fields_for from rendering it automatically.
1007
+ def fields_for(record_name, record_object = nil, options = {}, &block)
1008
+ builder = instantiate_builder(record_name, record_object, options)
1009
+ capture(builder, &block)
1010
+ end
1011
+
1012
+ # Scopes input fields with either an explicit scope or model.
1013
+ # Like +form_with+ does with <tt>:scope</tt> or <tt>:model</tt>,
1014
+ # except it doesn't output the form tags.
1015
+ #
1016
+ # # Using a scope prefixes the input field names:
1017
+ # <%= fields :comment do |fields| %>
1018
+ # <%= fields.text_field :body %>
1019
+ # <% end %>
1020
+ # # => <input type="text" name="comment[body]">
1021
+ #
1022
+ # # Using a model infers the scope and assigns field values:
1023
+ # <%= fields model: Comment.new(body: "full bodied") do |fields| %>
1024
+ # <%= fields.text_field :body %>
1025
+ # <% end %>
1026
+ # # => <input type="text" name="comment[body]" value="full bodied">
1027
+ #
1028
+ # # Using +fields+ with +form_with+:
1029
+ # <%= form_with model: @post do |form| %>
1030
+ # <%= form.text_field :title %>
1031
+ #
1032
+ # <%= form.fields :comment do |fields| %>
1033
+ # <%= fields.text_field :body %>
1034
+ # <% end %>
1035
+ # <% end %>
1036
+ #
1037
+ # Much like +form_with+ a FormBuilder instance associated with the scope
1038
+ # or model is yielded, so any generated field names are prefixed with
1039
+ # either the passed scope or the scope inferred from the <tt>:model</tt>.
1040
+ #
1041
+ # === Mixing with other form helpers
1042
+ #
1043
+ # While +form_with+ uses a FormBuilder object it's possible to mix and
1044
+ # match the stand-alone FormHelper methods and methods
1045
+ # from FormTagHelper:
1046
+ #
1047
+ # <%= fields model: @comment do |fields| %>
1048
+ # <%= fields.text_field :body %>
1049
+ #
1050
+ # <%= text_area :commenter, :biography %>
1051
+ # <%= check_box_tag "comment[all_caps]", "1", @comment.commenter.hulk_mode? %>
1052
+ # <% end %>
1053
+ #
1054
+ # Same goes for the methods in FormOptionsHelper and DateHelper designed
1055
+ # to work with an object as a base, like
1056
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
1057
+ def fields(scope = nil, model: nil, **options, &block)
1058
+ options[:allow_method_names_outside_object] = true
1059
+ options[:skip_default_ids] = !form_with_generates_ids
1060
+
1061
+ if model
1062
+ scope ||= model_name_from_record_or_class(model).param_key
1063
+ end
1064
+
1065
+ builder = instantiate_builder(scope, model, options)
1066
+ capture(builder, &block)
1067
+ end
1068
+
1069
+ # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
1070
+ # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
1071
+ # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
1072
+ # Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
1073
+ # onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to
1074
+ # target labels for radio_button tags (where the value is used in the ID of the input tag).
1075
+ #
1076
+ # ==== Examples
1077
+ # label(:post, :title)
1078
+ # # => <label for="post_title">Title</label>
1079
+ #
1080
+ # You can localize your labels based on model and attribute names.
1081
+ # For example you can define the following in your locale (e.g. en.yml)
1082
+ #
1083
+ # helpers:
1084
+ # label:
1085
+ # post:
1086
+ # body: "Write your entire text here"
1087
+ #
1088
+ # Which then will result in
1089
+ #
1090
+ # label(:post, :body)
1091
+ # # => <label for="post_body">Write your entire text here</label>
1092
+ #
1093
+ # Localization can also be based purely on the translation of the attribute-name
1094
+ # (if you are using ActiveRecord):
1095
+ #
1096
+ # activerecord:
1097
+ # attributes:
1098
+ # post:
1099
+ # cost: "Total cost"
1100
+ #
1101
+ # label(:post, :cost)
1102
+ # # => <label for="post_cost">Total cost</label>
1103
+ #
1104
+ # label(:post, :title, "A short title")
1105
+ # # => <label for="post_title">A short title</label>
1106
+ #
1107
+ # label(:post, :title, "A short title", class: "title_label")
1108
+ # # => <label for="post_title" class="title_label">A short title</label>
1109
+ #
1110
+ # label(:post, :privacy, "Public Post", value: "public")
1111
+ # # => <label for="post_privacy_public">Public Post</label>
1112
+ #
1113
+ # label(:post, :terms) do
1114
+ # raw('Accept <a href="/terms">Terms</a>.')
1115
+ # end
1116
+ # # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
1117
+ def label(object_name, method, content_or_options = nil, options = nil, &block)
1118
+ Tags::Label.new(object_name, method, self, content_or_options, options).render(&block)
1119
+ end
1120
+
1121
+ # Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
1122
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
1123
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
1124
+ # shown.
1125
+ #
1126
+ # ==== Examples
1127
+ # text_field(:post, :title, size: 20)
1128
+ # # => <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" />
1129
+ #
1130
+ # text_field(:post, :title, class: "create_input")
1131
+ # # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
1132
+ #
1133
+ # text_field(:post, :title, maxlength: 30, class: "title_input")
1134
+ # # => <input type="text" id="post_title" name="post[title]" maxlength="30" size="30" value="#{@post.title}" class="title_input" />
1135
+ #
1136
+ # text_field(:session, :user, onchange: "if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }")
1137
+ # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange="if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }"/>
1138
+ #
1139
+ # text_field(:snippet, :code, size: 20, class: 'code_input')
1140
+ # # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
1141
+ def text_field(object_name, method, options = {})
1142
+ Tags::TextField.new(object_name, method, self, options).render
1143
+ end
1144
+
1145
+ # Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
1146
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
1147
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
1148
+ # shown. For security reasons this field is blank by default; pass in a value via +options+ if this is not desired.
1149
+ #
1150
+ # ==== Examples
1151
+ # password_field(:login, :pass, size: 20)
1152
+ # # => <input type="password" id="login_pass" name="login[pass]" size="20" />
1153
+ #
1154
+ # password_field(:account, :secret, class: "form_input", value: @account.secret)
1155
+ # # => <input type="password" id="account_secret" name="account[secret]" value="#{@account.secret}" class="form_input" />
1156
+ #
1157
+ # password_field(:user, :password, onchange: "if ($('#user_password').val().length > 30) { alert('Your password needs to be shorter!'); }")
1158
+ # # => <input type="password" id="user_password" name="user[password]" onchange="if ($('#user_password').val().length > 30) { alert('Your password needs to be shorter!'); }"/>
1159
+ #
1160
+ # password_field(:account, :pin, size: 20, class: 'form_input')
1161
+ # # => <input type="password" id="account_pin" name="account[pin]" size="20" class="form_input" />
1162
+ def password_field(object_name, method, options = {})
1163
+ Tags::PasswordField.new(object_name, method, self, options).render
1164
+ end
1165
+
1166
+ # Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
1167
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
1168
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
1169
+ # shown.
1170
+ #
1171
+ # ==== Examples
1172
+ # hidden_field(:signup, :pass_confirm)
1173
+ # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
1174
+ #
1175
+ # hidden_field(:post, :tag_list)
1176
+ # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
1177
+ #
1178
+ # hidden_field(:user, :token)
1179
+ # # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
1180
+ def hidden_field(object_name, method, options = {})
1181
+ Tags::HiddenField.new(object_name, method, self, options).render
1182
+ end
1183
+
1184
+ # Returns a file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
1185
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
1186
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
1187
+ # shown.
1188
+ #
1189
+ # Using this method inside a +form_for+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>.
1190
+ #
1191
+ # ==== Options
1192
+ # * Creates standard HTML attributes for the tag.
1193
+ # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
1194
+ # * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files.
1195
+ # * <tt>:accept</tt> - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations.
1196
+ #
1197
+ # ==== Examples
1198
+ # file_field(:user, :avatar)
1199
+ # # => <input type="file" id="user_avatar" name="user[avatar]" />
1200
+ #
1201
+ # file_field(:post, :image, multiple: true)
1202
+ # # => <input type="file" id="post_image" name="post[image][]" multiple="multiple" />
1203
+ #
1204
+ # file_field(:post, :attached, accept: 'text/html')
1205
+ # # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
1206
+ #
1207
+ # file_field(:post, :image, accept: 'image/png,image/gif,image/jpeg')
1208
+ # # => <input type="file" id="post_image" name="post[image]" accept="image/png,image/gif,image/jpeg" />
1209
+ #
1210
+ # file_field(:attachment, :file, class: 'file_input')
1211
+ # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
1212
+ def file_field(object_name, method, options = {})
1213
+ Tags::FileField.new(object_name, method, self, convert_direct_upload_option_to_url(options.dup)).render
1214
+ end
1215
+
1216
+ # Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
1217
+ # on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
1218
+ # hash with +options+.
1219
+ #
1220
+ # ==== Examples
1221
+ # text_area(:post, :body, cols: 20, rows: 40)
1222
+ # # => <textarea cols="20" rows="40" id="post_body" name="post[body]">
1223
+ # # #{@post.body}
1224
+ # # </textarea>
1225
+ #
1226
+ # text_area(:comment, :text, size: "20x30")
1227
+ # # => <textarea cols="20" rows="30" id="comment_text" name="comment[text]">
1228
+ # # #{@comment.text}
1229
+ # # </textarea>
1230
+ #
1231
+ # text_area(:application, :notes, cols: 40, rows: 15, class: 'app_input')
1232
+ # # => <textarea cols="40" rows="15" id="application_notes" name="application[notes]" class="app_input">
1233
+ # # #{@application.notes}
1234
+ # # </textarea>
1235
+ #
1236
+ # text_area(:entry, :body, size: "20x20", disabled: 'disabled')
1237
+ # # => <textarea cols="20" rows="20" id="entry_body" name="entry[body]" disabled="disabled">
1238
+ # # #{@entry.body}
1239
+ # # </textarea>
1240
+ def text_area(object_name, method, options = {})
1241
+ Tags::TextArea.new(object_name, method, self, options).render
1242
+ end
1243
+
1244
+ # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
1245
+ # assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object.
1246
+ # It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked.
1247
+ # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
1248
+ # while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
1249
+ #
1250
+ # ==== Gotcha
1251
+ #
1252
+ # The HTML specification says unchecked check boxes are not successful, and
1253
+ # thus web browsers do not send them. Unfortunately this introduces a gotcha:
1254
+ # if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid
1255
+ # invoice the user unchecks its check box, no +paid+ parameter is sent. So,
1256
+ # any mass-assignment idiom like
1257
+ #
1258
+ # @invoice.update(params[:invoice])
1259
+ #
1260
+ # wouldn't update the flag.
1261
+ #
1262
+ # To prevent this the helper generates an auxiliary hidden field before
1263
+ # the very check box. The hidden field has the same name and its
1264
+ # attributes mimic an unchecked check box.
1265
+ #
1266
+ # This way, the client either sends only the hidden field (representing
1267
+ # the check box is unchecked), or both fields. Since the HTML specification
1268
+ # says key/value pairs have to be sent in the same order they appear in the
1269
+ # form, and parameters extraction gets the last occurrence of any repeated
1270
+ # key in the query string, that works for ordinary forms.
1271
+ #
1272
+ # Unfortunately that workaround does not work when the check box goes
1273
+ # within an array-like parameter, as in
1274
+ #
1275
+ # <%= fields_for "project[invoice_attributes][]", invoice, index: nil do |form| %>
1276
+ # <%= form.check_box :paid %>
1277
+ # ...
1278
+ # <% end %>
1279
+ #
1280
+ # because parameter name repetition is precisely what Rails seeks to distinguish
1281
+ # the elements of the array. For each item with a checked check box you
1282
+ # get an extra ghost item with only that attribute, assigned to "0".
1283
+ #
1284
+ # In that case it is preferable to either use +check_box_tag+ or to use
1285
+ # hashes instead of arrays.
1286
+ #
1287
+ # # Let's say that @post.validated? is 1:
1288
+ # check_box("post", "validated")
1289
+ # # => <input name="post[validated]" type="hidden" value="0" />
1290
+ # # <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" />
1291
+ #
1292
+ # # Let's say that @puppy.gooddog is "no":
1293
+ # check_box("puppy", "gooddog", {}, "yes", "no")
1294
+ # # => <input name="puppy[gooddog]" type="hidden" value="no" />
1295
+ # # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
1296
+ #
1297
+ # check_box("eula", "accepted", { class: 'eula_check' }, "yes", "no")
1298
+ # # => <input name="eula[accepted]" type="hidden" value="no" />
1299
+ # # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
1300
+ def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
1301
+ Tags::CheckBox.new(object_name, method, self, checked_value, unchecked_value, options).render
1302
+ end
1303
+
1304
+ # Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
1305
+ # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the
1306
+ # radio button will be checked.
1307
+ #
1308
+ # To force the radio button to be checked pass <tt>checked: true</tt> in the
1309
+ # +options+ hash. You may pass HTML options there as well.
1310
+ #
1311
+ # # Let's say that @post.category returns "rails":
1312
+ # radio_button("post", "category", "rails")
1313
+ # radio_button("post", "category", "java")
1314
+ # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
1315
+ # # <input type="radio" id="post_category_java" name="post[category]" value="java" />
1316
+ #
1317
+ # # Let's say that @user.receive_newsletter returns "no":
1318
+ # radio_button("user", "receive_newsletter", "yes")
1319
+ # radio_button("user", "receive_newsletter", "no")
1320
+ # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
1321
+ # # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
1322
+ def radio_button(object_name, method, tag_value, options = {})
1323
+ Tags::RadioButton.new(object_name, method, self, tag_value, options).render
1324
+ end
1325
+
1326
+ # Returns a text_field of type "color".
1327
+ #
1328
+ # color_field("car", "color")
1329
+ # # => <input id="car_color" name="car[color]" type="color" value="#000000" />
1330
+ def color_field(object_name, method, options = {})
1331
+ Tags::ColorField.new(object_name, method, self, options).render
1332
+ end
1333
+
1334
+ # Returns an input of type "search" for accessing a specified attribute (identified by +method+) on an object
1335
+ # assigned to the template (identified by +object_name+). Inputs of type "search" may be styled differently by
1336
+ # some browsers.
1337
+ #
1338
+ # search_field(:user, :name)
1339
+ # # => <input id="user_name" name="user[name]" type="search" />
1340
+ # search_field(:user, :name, autosave: false)
1341
+ # # => <input autosave="false" id="user_name" name="user[name]" type="search" />
1342
+ # search_field(:user, :name, results: 3)
1343
+ # # => <input id="user_name" name="user[name]" results="3" type="search" />
1344
+ # # Assume request.host returns "www.example.com"
1345
+ # search_field(:user, :name, autosave: true)
1346
+ # # => <input autosave="com.example.www" id="user_name" name="user[name]" results="10" type="search" />
1347
+ # search_field(:user, :name, onsearch: true)
1348
+ # # => <input id="user_name" incremental="true" name="user[name]" onsearch="true" type="search" />
1349
+ # search_field(:user, :name, autosave: false, onsearch: true)
1350
+ # # => <input autosave="false" id="user_name" incremental="true" name="user[name]" onsearch="true" type="search" />
1351
+ # search_field(:user, :name, autosave: true, onsearch: true)
1352
+ # # => <input autosave="com.example.www" id="user_name" incremental="true" name="user[name]" onsearch="true" results="10" type="search" />
1353
+ def search_field(object_name, method, options = {})
1354
+ Tags::SearchField.new(object_name, method, self, options).render
1355
+ end
1356
+
1357
+ # Returns a text_field of type "tel".
1358
+ #
1359
+ # telephone_field("user", "phone")
1360
+ # # => <input id="user_phone" name="user[phone]" type="tel" />
1361
+ #
1362
+ def telephone_field(object_name, method, options = {})
1363
+ Tags::TelField.new(object_name, method, self, options).render
1364
+ end
1365
+ # aliases telephone_field
1366
+ alias phone_field telephone_field
1367
+
1368
+ # Returns a text_field of type "date".
1369
+ #
1370
+ # date_field("user", "born_on")
1371
+ # # => <input id="user_born_on" name="user[born_on]" type="date" />
1372
+ #
1373
+ # The default value is generated by trying to call +strftime+ with "%Y-%m-%d"
1374
+ # on the object's value, which makes it behave as expected for instances
1375
+ # of DateTime and ActiveSupport::TimeWithZone. You can still override that
1376
+ # by passing the "value" option explicitly, e.g.
1377
+ #
1378
+ # @user.born_on = Date.new(1984, 1, 27)
1379
+ # date_field("user", "born_on", value: "1984-05-12")
1380
+ # # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-05-12" />
1381
+ #
1382
+ # You can create values for the "min" and "max" attributes by passing
1383
+ # instances of Date or Time to the options hash.
1384
+ #
1385
+ # date_field("user", "born_on", min: Date.today)
1386
+ # # => <input id="user_born_on" name="user[born_on]" type="date" min="2014-05-20" />
1387
+ #
1388
+ # Alternatively, you can pass a String formatted as an ISO8601 date as the
1389
+ # values for "min" and "max."
1390
+ #
1391
+ # date_field("user", "born_on", min: "2014-05-20")
1392
+ # # => <input id="user_born_on" name="user[born_on]" type="date" min="2014-05-20" />
1393
+ #
1394
+ def date_field(object_name, method, options = {})
1395
+ Tags::DateField.new(object_name, method, self, options).render
1396
+ end
1397
+
1398
+ # Returns a text_field of type "time".
1399
+ #
1400
+ # The default value is generated by trying to call +strftime+ with "%T.%L"
1401
+ # on the object's value. It is still possible to override that
1402
+ # by passing the "value" option.
1403
+ #
1404
+ # === Options
1405
+ # * Accepts same options as time_field_tag
1406
+ #
1407
+ # === Example
1408
+ # time_field("task", "started_at")
1409
+ # # => <input id="task_started_at" name="task[started_at]" type="time" />
1410
+ #
1411
+ # You can create values for the "min" and "max" attributes by passing
1412
+ # instances of Date or Time to the options hash.
1413
+ #
1414
+ # time_field("task", "started_at", min: Time.now)
1415
+ # # => <input id="task_started_at" name="task[started_at]" type="time" min="01:00:00.000" />
1416
+ #
1417
+ # Alternatively, you can pass a String formatted as an ISO8601 time as the
1418
+ # values for "min" and "max."
1419
+ #
1420
+ # time_field("task", "started_at", min: "01:00:00")
1421
+ # # => <input id="task_started_at" name="task[started_at]" type="time" min="01:00:00.000" />
1422
+ #
1423
+ def time_field(object_name, method, options = {})
1424
+ Tags::TimeField.new(object_name, method, self, options).render
1425
+ end
1426
+
1427
+ # Returns a text_field of type "datetime-local".
1428
+ #
1429
+ # datetime_field("user", "born_on")
1430
+ # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" />
1431
+ #
1432
+ # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T"
1433
+ # on the object's value, which makes it behave as expected for instances
1434
+ # of DateTime and ActiveSupport::TimeWithZone.
1435
+ #
1436
+ # @user.born_on = Date.new(1984, 1, 12)
1437
+ # datetime_field("user", "born_on")
1438
+ # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" value="1984-01-12T00:00:00" />
1439
+ #
1440
+ # You can create values for the "min" and "max" attributes by passing
1441
+ # instances of Date or Time to the options hash.
1442
+ #
1443
+ # datetime_field("user", "born_on", min: Date.today)
1444
+ # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" />
1445
+ #
1446
+ # Alternatively, you can pass a String formatted as an ISO8601 datetime as
1447
+ # the values for "min" and "max."
1448
+ #
1449
+ # datetime_field("user", "born_on", min: "2014-05-20T00:00:00")
1450
+ # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" />
1451
+ #
1452
+ def datetime_field(object_name, method, options = {})
1453
+ Tags::DatetimeLocalField.new(object_name, method, self, options).render
1454
+ end
1455
+
1456
+ alias datetime_local_field datetime_field
1457
+
1458
+ # Returns a text_field of type "month".
1459
+ #
1460
+ # month_field("user", "born_on")
1461
+ # # => <input id="user_born_on" name="user[born_on]" type="month" />
1462
+ #
1463
+ # The default value is generated by trying to call +strftime+ with "%Y-%m"
1464
+ # on the object's value, which makes it behave as expected for instances
1465
+ # of DateTime and ActiveSupport::TimeWithZone.
1466
+ #
1467
+ # @user.born_on = Date.new(1984, 1, 27)
1468
+ # month_field("user", "born_on")
1469
+ # # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-01" />
1470
+ #
1471
+ def month_field(object_name, method, options = {})
1472
+ Tags::MonthField.new(object_name, method, self, options).render
1473
+ end
1474
+
1475
+ # Returns a text_field of type "week".
1476
+ #
1477
+ # week_field("user", "born_on")
1478
+ # # => <input id="user_born_on" name="user[born_on]" type="week" />
1479
+ #
1480
+ # The default value is generated by trying to call +strftime+ with "%Y-W%W"
1481
+ # on the object's value, which makes it behave as expected for instances
1482
+ # of DateTime and ActiveSupport::TimeWithZone.
1483
+ #
1484
+ # @user.born_on = Date.new(1984, 5, 12)
1485
+ # week_field("user", "born_on")
1486
+ # # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-W19" />
1487
+ #
1488
+ def week_field(object_name, method, options = {})
1489
+ Tags::WeekField.new(object_name, method, self, options).render
1490
+ end
1491
+
1492
+ # Returns a text_field of type "url".
1493
+ #
1494
+ # url_field("user", "homepage")
1495
+ # # => <input id="user_homepage" name="user[homepage]" type="url" />
1496
+ #
1497
+ def url_field(object_name, method, options = {})
1498
+ Tags::UrlField.new(object_name, method, self, options).render
1499
+ end
1500
+
1501
+ # Returns a text_field of type "email".
1502
+ #
1503
+ # email_field("user", "address")
1504
+ # # => <input id="user_address" name="user[address]" type="email" />
1505
+ #
1506
+ def email_field(object_name, method, options = {})
1507
+ Tags::EmailField.new(object_name, method, self, options).render
1508
+ end
1509
+
1510
+ # Returns an input tag of type "number".
1511
+ #
1512
+ # ==== Options
1513
+ # * Accepts same options as number_field_tag
1514
+ def number_field(object_name, method, options = {})
1515
+ Tags::NumberField.new(object_name, method, self, options).render
1516
+ end
1517
+
1518
+ # Returns an input tag of type "range".
1519
+ #
1520
+ # ==== Options
1521
+ # * Accepts same options as range_field_tag
1522
+ def range_field(object_name, method, options = {})
1523
+ Tags::RangeField.new(object_name, method, self, options).render
1524
+ end
1525
+
1526
+ private
1527
+ def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: !form_with_generates_remote_forms,
1528
+ skip_enforcing_utf8: nil, **options)
1529
+ html_options = options.slice(:id, :class, :multipart, :method, :data).merge(html)
1530
+ html_options[:method] ||= :patch if model.respond_to?(:persisted?) && model.persisted?
1531
+ html_options[:enforce_utf8] = !skip_enforcing_utf8 unless skip_enforcing_utf8.nil?
1532
+
1533
+ html_options[:enctype] = "multipart/form-data" if html_options.delete(:multipart)
1534
+
1535
+ # The following URL is unescaped, this is just a hash of options, and it is the
1536
+ # responsibility of the caller to escape all the values.
1537
+ html_options[:action] = url_for(url_for_options || {})
1538
+ html_options[:"accept-charset"] = "UTF-8"
1539
+ html_options[:"data-remote"] = true unless local
1540
+
1541
+ html_options[:authenticity_token] = options.delete(:authenticity_token)
1542
+
1543
+ if !local && html_options[:authenticity_token].blank?
1544
+ html_options[:authenticity_token] = embed_authenticity_token_in_remote_forms
1545
+ end
1546
+
1547
+ if html_options[:authenticity_token] == true
1548
+ # Include the default authenticity_token, which is only generated when it's set to nil,
1549
+ # but we needed the true value to override the default of no authenticity_token on data-remote.
1550
+ html_options[:authenticity_token] = nil
1551
+ end
1552
+
1553
+ html_options.stringify_keys!
1554
+ end
1555
+
1556
+ def instantiate_builder(record_name, record_object, options)
1557
+ case record_name
1558
+ when String, Symbol
1559
+ object = record_object
1560
+ object_name = record_name
1561
+ else
1562
+ object = record_name
1563
+ object_name = model_name_from_record_or_class(object).param_key if object
1564
+ end
1565
+
1566
+ builder = options[:builder] || default_form_builder_class
1567
+ builder.new(object_name, object, self, options)
1568
+ end
1569
+
1570
+ def default_form_builder_class
1571
+ builder = default_form_builder || ActionView::Base.default_form_builder
1572
+ builder.respond_to?(:constantize) ? builder.constantize : builder
1573
+ end
1574
+ end
1575
+
1576
+ # A +FormBuilder+ object is associated with a particular model object and
1577
+ # allows you to generate fields associated with the model object. The
1578
+ # +FormBuilder+ object is yielded when using +form_for+ or +fields_for+.
1579
+ # For example:
1580
+ #
1581
+ # <%= form_for @person do |person_form| %>
1582
+ # Name: <%= person_form.text_field :name %>
1583
+ # Admin: <%= person_form.check_box :admin %>
1584
+ # <% end %>
1585
+ #
1586
+ # In the above block, a +FormBuilder+ object is yielded as the
1587
+ # +person_form+ variable. This allows you to generate the +text_field+
1588
+ # and +check_box+ fields by specifying their eponymous methods, which
1589
+ # modify the underlying template and associates the <tt>@person</tt> model object
1590
+ # with the form.
1591
+ #
1592
+ # The +FormBuilder+ object can be thought of as serving as a proxy for the
1593
+ # methods in the +FormHelper+ module. This class, however, allows you to
1594
+ # call methods with the model object you are building the form for.
1595
+ #
1596
+ # You can create your own custom FormBuilder templates by subclassing this
1597
+ # class. For example:
1598
+ #
1599
+ # class MyFormBuilder < ActionView::Helpers::FormBuilder
1600
+ # def div_radio_button(method, tag_value, options = {})
1601
+ # @template.content_tag(:div,
1602
+ # @template.radio_button(
1603
+ # @object_name, method, tag_value, objectify_options(options)
1604
+ # )
1605
+ # )
1606
+ # end
1607
+ # end
1608
+ #
1609
+ # The above code creates a new method +div_radio_button+ which wraps a div
1610
+ # around the new radio button. Note that when options are passed in, you
1611
+ # must call +objectify_options+ in order for the model object to get
1612
+ # correctly passed to the method. If +objectify_options+ is not called,
1613
+ # then the newly created helper will not be linked back to the model.
1614
+ #
1615
+ # The +div_radio_button+ code from above can now be used as follows:
1616
+ #
1617
+ # <%= form_for @person, :builder => MyFormBuilder do |f| %>
1618
+ # I am a child: <%= f.div_radio_button(:admin, "child") %>
1619
+ # I am an adult: <%= f.div_radio_button(:admin, "adult") %>
1620
+ # <% end -%>
1621
+ #
1622
+ # The standard set of helper methods for form building are located in the
1623
+ # +field_helpers+ class attribute.
1624
+ class FormBuilder
1625
+ include ModelNaming
1626
+
1627
+ # The methods which wrap a form helper call.
1628
+ class_attribute :field_helpers, default: [
1629
+ :fields_for, :fields, :label, :text_field, :password_field,
1630
+ :hidden_field, :file_field, :text_area, :check_box,
1631
+ :radio_button, :color_field, :search_field,
1632
+ :telephone_field, :phone_field, :date_field,
1633
+ :time_field, :datetime_field, :datetime_local_field,
1634
+ :month_field, :week_field, :url_field, :email_field,
1635
+ :number_field, :range_field
1636
+ ]
1637
+
1638
+ attr_accessor :object_name, :object, :options
1639
+
1640
+ attr_reader :multipart, :index
1641
+ alias :multipart? :multipart
1642
+
1643
+ def multipart=(multipart)
1644
+ @multipart = multipart
1645
+
1646
+ if parent_builder = @options[:parent_builder]
1647
+ parent_builder.multipart = multipart
1648
+ end
1649
+ end
1650
+
1651
+ def self._to_partial_path
1652
+ @_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, "")
1653
+ end
1654
+
1655
+ def to_partial_path
1656
+ self.class._to_partial_path
1657
+ end
1658
+
1659
+ def to_model
1660
+ self
1661
+ end
1662
+
1663
+ def initialize(object_name, object, template, options)
1664
+ @nested_child_index = {}
1665
+ @object_name, @object, @template, @options = object_name, object, template, options
1666
+ @default_options = @options ? @options.slice(:index, :namespace, :skip_default_ids, :allow_method_names_outside_object) : {}
1667
+ @default_html_options = @default_options.except(:skip_default_ids, :allow_method_names_outside_object)
1668
+
1669
+ convert_to_legacy_options(@options)
1670
+
1671
+ if @object_name.to_s.match(/\[\]$/)
1672
+ if (object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}")) && object.respond_to?(:to_param)
1673
+ @auto_index = object.to_param
1674
+ else
1675
+ raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
1676
+ end
1677
+ end
1678
+
1679
+ @multipart = nil
1680
+ @index = options[:index] || options[:child_index]
1681
+ end
1682
+
1683
+ ##
1684
+ # :method: text_field
1685
+ #
1686
+ # :call-seq: text_field(method, options = {})
1687
+ #
1688
+ # Wraps ActionView::Helpers::FormHelper#text_field for form builders:
1689
+ #
1690
+ # <%= form_with model: @user do |f| %>
1691
+ # <%= f.text_field :name %>
1692
+ # <% end %>
1693
+ #
1694
+ # Please refer to the documentation of the base helper for details.
1695
+
1696
+ ##
1697
+ # :method: password_field
1698
+ #
1699
+ # :call-seq: password_field(method, options = {})
1700
+ #
1701
+ # Wraps ActionView::Helpers::FormHelper#password_field for form builders:
1702
+ #
1703
+ # <%= form_with model: @user do |f| %>
1704
+ # <%= f.password_field :password %>
1705
+ # <% end %>
1706
+ #
1707
+ # Please refer to the documentation of the base helper for details.
1708
+
1709
+ ##
1710
+ # :method: text_area
1711
+ #
1712
+ # :call-seq: text_area(method, options = {})
1713
+ #
1714
+ # Wraps ActionView::Helpers::FormHelper#text_area for form builders:
1715
+ #
1716
+ # <%= form_with model: @user do |f| %>
1717
+ # <%= f.text_area :detail %>
1718
+ # <% end %>
1719
+ #
1720
+ # Please refer to the documentation of the base helper for details.
1721
+
1722
+ ##
1723
+ # :method: color_field
1724
+ #
1725
+ # :call-seq: color_field(method, options = {})
1726
+ #
1727
+ # Wraps ActionView::Helpers::FormHelper#color_field for form builders:
1728
+ #
1729
+ # <%= form_with model: @user do |f| %>
1730
+ # <%= f.color_field :favorite_color %>
1731
+ # <% end %>
1732
+ #
1733
+ # Please refer to the documentation of the base helper for details.
1734
+
1735
+ ##
1736
+ # :method: search_field
1737
+ #
1738
+ # :call-seq: search_field(method, options = {})
1739
+ #
1740
+ # Wraps ActionView::Helpers::FormHelper#search_field for form builders:
1741
+ #
1742
+ # <%= form_with model: @user do |f| %>
1743
+ # <%= f.search_field :name %>
1744
+ # <% end %>
1745
+ #
1746
+ # Please refer to the documentation of the base helper for details.
1747
+
1748
+ ##
1749
+ # :method: telephone_field
1750
+ #
1751
+ # :call-seq: telephone_field(method, options = {})
1752
+ #
1753
+ # Wraps ActionView::Helpers::FormHelper#telephone_field for form builders:
1754
+ #
1755
+ # <%= form_with model: @user do |f| %>
1756
+ # <%= f.telephone_field :phone %>
1757
+ # <% end %>
1758
+ #
1759
+ # Please refer to the documentation of the base helper for details.
1760
+
1761
+ ##
1762
+ # :method: phone_field
1763
+ #
1764
+ # :call-seq: phone_field(method, options = {})
1765
+ #
1766
+ # Wraps ActionView::Helpers::FormHelper#phone_field for form builders:
1767
+ #
1768
+ # <%= form_with model: @user do |f| %>
1769
+ # <%= f.phone_field :phone %>
1770
+ # <% end %>
1771
+ #
1772
+ # Please refer to the documentation of the base helper for details.
1773
+
1774
+ ##
1775
+ # :method: date_field
1776
+ #
1777
+ # :call-seq: date_field(method, options = {})
1778
+ #
1779
+ # Wraps ActionView::Helpers::FormHelper#date_field for form builders:
1780
+ #
1781
+ # <%= form_with model: @user do |f| %>
1782
+ # <%= f.date_field :born_on %>
1783
+ # <% end %>
1784
+ #
1785
+ # Please refer to the documentation of the base helper for details.
1786
+
1787
+ ##
1788
+ # :method: time_field
1789
+ #
1790
+ # :call-seq: time_field(method, options = {})
1791
+ #
1792
+ # Wraps ActionView::Helpers::FormHelper#time_field for form builders:
1793
+ #
1794
+ # <%= form_with model: @user do |f| %>
1795
+ # <%= f.time_field :borned_at %>
1796
+ # <% end %>
1797
+ #
1798
+ # Please refer to the documentation of the base helper for details.
1799
+
1800
+ ##
1801
+ # :method: datetime_field
1802
+ #
1803
+ # :call-seq: datetime_field(method, options = {})
1804
+ #
1805
+ # Wraps ActionView::Helpers::FormHelper#datetime_field for form builders:
1806
+ #
1807
+ # <%= form_with model: @user do |f| %>
1808
+ # <%= f.datetime_field :graduation_day %>
1809
+ # <% end %>
1810
+ #
1811
+ # Please refer to the documentation of the base helper for details.
1812
+
1813
+ ##
1814
+ # :method: datetime_local_field
1815
+ #
1816
+ # :call-seq: datetime_local_field(method, options = {})
1817
+ #
1818
+ # Wraps ActionView::Helpers::FormHelper#datetime_local_field for form builders:
1819
+ #
1820
+ # <%= form_with model: @user do |f| %>
1821
+ # <%= f.datetime_local_field :graduation_day %>
1822
+ # <% end %>
1823
+ #
1824
+ # Please refer to the documentation of the base helper for details.
1825
+
1826
+ ##
1827
+ # :method: month_field
1828
+ #
1829
+ # :call-seq: month_field(method, options = {})
1830
+ #
1831
+ # Wraps ActionView::Helpers::FormHelper#month_field for form builders:
1832
+ #
1833
+ # <%= form_with model: @user do |f| %>
1834
+ # <%= f.month_field :birthday_month %>
1835
+ # <% end %>
1836
+ #
1837
+ # Please refer to the documentation of the base helper for details.
1838
+
1839
+ ##
1840
+ # :method: week_field
1841
+ #
1842
+ # :call-seq: week_field(method, options = {})
1843
+ #
1844
+ # Wraps ActionView::Helpers::FormHelper#week_field for form builders:
1845
+ #
1846
+ # <%= form_with model: @user do |f| %>
1847
+ # <%= f.week_field :birthday_week %>
1848
+ # <% end %>
1849
+ #
1850
+ # Please refer to the documentation of the base helper for details.
1851
+
1852
+ ##
1853
+ # :method: url_field
1854
+ #
1855
+ # :call-seq: url_field(method, options = {})
1856
+ #
1857
+ # Wraps ActionView::Helpers::FormHelper#url_field for form builders:
1858
+ #
1859
+ # <%= form_with model: @user do |f| %>
1860
+ # <%= f.url_field :homepage %>
1861
+ # <% end %>
1862
+ #
1863
+ # Please refer to the documentation of the base helper for details.
1864
+
1865
+ ##
1866
+ # :method: email_field
1867
+ #
1868
+ # :call-seq: email_field(method, options = {})
1869
+ #
1870
+ # Wraps ActionView::Helpers::FormHelper#email_field for form builders:
1871
+ #
1872
+ # <%= form_with model: @user do |f| %>
1873
+ # <%= f.email_field :address %>
1874
+ # <% end %>
1875
+ #
1876
+ # Please refer to the documentation of the base helper for details.
1877
+
1878
+ ##
1879
+ # :method: number_field
1880
+ #
1881
+ # :call-seq: number_field(method, options = {})
1882
+ #
1883
+ # Wraps ActionView::Helpers::FormHelper#number_field for form builders:
1884
+ #
1885
+ # <%= form_with model: @user do |f| %>
1886
+ # <%= f.number_field :age %>
1887
+ # <% end %>
1888
+ #
1889
+ # Please refer to the documentation of the base helper for details.
1890
+
1891
+ ##
1892
+ # :method: range_field
1893
+ #
1894
+ # :call-seq: range_field(method, options = {})
1895
+ #
1896
+ # Wraps ActionView::Helpers::FormHelper#range_field for form builders:
1897
+ #
1898
+ # <%= form_with model: @user do |f| %>
1899
+ # <%= f.range_field :age %>
1900
+ # <% end %>
1901
+ #
1902
+ # Please refer to the documentation of the base helper for details.
1903
+
1904
+ (field_helpers - [:label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field]).each do |selector|
1905
+ class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
1906
+ def #{selector}(method, options = {}) # def text_field(method, options = {})
1907
+ @template.send( # @template.send(
1908
+ #{selector.inspect}, # "text_field",
1909
+ @object_name, # @object_name,
1910
+ method, # method,
1911
+ objectify_options(options)) # objectify_options(options))
1912
+ end # end
1913
+ RUBY_EVAL
1914
+ end
1915
+
1916
+ # Creates a scope around a specific model object like form_for, but
1917
+ # doesn't create the form tags themselves. This makes fields_for suitable
1918
+ # for specifying additional model objects in the same form.
1919
+ #
1920
+ # Although the usage and purpose of +fields_for+ is similar to +form_for+'s,
1921
+ # its method signature is slightly different. Like +form_for+, it yields
1922
+ # a FormBuilder object associated with a particular model object to a block,
1923
+ # and within the block allows methods to be called on the builder to
1924
+ # generate fields associated with the model object. Fields may reflect
1925
+ # a model object in two ways - how they are named (hence how submitted
1926
+ # values appear within the +params+ hash in the controller) and what
1927
+ # default values are shown when the form the fields appear in is first
1928
+ # displayed. In order for both of these features to be specified independently,
1929
+ # both an object name (represented by either a symbol or string) and the
1930
+ # object itself can be passed to the method separately -
1931
+ #
1932
+ # <%= form_for @person do |person_form| %>
1933
+ # First name: <%= person_form.text_field :first_name %>
1934
+ # Last name : <%= person_form.text_field :last_name %>
1935
+ #
1936
+ # <%= fields_for :permission, @person.permission do |permission_fields| %>
1937
+ # Admin? : <%= permission_fields.check_box :admin %>
1938
+ # <% end %>
1939
+ #
1940
+ # <%= person_form.submit %>
1941
+ # <% end %>
1942
+ #
1943
+ # In this case, the checkbox field will be represented by an HTML +input+
1944
+ # tag with the +name+ attribute <tt>permission[admin]</tt>, and the submitted
1945
+ # value will appear in the controller as <tt>params[:permission][:admin]</tt>.
1946
+ # If <tt>@person.permission</tt> is an existing record with an attribute
1947
+ # +admin+, the initial state of the checkbox when first displayed will
1948
+ # reflect the value of <tt>@person.permission.admin</tt>.
1949
+ #
1950
+ # Often this can be simplified by passing just the name of the model
1951
+ # object to +fields_for+ -
1952
+ #
1953
+ # <%= fields_for :permission do |permission_fields| %>
1954
+ # Admin?: <%= permission_fields.check_box :admin %>
1955
+ # <% end %>
1956
+ #
1957
+ # ...in which case, if <tt>:permission</tt> also happens to be the name of an
1958
+ # instance variable <tt>@permission</tt>, the initial state of the input
1959
+ # field will reflect the value of that variable's attribute <tt>@permission.admin</tt>.
1960
+ #
1961
+ # Alternatively, you can pass just the model object itself (if the first
1962
+ # argument isn't a string or symbol +fields_for+ will realize that the
1963
+ # name has been omitted) -
1964
+ #
1965
+ # <%= fields_for @person.permission do |permission_fields| %>
1966
+ # Admin?: <%= permission_fields.check_box :admin %>
1967
+ # <% end %>
1968
+ #
1969
+ # and +fields_for+ will derive the required name of the field from the
1970
+ # _class_ of the model object, e.g. if <tt>@person.permission</tt>, is
1971
+ # of class +Permission+, the field will still be named <tt>permission[admin]</tt>.
1972
+ #
1973
+ # Note: This also works for the methods in FormOptionsHelper and
1974
+ # DateHelper that are designed to work with an object as base, like
1975
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
1976
+ #
1977
+ # === Nested Attributes Examples
1978
+ #
1979
+ # When the object belonging to the current scope has a nested attribute
1980
+ # writer for a certain attribute, fields_for will yield a new scope
1981
+ # for that attribute. This allows you to create forms that set or change
1982
+ # the attributes of a parent object and its associations in one go.
1983
+ #
1984
+ # Nested attribute writers are normal setter methods named after an
1985
+ # association. The most common way of defining these writers is either
1986
+ # with +accepts_nested_attributes_for+ in a model definition or by
1987
+ # defining a method with the proper name. For example: the attribute
1988
+ # writer for the association <tt>:address</tt> is called
1989
+ # <tt>address_attributes=</tt>.
1990
+ #
1991
+ # Whether a one-to-one or one-to-many style form builder will be yielded
1992
+ # depends on whether the normal reader method returns a _single_ object
1993
+ # or an _array_ of objects.
1994
+ #
1995
+ # ==== One-to-one
1996
+ #
1997
+ # Consider a Person class which returns a _single_ Address from the
1998
+ # <tt>address</tt> reader method and responds to the
1999
+ # <tt>address_attributes=</tt> writer method:
2000
+ #
2001
+ # class Person
2002
+ # def address
2003
+ # @address
2004
+ # end
2005
+ #
2006
+ # def address_attributes=(attributes)
2007
+ # # Process the attributes hash
2008
+ # end
2009
+ # end
2010
+ #
2011
+ # This model can now be used with a nested fields_for, like so:
2012
+ #
2013
+ # <%= form_for @person do |person_form| %>
2014
+ # ...
2015
+ # <%= person_form.fields_for :address do |address_fields| %>
2016
+ # Street : <%= address_fields.text_field :street %>
2017
+ # Zip code: <%= address_fields.text_field :zip_code %>
2018
+ # <% end %>
2019
+ # ...
2020
+ # <% end %>
2021
+ #
2022
+ # When address is already an association on a Person you can use
2023
+ # +accepts_nested_attributes_for+ to define the writer method for you:
2024
+ #
2025
+ # class Person < ActiveRecord::Base
2026
+ # has_one :address
2027
+ # accepts_nested_attributes_for :address
2028
+ # end
2029
+ #
2030
+ # If you want to destroy the associated model through the form, you have
2031
+ # to enable it first using the <tt>:allow_destroy</tt> option for
2032
+ # +accepts_nested_attributes_for+:
2033
+ #
2034
+ # class Person < ActiveRecord::Base
2035
+ # has_one :address
2036
+ # accepts_nested_attributes_for :address, allow_destroy: true
2037
+ # end
2038
+ #
2039
+ # Now, when you use a form element with the <tt>_destroy</tt> parameter,
2040
+ # with a value that evaluates to +true+, you will destroy the associated
2041
+ # model (eg. 1, '1', true, or 'true'):
2042
+ #
2043
+ # <%= form_for @person do |person_form| %>
2044
+ # ...
2045
+ # <%= person_form.fields_for :address do |address_fields| %>
2046
+ # ...
2047
+ # Delete: <%= address_fields.check_box :_destroy %>
2048
+ # <% end %>
2049
+ # ...
2050
+ # <% end %>
2051
+ #
2052
+ # ==== One-to-many
2053
+ #
2054
+ # Consider a Person class which returns an _array_ of Project instances
2055
+ # from the <tt>projects</tt> reader method and responds to the
2056
+ # <tt>projects_attributes=</tt> writer method:
2057
+ #
2058
+ # class Person
2059
+ # def projects
2060
+ # [@project1, @project2]
2061
+ # end
2062
+ #
2063
+ # def projects_attributes=(attributes)
2064
+ # # Process the attributes hash
2065
+ # end
2066
+ # end
2067
+ #
2068
+ # Note that the <tt>projects_attributes=</tt> writer method is in fact
2069
+ # required for fields_for to correctly identify <tt>:projects</tt> as a
2070
+ # collection, and the correct indices to be set in the form markup.
2071
+ #
2072
+ # When projects is already an association on Person you can use
2073
+ # +accepts_nested_attributes_for+ to define the writer method for you:
2074
+ #
2075
+ # class Person < ActiveRecord::Base
2076
+ # has_many :projects
2077
+ # accepts_nested_attributes_for :projects
2078
+ # end
2079
+ #
2080
+ # This model can now be used with a nested fields_for. The block given to
2081
+ # the nested fields_for call will be repeated for each instance in the
2082
+ # collection:
2083
+ #
2084
+ # <%= form_for @person do |person_form| %>
2085
+ # ...
2086
+ # <%= person_form.fields_for :projects do |project_fields| %>
2087
+ # <% if project_fields.object.active? %>
2088
+ # Name: <%= project_fields.text_field :name %>
2089
+ # <% end %>
2090
+ # <% end %>
2091
+ # ...
2092
+ # <% end %>
2093
+ #
2094
+ # It's also possible to specify the instance to be used:
2095
+ #
2096
+ # <%= form_for @person do |person_form| %>
2097
+ # ...
2098
+ # <% @person.projects.each do |project| %>
2099
+ # <% if project.active? %>
2100
+ # <%= person_form.fields_for :projects, project do |project_fields| %>
2101
+ # Name: <%= project_fields.text_field :name %>
2102
+ # <% end %>
2103
+ # <% end %>
2104
+ # <% end %>
2105
+ # ...
2106
+ # <% end %>
2107
+ #
2108
+ # Or a collection to be used:
2109
+ #
2110
+ # <%= form_for @person do |person_form| %>
2111
+ # ...
2112
+ # <%= person_form.fields_for :projects, @active_projects do |project_fields| %>
2113
+ # Name: <%= project_fields.text_field :name %>
2114
+ # <% end %>
2115
+ # ...
2116
+ # <% end %>
2117
+ #
2118
+ # If you want to destroy any of the associated models through the
2119
+ # form, you have to enable it first using the <tt>:allow_destroy</tt>
2120
+ # option for +accepts_nested_attributes_for+:
2121
+ #
2122
+ # class Person < ActiveRecord::Base
2123
+ # has_many :projects
2124
+ # accepts_nested_attributes_for :projects, allow_destroy: true
2125
+ # end
2126
+ #
2127
+ # This will allow you to specify which models to destroy in the
2128
+ # attributes hash by adding a form element for the <tt>_destroy</tt>
2129
+ # parameter with a value that evaluates to +true+
2130
+ # (eg. 1, '1', true, or 'true'):
2131
+ #
2132
+ # <%= form_for @person do |person_form| %>
2133
+ # ...
2134
+ # <%= person_form.fields_for :projects do |project_fields| %>
2135
+ # Delete: <%= project_fields.check_box :_destroy %>
2136
+ # <% end %>
2137
+ # ...
2138
+ # <% end %>
2139
+ #
2140
+ # When a collection is used you might want to know the index of each
2141
+ # object into the array. For this purpose, the <tt>index</tt> method
2142
+ # is available in the FormBuilder object.
2143
+ #
2144
+ # <%= form_for @person do |person_form| %>
2145
+ # ...
2146
+ # <%= person_form.fields_for :projects do |project_fields| %>
2147
+ # Project #<%= project_fields.index %>
2148
+ # ...
2149
+ # <% end %>
2150
+ # ...
2151
+ # <% end %>
2152
+ #
2153
+ # Note that fields_for will automatically generate a hidden field
2154
+ # to store the ID of the record. There are circumstances where this
2155
+ # hidden field is not needed and you can pass <tt>include_id: false</tt>
2156
+ # to prevent fields_for from rendering it automatically.
2157
+ def fields_for(record_name, record_object = nil, fields_options = {}, &block)
2158
+ fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
2159
+ fields_options[:builder] ||= options[:builder]
2160
+ fields_options[:namespace] = options[:namespace]
2161
+ fields_options[:parent_builder] = self
2162
+
2163
+ case record_name
2164
+ when String, Symbol
2165
+ if nested_attributes_association?(record_name)
2166
+ return fields_for_with_nested_attributes(record_name, record_object, fields_options, block)
2167
+ end
2168
+ else
2169
+ record_object = record_name.is_a?(Array) ? record_name.last : record_name
2170
+ record_name = model_name_from_record_or_class(record_object).param_key
2171
+ end
2172
+
2173
+ object_name = @object_name
2174
+ index = if options.has_key?(:index)
2175
+ options[:index]
2176
+ elsif defined?(@auto_index)
2177
+ object_name = object_name.to_s.sub(/\[\]$/, "")
2178
+ @auto_index
2179
+ end
2180
+
2181
+ record_name = if index
2182
+ "#{object_name}[#{index}][#{record_name}]"
2183
+ elsif record_name.to_s.end_with?("[]")
2184
+ record_name = record_name.to_s.sub(/(.*)\[\]$/, "[\\1][#{record_object.id}]")
2185
+ "#{object_name}#{record_name}"
2186
+ else
2187
+ "#{object_name}[#{record_name}]"
2188
+ end
2189
+ fields_options[:child_index] = index
2190
+
2191
+ @template.fields_for(record_name, record_object, fields_options, &block)
2192
+ end
2193
+
2194
+ # See the docs for the <tt>ActionView::FormHelper.fields</tt> helper method.
2195
+ def fields(scope = nil, model: nil, **options, &block)
2196
+ options[:allow_method_names_outside_object] = true
2197
+ options[:skip_default_ids] = !FormHelper.form_with_generates_ids
2198
+
2199
+ convert_to_legacy_options(options)
2200
+
2201
+ fields_for(scope || model, model, options, &block)
2202
+ end
2203
+
2204
+ # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
2205
+ # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
2206
+ # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
2207
+ # Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
2208
+ # onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to
2209
+ # target labels for radio_button tags (where the value is used in the ID of the input tag).
2210
+ #
2211
+ # ==== Examples
2212
+ # label(:title)
2213
+ # # => <label for="post_title">Title</label>
2214
+ #
2215
+ # You can localize your labels based on model and attribute names.
2216
+ # For example you can define the following in your locale (e.g. en.yml)
2217
+ #
2218
+ # helpers:
2219
+ # label:
2220
+ # post:
2221
+ # body: "Write your entire text here"
2222
+ #
2223
+ # Which then will result in
2224
+ #
2225
+ # label(:body)
2226
+ # # => <label for="post_body">Write your entire text here</label>
2227
+ #
2228
+ # Localization can also be based purely on the translation of the attribute-name
2229
+ # (if you are using ActiveRecord):
2230
+ #
2231
+ # activerecord:
2232
+ # attributes:
2233
+ # post:
2234
+ # cost: "Total cost"
2235
+ #
2236
+ # label(:cost)
2237
+ # # => <label for="post_cost">Total cost</label>
2238
+ #
2239
+ # label(:title, "A short title")
2240
+ # # => <label for="post_title">A short title</label>
2241
+ #
2242
+ # label(:title, "A short title", class: "title_label")
2243
+ # # => <label for="post_title" class="title_label">A short title</label>
2244
+ #
2245
+ # label(:privacy, "Public Post", value: "public")
2246
+ # # => <label for="post_privacy_public">Public Post</label>
2247
+ #
2248
+ # label(:terms) do
2249
+ # raw('Accept <a href="/terms">Terms</a>.')
2250
+ # end
2251
+ # # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
2252
+ def label(method, text = nil, options = {}, &block)
2253
+ @template.label(@object_name, method, text, objectify_options(options), &block)
2254
+ end
2255
+
2256
+ # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
2257
+ # assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object.
2258
+ # It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked.
2259
+ # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
2260
+ # while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
2261
+ #
2262
+ # ==== Gotcha
2263
+ #
2264
+ # The HTML specification says unchecked check boxes are not successful, and
2265
+ # thus web browsers do not send them. Unfortunately this introduces a gotcha:
2266
+ # if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid
2267
+ # invoice the user unchecks its check box, no +paid+ parameter is sent. So,
2268
+ # any mass-assignment idiom like
2269
+ #
2270
+ # @invoice.update(params[:invoice])
2271
+ #
2272
+ # wouldn't update the flag.
2273
+ #
2274
+ # To prevent this the helper generates an auxiliary hidden field before
2275
+ # the very check box. The hidden field has the same name and its
2276
+ # attributes mimic an unchecked check box.
2277
+ #
2278
+ # This way, the client either sends only the hidden field (representing
2279
+ # the check box is unchecked), or both fields. Since the HTML specification
2280
+ # says key/value pairs have to be sent in the same order they appear in the
2281
+ # form, and parameters extraction gets the last occurrence of any repeated
2282
+ # key in the query string, that works for ordinary forms.
2283
+ #
2284
+ # Unfortunately that workaround does not work when the check box goes
2285
+ # within an array-like parameter, as in
2286
+ #
2287
+ # <%= fields_for "project[invoice_attributes][]", invoice, index: nil do |form| %>
2288
+ # <%= form.check_box :paid %>
2289
+ # ...
2290
+ # <% end %>
2291
+ #
2292
+ # because parameter name repetition is precisely what Rails seeks to distinguish
2293
+ # the elements of the array. For each item with a checked check box you
2294
+ # get an extra ghost item with only that attribute, assigned to "0".
2295
+ #
2296
+ # In that case it is preferable to either use +check_box_tag+ or to use
2297
+ # hashes instead of arrays.
2298
+ #
2299
+ # # Let's say that @post.validated? is 1:
2300
+ # check_box("validated")
2301
+ # # => <input name="post[validated]" type="hidden" value="0" />
2302
+ # # <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" />
2303
+ #
2304
+ # # Let's say that @puppy.gooddog is "no":
2305
+ # check_box("gooddog", {}, "yes", "no")
2306
+ # # => <input name="puppy[gooddog]" type="hidden" value="no" />
2307
+ # # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
2308
+ #
2309
+ # # Let's say that @eula.accepted is "no":
2310
+ # check_box("accepted", { class: 'eula_check' }, "yes", "no")
2311
+ # # => <input name="eula[accepted]" type="hidden" value="no" />
2312
+ # # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
2313
+ def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
2314
+ @template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value)
2315
+ end
2316
+
2317
+ # Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
2318
+ # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the
2319
+ # radio button will be checked.
2320
+ #
2321
+ # To force the radio button to be checked pass <tt>checked: true</tt> in the
2322
+ # +options+ hash. You may pass HTML options there as well.
2323
+ #
2324
+ # # Let's say that @post.category returns "rails":
2325
+ # radio_button("category", "rails")
2326
+ # radio_button("category", "java")
2327
+ # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
2328
+ # # <input type="radio" id="post_category_java" name="post[category]" value="java" />
2329
+ #
2330
+ # # Let's say that @user.receive_newsletter returns "no":
2331
+ # radio_button("receive_newsletter", "yes")
2332
+ # radio_button("receive_newsletter", "no")
2333
+ # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
2334
+ # # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
2335
+ def radio_button(method, tag_value, options = {})
2336
+ @template.radio_button(@object_name, method, tag_value, objectify_options(options))
2337
+ end
2338
+
2339
+ # Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
2340
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
2341
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
2342
+ # shown.
2343
+ #
2344
+ # ==== Examples
2345
+ # # Let's say that @signup.pass_confirm returns true:
2346
+ # hidden_field(:pass_confirm)
2347
+ # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="true" />
2348
+ #
2349
+ # # Let's say that @post.tag_list returns "blog, ruby":
2350
+ # hidden_field(:tag_list)
2351
+ # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="blog, ruby" />
2352
+ #
2353
+ # # Let's say that @user.token returns "abcde":
2354
+ # hidden_field(:token)
2355
+ # # => <input type="hidden" id="user_token" name="user[token]" value="abcde" />
2356
+ #
2357
+ def hidden_field(method, options = {})
2358
+ @emitted_hidden_id = true if method == :id
2359
+ @template.hidden_field(@object_name, method, objectify_options(options))
2360
+ end
2361
+
2362
+ # Returns a file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
2363
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
2364
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
2365
+ # shown.
2366
+ #
2367
+ # Using this method inside a +form_for+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>.
2368
+ #
2369
+ # ==== Options
2370
+ # * Creates standard HTML attributes for the tag.
2371
+ # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
2372
+ # * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files.
2373
+ # * <tt>:accept</tt> - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations.
2374
+ #
2375
+ # ==== Examples
2376
+ # # Let's say that @user has avatar:
2377
+ # file_field(:avatar)
2378
+ # # => <input type="file" id="user_avatar" name="user[avatar]" />
2379
+ #
2380
+ # # Let's say that @post has image:
2381
+ # file_field(:image, :multiple => true)
2382
+ # # => <input type="file" id="post_image" name="post[image][]" multiple="multiple" />
2383
+ #
2384
+ # # Let's say that @post has attached:
2385
+ # file_field(:attached, accept: 'text/html')
2386
+ # # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
2387
+ #
2388
+ # # Let's say that @post has image:
2389
+ # file_field(:image, accept: 'image/png,image/gif,image/jpeg')
2390
+ # # => <input type="file" id="post_image" name="post[image]" accept="image/png,image/gif,image/jpeg" />
2391
+ #
2392
+ # # Let's say that @attachment has file:
2393
+ # file_field(:file, class: 'file_input')
2394
+ # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
2395
+ def file_field(method, options = {})
2396
+ self.multipart = true
2397
+ @template.file_field(@object_name, method, objectify_options(options))
2398
+ end
2399
+
2400
+ # Add the submit button for the given form. When no value is given, it checks
2401
+ # if the object is a new resource or not to create the proper label:
2402
+ #
2403
+ # <%= form_for @post do |f| %>
2404
+ # <%= f.submit %>
2405
+ # <% end %>
2406
+ #
2407
+ # In the example above, if <tt>@post</tt> is a new record, it will use "Create Post" as
2408
+ # submit button label; otherwise, it uses "Update Post".
2409
+ #
2410
+ # Those labels can be customized using I18n under the +helpers.submit+ key and using
2411
+ # <tt>%{model}</tt> for translation interpolation:
2412
+ #
2413
+ # en:
2414
+ # helpers:
2415
+ # submit:
2416
+ # create: "Create a %{model}"
2417
+ # update: "Confirm changes to %{model}"
2418
+ #
2419
+ # It also searches for a key specific to the given object:
2420
+ #
2421
+ # en:
2422
+ # helpers:
2423
+ # submit:
2424
+ # post:
2425
+ # create: "Add %{model}"
2426
+ #
2427
+ def submit(value = nil, options = {})
2428
+ value, options = nil, value if value.is_a?(Hash)
2429
+ value ||= submit_default_value
2430
+ @template.submit_tag(value, options)
2431
+ end
2432
+
2433
+ # Add the submit button for the given form. When no value is given, it checks
2434
+ # if the object is a new resource or not to create the proper label:
2435
+ #
2436
+ # <%= form_for @post do |f| %>
2437
+ # <%= f.button %>
2438
+ # <% end %>
2439
+ #
2440
+ # In the example above, if <tt>@post</tt> is a new record, it will use "Create Post" as
2441
+ # button label; otherwise, it uses "Update Post".
2442
+ #
2443
+ # Those labels can be customized using I18n under the +helpers.submit+ key
2444
+ # (the same as submit helper) and using <tt>%{model}</tt> for translation interpolation:
2445
+ #
2446
+ # en:
2447
+ # helpers:
2448
+ # submit:
2449
+ # create: "Create a %{model}"
2450
+ # update: "Confirm changes to %{model}"
2451
+ #
2452
+ # It also searches for a key specific to the given object:
2453
+ #
2454
+ # en:
2455
+ # helpers:
2456
+ # submit:
2457
+ # post:
2458
+ # create: "Add %{model}"
2459
+ #
2460
+ # ==== Examples
2461
+ # button("Create post")
2462
+ # # => <button name='button' type='submit'>Create post</button>
2463
+ #
2464
+ # button do
2465
+ # content_tag(:strong, 'Ask me!')
2466
+ # end
2467
+ # # => <button name='button' type='submit'>
2468
+ # # <strong>Ask me!</strong>
2469
+ # # </button>
2470
+ #
2471
+ def button(value = nil, options = {}, &block)
2472
+ value, options = nil, value if value.is_a?(Hash)
2473
+ value ||= submit_default_value
2474
+ @template.button_tag(value, options, &block)
2475
+ end
2476
+
2477
+ def emitted_hidden_id? # :nodoc:
2478
+ @emitted_hidden_id ||= nil
2479
+ end
2480
+
2481
+ private
2482
+ def objectify_options(options)
2483
+ @default_options.merge(options.merge(object: @object))
2484
+ end
2485
+
2486
+ def submit_default_value
2487
+ object = convert_to_model(@object)
2488
+ key = object ? (object.persisted? ? :update : :create) : :submit
2489
+
2490
+ model = if object.respond_to?(:model_name)
2491
+ object.model_name.human
2492
+ else
2493
+ @object_name.to_s.humanize
2494
+ end
2495
+
2496
+ defaults = []
2497
+ # Object is a model and it is not overwritten by as and scope option.
2498
+ if object.respond_to?(:model_name) && object_name.to_s == model.downcase
2499
+ defaults << :"helpers.submit.#{object.model_name.i18n_key}.#{key}"
2500
+ else
2501
+ defaults << :"helpers.submit.#{object_name}.#{key}"
2502
+ end
2503
+ defaults << :"helpers.submit.#{key}"
2504
+ defaults << "#{key.to_s.humanize} #{model}"
2505
+
2506
+ I18n.t(defaults.shift, model: model, default: defaults)
2507
+ end
2508
+
2509
+ def nested_attributes_association?(association_name)
2510
+ @object.respond_to?("#{association_name}_attributes=")
2511
+ end
2512
+
2513
+ def fields_for_with_nested_attributes(association_name, association, options, block)
2514
+ name = "#{object_name}[#{association_name}_attributes]"
2515
+ association = convert_to_model(association)
2516
+
2517
+ if association.respond_to?(:persisted?)
2518
+ association = [association] if @object.send(association_name).respond_to?(:to_ary)
2519
+ elsif !association.respond_to?(:to_ary)
2520
+ association = @object.send(association_name)
2521
+ end
2522
+
2523
+ if association.respond_to?(:to_ary)
2524
+ explicit_child_index = options[:child_index]
2525
+ output = ActiveSupport::SafeBuffer.new
2526
+ association.each do |child|
2527
+ if explicit_child_index
2528
+ options[:child_index] = explicit_child_index.call if explicit_child_index.respond_to?(:call)
2529
+ else
2530
+ options[:child_index] = nested_child_index(name)
2531
+ end
2532
+ output << fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
2533
+ end
2534
+ output
2535
+ elsif association
2536
+ fields_for_nested_model(name, association, options, block)
2537
+ end
2538
+ end
2539
+
2540
+ def fields_for_nested_model(name, object, fields_options, block)
2541
+ object = convert_to_model(object)
2542
+ emit_hidden_id = object.persisted? && fields_options.fetch(:include_id) {
2543
+ options.fetch(:include_id, true)
2544
+ }
2545
+
2546
+ @template.fields_for(name, object, fields_options) do |f|
2547
+ output = @template.capture(f, &block)
2548
+ output.concat f.hidden_field(:id) if output && emit_hidden_id && !f.emitted_hidden_id?
2549
+ output
2550
+ end
2551
+ end
2552
+
2553
+ def nested_child_index(name)
2554
+ @nested_child_index[name] ||= -1
2555
+ @nested_child_index[name] += 1
2556
+ end
2557
+
2558
+ def convert_to_legacy_options(options)
2559
+ if options.key?(:skip_id)
2560
+ options[:include_id] = !options.delete(:skip_id)
2561
+ end
2562
+ end
2563
+ end
2564
+ end
2565
+
2566
+ ActiveSupport.on_load(:action_view) do
2567
+ cattr_accessor :default_form_builder, instance_writer: false, instance_reader: false, default: ::ActionView::Helpers::FormBuilder
2568
+ end
2569
+ end