omg-actionview 8.0.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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