omg-actionview 8.0.0.alpha1

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