actionview 4.2.11.1 → 7.0.2.4

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.

Potentially problematic release.


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

Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +229 -215
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +9 -8
  5. data/lib/action_view/base.rb +116 -43
  6. data/lib/action_view/buffers.rb +20 -3
  7. data/lib/action_view/cache_expiry.rb +66 -0
  8. data/lib/action_view/context.rb +8 -12
  9. data/lib/action_view/dependency_tracker/erb_tracker.rb +154 -0
  10. data/lib/action_view/dependency_tracker/ripper_tracker.rb +59 -0
  11. data/lib/action_view/dependency_tracker.rb +21 -122
  12. data/lib/action_view/digestor.rb +92 -85
  13. data/lib/action_view/flows.rb +15 -16
  14. data/lib/action_view/gem_version.rb +6 -4
  15. data/lib/action_view/helpers/active_model_helper.rb +17 -12
  16. data/lib/action_view/helpers/asset_tag_helper.rb +356 -101
  17. data/lib/action_view/helpers/asset_url_helper.rb +180 -74
  18. data/lib/action_view/helpers/atom_feed_helper.rb +21 -19
  19. data/lib/action_view/helpers/cache_helper.rb +156 -43
  20. data/lib/action_view/helpers/capture_helper.rb +21 -14
  21. data/lib/action_view/helpers/controller_helper.rb +16 -5
  22. data/lib/action_view/helpers/csp_helper.rb +26 -0
  23. data/lib/action_view/helpers/csrf_helper.rb +8 -6
  24. data/lib/action_view/helpers/date_helper.rb +288 -132
  25. data/lib/action_view/helpers/debug_helper.rb +9 -6
  26. data/lib/action_view/helpers/form_helper.rb +956 -173
  27. data/lib/action_view/helpers/form_options_helper.rb +178 -97
  28. data/lib/action_view/helpers/form_tag_helper.rb +220 -101
  29. data/lib/action_view/helpers/javascript_helper.rb +33 -19
  30. data/lib/action_view/helpers/number_helper.rb +88 -63
  31. data/lib/action_view/helpers/output_safety_helper.rb +38 -6
  32. data/lib/action_view/helpers/rendering_helper.rb +21 -10
  33. data/lib/action_view/helpers/sanitize_helper.rb +31 -32
  34. data/lib/action_view/helpers/tag_helper.rb +332 -71
  35. data/lib/action_view/helpers/tags/base.rb +123 -99
  36. data/lib/action_view/helpers/tags/check_box.rb +21 -20
  37. data/lib/action_view/helpers/tags/checkable.rb +4 -2
  38. data/lib/action_view/helpers/tags/collection_check_boxes.rb +12 -34
  39. data/lib/action_view/helpers/tags/collection_helpers.rb +69 -36
  40. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +6 -12
  41. data/lib/action_view/helpers/tags/collection_select.rb +5 -3
  42. data/lib/action_view/helpers/tags/color_field.rb +4 -3
  43. data/lib/action_view/helpers/tags/date_field.rb +3 -2
  44. data/lib/action_view/helpers/tags/date_select.rb +38 -37
  45. data/lib/action_view/helpers/tags/datetime_field.rb +4 -3
  46. data/lib/action_view/helpers/tags/datetime_local_field.rb +3 -2
  47. data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
  48. data/lib/action_view/helpers/tags/email_field.rb +2 -0
  49. data/lib/action_view/helpers/tags/file_field.rb +18 -0
  50. data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
  51. data/lib/action_view/helpers/tags/hidden_field.rb +6 -0
  52. data/lib/action_view/helpers/tags/label.rb +7 -2
  53. data/lib/action_view/helpers/tags/month_field.rb +3 -2
  54. data/lib/action_view/helpers/tags/number_field.rb +2 -0
  55. data/lib/action_view/helpers/tags/password_field.rb +3 -1
  56. data/lib/action_view/helpers/tags/placeholderable.rb +3 -1
  57. data/lib/action_view/helpers/tags/radio_button.rb +7 -6
  58. data/lib/action_view/helpers/tags/range_field.rb +2 -0
  59. data/lib/action_view/helpers/tags/search_field.rb +14 -9
  60. data/lib/action_view/helpers/tags/select.rb +11 -10
  61. data/lib/action_view/helpers/tags/tel_field.rb +2 -0
  62. data/lib/action_view/helpers/tags/text_area.rb +4 -2
  63. data/lib/action_view/helpers/tags/text_field.rb +8 -8
  64. data/lib/action_view/helpers/tags/time_field.rb +12 -2
  65. data/lib/action_view/helpers/tags/time_select.rb +2 -0
  66. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
  67. data/lib/action_view/helpers/tags/translator.rb +15 -16
  68. data/lib/action_view/helpers/tags/url_field.rb +2 -0
  69. data/lib/action_view/helpers/tags/week_field.rb +3 -2
  70. data/lib/action_view/helpers/tags/weekday_select.rb +28 -0
  71. data/lib/action_view/helpers/tags.rb +5 -2
  72. data/lib/action_view/helpers/text_helper.rb +80 -51
  73. data/lib/action_view/helpers/translation_helper.rb +120 -69
  74. data/lib/action_view/helpers/url_helper.rb +398 -171
  75. data/lib/action_view/helpers.rb +29 -27
  76. data/lib/action_view/layouts.rb +68 -63
  77. data/lib/action_view/log_subscriber.rb +77 -10
  78. data/lib/action_view/lookup_context.rb +137 -113
  79. data/lib/action_view/model_naming.rb +4 -2
  80. data/lib/action_view/path_set.rb +28 -32
  81. data/lib/action_view/railtie.rb +74 -13
  82. data/lib/action_view/record_identifier.rb +53 -26
  83. data/lib/action_view/render_parser.rb +188 -0
  84. data/lib/action_view/renderer/abstract_renderer.rb +152 -15
  85. data/lib/action_view/renderer/collection_renderer.rb +196 -0
  86. data/lib/action_view/renderer/object_renderer.rb +34 -0
  87. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +102 -0
  88. data/lib/action_view/renderer/partial_renderer.rb +51 -333
  89. data/lib/action_view/renderer/renderer.rb +68 -11
  90. data/lib/action_view/renderer/streaming_template_renderer.rb +60 -56
  91. data/lib/action_view/renderer/template_renderer.rb +87 -74
  92. data/lib/action_view/rendering.rb +73 -47
  93. data/lib/action_view/ripper_ast_parser.rb +198 -0
  94. data/lib/action_view/routing_url_for.rb +35 -24
  95. data/lib/action_view/tasks/cache_digests.rake +25 -0
  96. data/lib/action_view/template/error.rb +151 -41
  97. data/lib/action_view/template/handlers/builder.rb +12 -13
  98. data/lib/action_view/template/handlers/erb/erubi.rb +89 -0
  99. data/lib/action_view/template/handlers/erb.rb +29 -89
  100. data/lib/action_view/template/handlers/html.rb +11 -0
  101. data/lib/action_view/template/handlers/raw.rb +4 -4
  102. data/lib/action_view/template/handlers.rb +14 -10
  103. data/lib/action_view/template/html.rb +12 -13
  104. data/lib/action_view/template/inline.rb +22 -0
  105. data/lib/action_view/template/raw_file.rb +25 -0
  106. data/lib/action_view/template/renderable.rb +24 -0
  107. data/lib/action_view/template/resolver.rb +139 -300
  108. data/lib/action_view/template/sources/file.rb +17 -0
  109. data/lib/action_view/template/sources.rb +13 -0
  110. data/lib/action_view/template/text.rb +10 -12
  111. data/lib/action_view/template/types.rb +28 -26
  112. data/lib/action_view/template.rb +123 -91
  113. data/lib/action_view/template_details.rb +66 -0
  114. data/lib/action_view/template_path.rb +64 -0
  115. data/lib/action_view/test_case.rb +70 -53
  116. data/lib/action_view/testing/resolvers.rb +25 -35
  117. data/lib/action_view/unbound_template.rb +57 -0
  118. data/lib/action_view/version.rb +3 -1
  119. data/lib/action_view/view_paths.rb +73 -58
  120. data/lib/action_view.rb +16 -11
  121. data/lib/assets/compiled/rails-ujs.js +746 -0
  122. metadata +52 -32
  123. data/lib/action_view/helpers/record_tag_helper.rb +0 -108
  124. data/lib/action_view/tasks/dependencies.rake +0 -23
@@ -1,22 +1,25 @@
1
- require 'cgi'
2
- require 'action_view/helpers/date_helper'
3
- require 'action_view/helpers/tag_helper'
4
- require 'action_view/helpers/form_tag_helper'
5
- require 'action_view/helpers/active_model_helper'
6
- require 'action_view/model_naming'
7
- require 'active_support/core_ext/module/attribute_accessors'
8
- require 'active_support/core_ext/hash/slice'
9
- require 'active_support/core_ext/string/output_safety'
10
- require 'active_support/core_ext/string/inflections'
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/core_ext/module/attribute_accessors"
11
+ require "active_support/core_ext/hash/slice"
12
+ require "active_support/core_ext/string/output_safety"
13
+ require "active_support/core_ext/string/inflections"
11
14
 
12
15
  module ActionView
13
16
  # = Action View Form Helpers
14
- module Helpers
17
+ module Helpers # :nodoc:
15
18
  # Form helpers are designed to make working with resources much easier
16
19
  # compared to using vanilla HTML.
17
20
  #
18
21
  # Typically, a form designed to create or update a resource reflects the
19
- # identity of the resource in several ways: (i) the url that the form is
22
+ # identity of the resource in several ways: (i) the URL that the form is
20
23
  # sent to (the form element's +action+ attribute) should result in a request
21
24
  # being routed to the appropriate controller action (with the appropriate <tt>:id</tt>
22
25
  # parameter in the case of an existing resource), (ii) input fields should
@@ -66,9 +69,10 @@ module ActionView
66
69
  #
67
70
  # In particular, thanks to the conventions followed in the generated field names, the
68
71
  # controller gets a nested hash <tt>params[:person]</tt> with the person attributes
69
- # set in the form. That hash is ready to be passed to <tt>Person.create</tt>:
72
+ # set in the form. That hash is ready to be passed to <tt>Person.new</tt>:
70
73
  #
71
- # if @person = Person.create(params[:person])
74
+ # @person = Person.new(params[:person])
75
+ # if @person.save
72
76
  # # success
73
77
  # else
74
78
  # # error handling
@@ -110,6 +114,9 @@ module ActionView
110
114
  include FormTagHelper
111
115
  include UrlHelper
112
116
  include ModelNaming
117
+ include RecordIdentifier
118
+
119
+ attr_internal :default_form_builder
113
120
 
114
121
  # Creates a form that allows the user to create or update the attributes
115
122
  # of a specific model object.
@@ -138,6 +145,7 @@ module ActionView
138
145
  # will get expanded to
139
146
  #
140
147
  # <%= text_field :person, :first_name %>
148
+ #
141
149
  # which results in an HTML <tt><input></tt> tag whose +name+ attribute is
142
150
  # <tt>person[first_name]</tt>. This means that when the form is submitted,
143
151
  # the value entered by the user will be available in the controller as
@@ -158,7 +166,7 @@ module ActionView
158
166
  # So for example you may use a named route directly. When the model is
159
167
  # represented by a string or symbol, as in the example above, if the
160
168
  # <tt>:url</tt> option is not specified, by default the form will be
161
- # sent back to the current url (We will describe below an alternative
169
+ # sent back to the current URL (We will describe below an alternative
162
170
  # resource-oriented usage of +form_for+ in which the URL does not need
163
171
  # to be specified explicitly).
164
172
  # * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of
@@ -177,8 +185,7 @@ module ActionView
177
185
  # get the authenticity token from the <tt>meta</tt> tag, so embedding is
178
186
  # unnecessary unless you support browsers without JavaScript.
179
187
  # * <tt>:remote</tt> - If set to true, will allow the Unobtrusive
180
- # JavaScript drivers to control the submit behavior. By default this
181
- # behavior is an ajax submit.
188
+ # JavaScript drivers to control the submit behavior.
182
189
  # * <tt>:enforce_utf8</tt> - If set to false, a hidden input with name
183
190
  # utf8 is not output.
184
191
  # * <tt>:html</tt> - Optional HTML attributes for the form tag.
@@ -195,9 +202,9 @@ module ActionView
195
202
  # <%= f.submit %>
196
203
  # <% end %>
197
204
  #
198
- # This also works for the methods in FormOptionHelper and DateHelper that
205
+ # This also works for the methods in FormOptionsHelper and DateHelper that
199
206
  # are designed to work with an object as base, like
200
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
207
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
201
208
  #
202
209
  # === #form_for with a model object
203
210
  #
@@ -275,6 +282,12 @@ module ActionView
275
282
  # ...
276
283
  # <% end %>
277
284
  #
285
+ # You can omit the <tt>action</tt> attribute by passing <tt>url: false</tt>:
286
+ #
287
+ # <%= form_for(@post, url: false) do |f| %>
288
+ # ...
289
+ # <% end %>
290
+ #
278
291
  # You can also set the answer format, like this:
279
292
  #
280
293
  # <%= form_for(@post, format: :json) do |f| %>
@@ -314,10 +327,8 @@ module ActionView
314
327
  # remote: true
315
328
  #
316
329
  # in the options hash creates a form that will allow the unobtrusive JavaScript drivers to modify its
317
- # behavior. The expected default behavior is an XMLHttpRequest in the background instead of the regular
318
- # POST arrangement, but ultimately the behavior is the choice of the JavaScript driver implementor.
319
- # Even though it's using JavaScript to serialize the form elements, the form submission will work just like
320
- # a regular submission as viewed by the receiving side (all elements available in <tt>params</tt>).
330
+ # behavior. The form submission will work just like a regular submission as viewed by the receiving
331
+ # side (all elements available in <tt>params</tt>).
321
332
  #
322
333
  # Example:
323
334
  #
@@ -410,64 +421,361 @@ module ActionView
410
421
  #
411
422
  # To set an authenticity token you need to pass an <tt>:authenticity_token</tt> parameter
412
423
  #
413
- # <%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f|
424
+ # <%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f| %>
414
425
  # ...
415
426
  # <% end %>
416
427
  #
417
428
  # If you don't want to an authenticity token field be rendered at all just pass <tt>false</tt>:
418
429
  #
419
- # <%= form_for @invoice, url: external_url, authenticity_token: false do |f|
430
+ # <%= form_for @invoice, url: external_url, authenticity_token: false do |f| %>
420
431
  # ...
421
432
  # <% end %>
422
433
  def form_for(record, options = {}, &block)
423
434
  raise ArgumentError, "Missing block" unless block_given?
424
- html_options = options[:html] ||= {}
425
435
 
426
436
  case record
427
437
  when String, Symbol
438
+ model = nil
428
439
  object_name = record
429
- object = nil
430
440
  else
431
- object = record.is_a?(Array) ? record.last : record
441
+ model = record
442
+ object = _object_for_form_builder(record)
432
443
  raise ArgumentError, "First argument in form cannot contain nil or be empty" unless object
433
444
  object_name = options[:as] || model_name_from_record_or_class(object).param_key
434
- apply_form_for_options!(record, object, options)
445
+ apply_form_for_options!(object, options)
435
446
  end
436
447
 
437
- html_options[:data] = options.delete(:data) if options.has_key?(:data)
438
- html_options[:remote] = options.delete(:remote) if options.has_key?(:remote)
439
- html_options[:method] = options.delete(:method) if options.has_key?(:method)
440
- html_options[:enforce_utf8] = options.delete(:enforce_utf8) if options.has_key?(:enforce_utf8)
441
- html_options[:authenticity_token] = options.delete(:authenticity_token)
448
+ remote = options.delete(:remote)
442
449
 
443
- builder = instantiate_builder(object_name, object, options)
444
- output = capture(builder, &block)
445
- html_options[:multipart] ||= builder.multipart?
450
+ if remote && !embed_authenticity_token_in_remote_forms && options[:authenticity_token].blank?
451
+ options[:authenticity_token] = false
452
+ end
446
453
 
447
- html_options = html_options_for_form(options[:url] || {}, html_options)
448
- form_tag_with_body(html_options, output)
454
+ options[:model] = model
455
+ options[:scope] = object_name
456
+ options[:local] = !remote
457
+ options[:skip_default_ids] = false
458
+ options[:allow_method_names_outside_object] = options.fetch(:allow_method_names_outside_object, false)
459
+
460
+ form_with(**options, &block)
449
461
  end
450
462
 
451
- def apply_form_for_options!(record, object, options) #:nodoc:
463
+ def apply_form_for_options!(object, options) # :nodoc:
452
464
  object = convert_to_model(object)
453
465
 
454
466
  as = options[:as]
455
467
  namespace = options[:namespace]
456
- action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :patch] : [:new, :post]
468
+ action = object.respond_to?(:persisted?) && object.persisted? ? :edit : :new
469
+ options[:html] ||= {}
457
470
  options[:html].reverse_merge!(
458
471
  class: as ? "#{action}_#{as}" : dom_class(object, action),
459
472
  id: (as ? [namespace, action, as] : [namespace, dom_id(object, action)]).compact.join("_").presence,
460
- method: method
461
473
  )
462
-
463
- options[:url] ||= if options.key?(:format)
464
- polymorphic_path(record, format: options.delete(:format))
465
- else
466
- polymorphic_path(record, {})
467
- end
468
474
  end
469
475
  private :apply_form_for_options!
470
476
 
477
+ mattr_accessor :form_with_generates_remote_forms, default: true
478
+
479
+ mattr_accessor :form_with_generates_ids, default: false
480
+
481
+ mattr_accessor :multiple_file_field_include_hidden, default: false
482
+
483
+ # Creates a form tag based on mixing URLs, scopes, or models.
484
+ #
485
+ # # Using just a URL:
486
+ # <%= form_with url: posts_path do |form| %>
487
+ # <%= form.text_field :title %>
488
+ # <% end %>
489
+ # # =>
490
+ # <form action="/posts" method="post">
491
+ # <input type="text" name="title">
492
+ # </form>
493
+ #
494
+ # # With an intentionally empty URL:
495
+ # <%= form_with url: false do |form| %>
496
+ # <%= form.text_field :title %>
497
+ # <% end %>
498
+ # # =>
499
+ # <form method="post" data-remote="true">
500
+ # <input type="text" name="title">
501
+ # </form>
502
+ #
503
+ # # Adding a scope prefixes the input field names:
504
+ # <%= form_with scope: :post, url: posts_path do |form| %>
505
+ # <%= form.text_field :title %>
506
+ # <% end %>
507
+ # # =>
508
+ # <form action="/posts" method="post">
509
+ # <input type="text" name="post[title]">
510
+ # </form>
511
+ #
512
+ # # Using a model infers both the URL and scope:
513
+ # <%= form_with model: Post.new do |form| %>
514
+ # <%= form.text_field :title %>
515
+ # <% end %>
516
+ # # =>
517
+ # <form action="/posts" method="post">
518
+ # <input type="text" name="post[title]">
519
+ # </form>
520
+ #
521
+ # # An existing model makes an update form and fills out field values:
522
+ # <%= form_with model: Post.first do |form| %>
523
+ # <%= form.text_field :title %>
524
+ # <% end %>
525
+ # # =>
526
+ # <form action="/posts/1" method="post">
527
+ # <input type="hidden" name="_method" value="patch">
528
+ # <input type="text" name="post[title]" value="<the title of the post>">
529
+ # </form>
530
+ #
531
+ # # Though the fields don't have to correspond to model attributes:
532
+ # <%= form_with model: Cat.new do |form| %>
533
+ # <%= form.text_field :cats_dont_have_gills %>
534
+ # <%= form.text_field :but_in_forms_they_can %>
535
+ # <% end %>
536
+ # # =>
537
+ # <form action="/cats" method="post">
538
+ # <input type="text" name="cat[cats_dont_have_gills]">
539
+ # <input type="text" name="cat[but_in_forms_they_can]">
540
+ # </form>
541
+ #
542
+ # The parameters in the forms are accessible in controllers according to
543
+ # their name nesting. So inputs named +title+ and <tt>post[title]</tt> are
544
+ # accessible as <tt>params[:title]</tt> and <tt>params[:post][:title]</tt>
545
+ # respectively.
546
+ #
547
+ # For ease of comparison the examples above left out the submit button,
548
+ # as well as the auto generated hidden fields that enable UTF-8 support
549
+ # and adds an authenticity token needed for cross site request forgery
550
+ # protection.
551
+ #
552
+ # === Resource-oriented style
553
+ #
554
+ # In many of the examples just shown, the +:model+ passed to +form_with+
555
+ # is a _resource_. It corresponds to a set of RESTful routes, most likely
556
+ # defined via +resources+ in <tt>config/routes.rb</tt>.
557
+ #
558
+ # So when passing such a model record, Rails infers the URL and method.
559
+ #
560
+ # <%= form_with model: @post do |form| %>
561
+ # ...
562
+ # <% end %>
563
+ #
564
+ # is then equivalent to something like:
565
+ #
566
+ # <%= form_with scope: :post, url: post_path(@post), method: :patch do |form| %>
567
+ # ...
568
+ # <% end %>
569
+ #
570
+ # And for a new record
571
+ #
572
+ # <%= form_with model: Post.new do |form| %>
573
+ # ...
574
+ # <% end %>
575
+ #
576
+ # is equivalent to something like:
577
+ #
578
+ # <%= form_with scope: :post, url: posts_path do |form| %>
579
+ # ...
580
+ # <% end %>
581
+ #
582
+ # ==== +form_with+ options
583
+ #
584
+ # * <tt>:url</tt> - The URL the form submits to. Akin to values passed to
585
+ # +url_for+ or +link_to+. For example, you may use a named route
586
+ # directly. When a <tt>:scope</tt> is passed without a <tt>:url</tt> the
587
+ # form just submits to the current URL.
588
+ # * <tt>:method</tt> - The method to use when submitting the form, usually
589
+ # either "get" or "post". If "patch", "put", "delete", or another verb
590
+ # is used, a hidden input named <tt>_method</tt> is added to
591
+ # simulate the verb over post.
592
+ # * <tt>:format</tt> - The format of the route the form submits to.
593
+ # Useful when submitting to another resource type, like <tt>:json</tt>.
594
+ # Skipped if a <tt>:url</tt> is passed.
595
+ # * <tt>:scope</tt> - The scope to prefix input field names with and
596
+ # thereby how the submitted parameters are grouped in controllers.
597
+ # * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of
598
+ # id attributes on form elements. The namespace attribute will be prefixed
599
+ # with underscore on the generated HTML id.
600
+ # * <tt>:model</tt> - A model object to infer the <tt>:url</tt> and
601
+ # <tt>:scope</tt> by, plus fill out input field values.
602
+ # So if a +title+ attribute is set to "Ahoy!" then a +title+ input
603
+ # field's value would be "Ahoy!".
604
+ # If the model is a new record a create form is generated, if an
605
+ # existing record, however, an update form is generated.
606
+ # Pass <tt>:scope</tt> or <tt>:url</tt> to override the defaults.
607
+ # E.g. turn <tt>params[:post]</tt> into <tt>params[:article]</tt>.
608
+ # * <tt>:authenticity_token</tt> - Authenticity token to use in the form.
609
+ # Override with a custom authenticity token or pass <tt>false</tt> to
610
+ # skip the authenticity token field altogether.
611
+ # Useful when submitting to an external resource like a payment gateway
612
+ # that might limit the valid fields.
613
+ # Remote forms may omit the embedded authenticity token by setting
614
+ # <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>.
615
+ # This is helpful when fragment-caching the form. Remote forms
616
+ # get the authenticity token from the <tt>meta</tt> tag, so embedding is
617
+ # unnecessary unless you support browsers without JavaScript.
618
+ # * <tt>:local</tt> - Whether to use standard HTTP form submission.
619
+ # When set to <tt>true</tt>, the form is submitted via standard HTTP.
620
+ # When set to <tt>false</tt>, the form is submitted as a "remote form", which
621
+ # is handled by Rails UJS as an XHR. When unspecified, the behavior is derived
622
+ # from <tt>config.action_view.form_with_generates_remote_forms</tt> where the
623
+ # config's value is actually the inverse of what <tt>local</tt>'s value would be.
624
+ # As of Rails 6.1, that configuration option defaults to <tt>false</tt>
625
+ # (which has the equivalent effect of passing <tt>local: true</tt>).
626
+ # In previous versions of Rails, that configuration option defaults to
627
+ # <tt>true</tt> (the equivalent of passing <tt>local: false</tt>).
628
+ # * <tt>:skip_enforcing_utf8</tt> - If set to true, a hidden input with name
629
+ # utf8 is not output.
630
+ # * <tt>:builder</tt> - Override the object used to build the form.
631
+ # * <tt>:id</tt> - Optional HTML id attribute.
632
+ # * <tt>:class</tt> - Optional HTML class attribute.
633
+ # * <tt>:data</tt> - Optional HTML data attributes.
634
+ # * <tt>:html</tt> - Other optional HTML attributes for the form tag.
635
+ #
636
+ # === Examples
637
+ #
638
+ # When not passing a block, +form_with+ just generates an opening form tag.
639
+ #
640
+ # <%= form_with(model: @post, url: super_posts_path) %>
641
+ # <%= form_with(model: @post, scope: :article) %>
642
+ # <%= form_with(model: @post, format: :json) %>
643
+ # <%= form_with(model: @post, authenticity_token: false) %> # Disables the token.
644
+ #
645
+ # For namespaced routes, like +admin_post_url+:
646
+ #
647
+ # <%= form_with(model: [ :admin, @post ]) do |form| %>
648
+ # ...
649
+ # <% end %>
650
+ #
651
+ # If your resource has associations defined, for example, you want to add comments
652
+ # to the document given that the routes are set correctly:
653
+ #
654
+ # <%= form_with(model: [ @document, Comment.new ]) do |form| %>
655
+ # ...
656
+ # <% end %>
657
+ #
658
+ # Where <tt>@document = Document.find(params[:id])</tt>.
659
+ #
660
+ # === Mixing with other form helpers
661
+ #
662
+ # While +form_with+ uses a FormBuilder object it's possible to mix and
663
+ # match the stand-alone FormHelper methods and methods
664
+ # from FormTagHelper:
665
+ #
666
+ # <%= form_with scope: :person do |form| %>
667
+ # <%= form.text_field :first_name %>
668
+ # <%= form.text_field :last_name %>
669
+ #
670
+ # <%= text_area :person, :biography %>
671
+ # <%= check_box_tag "person[admin]", "1", @person.company.admin? %>
672
+ #
673
+ # <%= form.submit %>
674
+ # <% end %>
675
+ #
676
+ # Same goes for the methods in FormOptionsHelper and DateHelper designed
677
+ # to work with an object as a base, like
678
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
679
+ #
680
+ # === Setting the method
681
+ #
682
+ # You can force the form to use the full array of HTTP verbs by setting
683
+ #
684
+ # method: (:get|:post|:patch|:put|:delete)
685
+ #
686
+ # in the options hash. If the verb is not GET or POST, which are natively
687
+ # supported by HTML forms, the form will be set to POST and a hidden input
688
+ # called _method will carry the intended verb for the server to interpret.
689
+ #
690
+ # === Setting HTML options
691
+ #
692
+ # You can set data attributes directly in a data hash, but HTML options
693
+ # besides id and class must be wrapped in an HTML key:
694
+ #
695
+ # <%= form_with(model: @post, data: { behavior: "autosave" }, html: { name: "go" }) do |form| %>
696
+ # ...
697
+ # <% end %>
698
+ #
699
+ # generates
700
+ #
701
+ # <form action="/posts/123" method="post" data-behavior="autosave" name="go">
702
+ # <input name="_method" type="hidden" value="patch" />
703
+ # ...
704
+ # </form>
705
+ #
706
+ # === Removing hidden model id's
707
+ #
708
+ # The +form_with+ method automatically includes the model id as a hidden field in the form.
709
+ # This is used to maintain the correlation between the form data and its associated model.
710
+ # Some ORM systems do not use IDs on nested models so in this case you want to be able
711
+ # to disable the hidden id.
712
+ #
713
+ # In the following example the Post model has many Comments stored within it in a NoSQL database,
714
+ # thus there is no primary key for comments.
715
+ #
716
+ # <%= form_with(model: @post) do |form| %>
717
+ # <%= form.fields(:comments, skip_id: true) do |fields| %>
718
+ # ...
719
+ # <% end %>
720
+ # <% end %>
721
+ #
722
+ # === Customized form builders
723
+ #
724
+ # You can also build forms using a customized FormBuilder class. Subclass
725
+ # FormBuilder and override or define some more helpers, then use your
726
+ # custom builder. For example, let's say you made a helper to
727
+ # automatically add labels to form inputs.
728
+ #
729
+ # <%= form_with model: @person, url: { action: "create" }, builder: LabellingFormBuilder do |form| %>
730
+ # <%= form.text_field :first_name %>
731
+ # <%= form.text_field :last_name %>
732
+ # <%= form.text_area :biography %>
733
+ # <%= form.check_box :admin %>
734
+ # <%= form.submit %>
735
+ # <% end %>
736
+ #
737
+ # In this case, if you use:
738
+ #
739
+ # <%= render form %>
740
+ #
741
+ # The rendered template is <tt>people/_labelling_form</tt> and the local
742
+ # variable referencing the form builder is called
743
+ # <tt>labelling_form</tt>.
744
+ #
745
+ # The custom FormBuilder class is automatically merged with the options
746
+ # of a nested +fields+ call, unless it's explicitly set.
747
+ #
748
+ # In many cases you will want to wrap the above in another helper, so you
749
+ # could do something like the following:
750
+ #
751
+ # def labelled_form_with(**options, &block)
752
+ # form_with(**options.merge(builder: LabellingFormBuilder), &block)
753
+ # end
754
+ def form_with(model: nil, scope: nil, url: nil, format: nil, **options, &block)
755
+ options = { allow_method_names_outside_object: true, skip_default_ids: !form_with_generates_ids }.merge!(options)
756
+
757
+ if model
758
+ if url != false
759
+ url ||= polymorphic_path(model, format: format)
760
+ end
761
+
762
+ model = _object_for_form_builder(model)
763
+ scope ||= model_name_from_record_or_class(model).param_key
764
+ end
765
+
766
+ if block_given?
767
+ builder = instantiate_builder(scope, model, options)
768
+ output = capture(builder, &block)
769
+ options[:multipart] ||= builder.multipart?
770
+
771
+ html_options = html_options_for_form_with(url, model, **options)
772
+ form_tag_with_body(html_options, output)
773
+ else
774
+ html_options = html_options_for_form_with(url, model, **options)
775
+ form_tag_html(html_options)
776
+ end
777
+ end
778
+
471
779
  # Creates a scope around a specific model object like form_for, but
472
780
  # doesn't create the form tags themselves. This makes fields_for suitable
473
781
  # for specifying additional model objects in the same form.
@@ -525,9 +833,9 @@ module ActionView
525
833
  # _class_ of the model object, e.g. if <tt>@person.permission</tt>, is
526
834
  # of class +Permission+, the field will still be named <tt>permission[admin]</tt>.
527
835
  #
528
- # Note: This also works for the methods in FormOptionHelper and
836
+ # Note: This also works for the methods in FormOptionsHelper and
529
837
  # DateHelper that are designed to work with an object as base, like
530
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
838
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
531
839
  #
532
840
  # === Nested Attributes Examples
533
841
  #
@@ -593,7 +901,7 @@ module ActionView
593
901
  #
594
902
  # Now, when you use a form element with the <tt>_destroy</tt> parameter,
595
903
  # with a value that evaluates to +true+, you will destroy the associated
596
- # model (eg. 1, '1', true, or 'true'):
904
+ # model (e.g. 1, '1', true, or 'true'):
597
905
  #
598
906
  # <%= form_for @person do |person_form| %>
599
907
  # ...
@@ -682,7 +990,7 @@ module ActionView
682
990
  # This will allow you to specify which models to destroy in the
683
991
  # attributes hash by adding a form element for the <tt>_destroy</tt>
684
992
  # parameter with a value that evaluates to +true+
685
- # (eg. 1, '1', true, or 'true'):
993
+ # (e.g. 1, '1', true, or 'true'):
686
994
  #
687
995
  # <%= form_for @person do |person_form| %>
688
996
  # ...
@@ -710,7 +1018,65 @@ module ActionView
710
1018
  # hidden field is not needed and you can pass <tt>include_id: false</tt>
711
1019
  # to prevent fields_for from rendering it automatically.
712
1020
  def fields_for(record_name, record_object = nil, options = {}, &block)
713
- builder = instantiate_builder(record_name, record_object, options)
1021
+ options = { model: record_object, allow_method_names_outside_object: false, skip_default_ids: false }.merge!(options)
1022
+
1023
+ fields(record_name, **options, &block)
1024
+ end
1025
+
1026
+ # Scopes input fields with either an explicit scope or model.
1027
+ # Like +form_with+ does with <tt>:scope</tt> or <tt>:model</tt>,
1028
+ # except it doesn't output the form tags.
1029
+ #
1030
+ # # Using a scope prefixes the input field names:
1031
+ # <%= fields :comment do |fields| %>
1032
+ # <%= fields.text_field :body %>
1033
+ # <% end %>
1034
+ # # => <input type="text" name="comment[body]">
1035
+ #
1036
+ # # Using a model infers the scope and assigns field values:
1037
+ # <%= fields model: Comment.new(body: "full bodied") do |fields| %>
1038
+ # <%= fields.text_field :body %>
1039
+ # <% end %>
1040
+ # # => <input type="text" name="comment[body]" value="full bodied">
1041
+ #
1042
+ # # Using +fields+ with +form_with+:
1043
+ # <%= form_with model: @post do |form| %>
1044
+ # <%= form.text_field :title %>
1045
+ #
1046
+ # <%= form.fields :comment do |fields| %>
1047
+ # <%= fields.text_field :body %>
1048
+ # <% end %>
1049
+ # <% end %>
1050
+ #
1051
+ # Much like +form_with+ a FormBuilder instance associated with the scope
1052
+ # or model is yielded, so any generated field names are prefixed with
1053
+ # either the passed scope or the scope inferred from the <tt>:model</tt>.
1054
+ #
1055
+ # === Mixing with other form helpers
1056
+ #
1057
+ # While +form_with+ uses a FormBuilder object it's possible to mix and
1058
+ # match the stand-alone FormHelper methods and methods
1059
+ # from FormTagHelper:
1060
+ #
1061
+ # <%= fields model: @comment do |fields| %>
1062
+ # <%= fields.text_field :body %>
1063
+ #
1064
+ # <%= text_area :commenter, :biography %>
1065
+ # <%= check_box_tag "comment[all_caps]", "1", @comment.commenter.hulk_mode? %>
1066
+ # <% end %>
1067
+ #
1068
+ # Same goes for the methods in FormOptionsHelper and DateHelper designed
1069
+ # to work with an object as a base, like
1070
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
1071
+ def fields(scope = nil, model: nil, **options, &block)
1072
+ options = { allow_method_names_outside_object: true, skip_default_ids: !form_with_generates_ids }.merge!(options)
1073
+
1074
+ if model
1075
+ model = _object_for_form_builder(model)
1076
+ scope ||= model_name_from_record_or_class(model).param_key
1077
+ end
1078
+
1079
+ builder = instantiate_builder(scope, model, options)
714
1080
  capture(builder, &block)
715
1081
  end
716
1082
 
@@ -758,8 +1124,18 @@ module ActionView
758
1124
  # label(:post, :privacy, "Public Post", value: "public")
759
1125
  # # => <label for="post_privacy_public">Public Post</label>
760
1126
  #
1127
+ # label(:post, :cost) do |translation|
1128
+ # content_tag(:span, translation, class: "cost_label")
1129
+ # end
1130
+ # # => <label for="post_cost"><span class="cost_label">Total cost</span></label>
1131
+ #
1132
+ # label(:post, :cost) do |builder|
1133
+ # content_tag(:span, builder.translation, class: "cost_label")
1134
+ # end
1135
+ # # => <label for="post_cost"><span class="cost_label">Total cost</span></label>
1136
+ #
761
1137
  # label(:post, :terms) do
762
- # 'Accept <a href="/terms">Terms</a>.'.html_safe
1138
+ # raw('Accept <a href="/terms">Terms</a>.')
763
1139
  # end
764
1140
  # # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
765
1141
  def label(object_name, method, content_or_options = nil, options = nil, &block)
@@ -778,6 +1154,9 @@ module ActionView
778
1154
  # text_field(:post, :title, class: "create_input")
779
1155
  # # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
780
1156
  #
1157
+ # text_field(:post, :title, maxlength: 30, class: "title_input")
1158
+ # # => <input type="text" id="post_title" name="post[title]" maxlength="30" size="30" value="#{@post.title}" class="title_input" />
1159
+ #
781
1160
  # text_field(:session, :user, onchange: "if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }")
782
1161
  # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange="if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }"/>
783
1162
  #
@@ -837,14 +1216,15 @@ module ActionView
837
1216
  # * Creates standard HTML attributes for the tag.
838
1217
  # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
839
1218
  # * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files.
1219
+ # * <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.
840
1220
  # * <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.
841
1221
  #
842
1222
  # ==== Examples
843
1223
  # file_field(:user, :avatar)
844
1224
  # # => <input type="file" id="user_avatar" name="user[avatar]" />
845
1225
  #
846
- # file_field(:post, :image, :multiple => true)
847
- # # => <input type="file" id="post_image" name="post[image]" multiple="true" />
1226
+ # file_field(:post, :image, multiple: true)
1227
+ # # => <input type="file" id="post_image" name="post[image][]" multiple="multiple" />
848
1228
  #
849
1229
  # file_field(:post, :attached, accept: 'text/html')
850
1230
  # # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
@@ -855,7 +1235,9 @@ module ActionView
855
1235
  # file_field(:attachment, :file, class: 'file_input')
856
1236
  # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
857
1237
  def file_field(object_name, method, options = {})
858
- Tags::FileField.new(object_name, method, self, options).render
1238
+ options = { include_hidden: multiple_file_field_include_hidden }.merge!(options)
1239
+
1240
+ Tags::FileField.new(object_name, method, self, convert_direct_upload_option_to_url(options.dup)).render
859
1241
  end
860
1242
 
861
1243
  # Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
@@ -892,6 +1274,12 @@ module ActionView
892
1274
  # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
893
1275
  # while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
894
1276
  #
1277
+ # ==== Options
1278
+ #
1279
+ # * Any standard HTML attributes for the tag can be passed in, for example +:class+.
1280
+ # * <tt>:checked</tt> - +true+ or +false+ forces the state of the checkbox to be checked or not.
1281
+ # * <tt>:include_hidden</tt> - If set to false, the auxiliary hidden field described below will not be generated.
1282
+ #
895
1283
  # ==== Gotcha
896
1284
  #
897
1285
  # The HTML specification says unchecked check boxes are not successful, and
@@ -905,7 +1293,7 @@ module ActionView
905
1293
  # wouldn't update the flag.
906
1294
  #
907
1295
  # To prevent this the helper generates an auxiliary hidden field before
908
- # the very check box. The hidden field has the same name and its
1296
+ # every check box. The hidden field has the same name and its
909
1297
  # attributes mimic an unchecked check box.
910
1298
  #
911
1299
  # This way, the client either sends only the hidden field (representing
@@ -929,6 +1317,8 @@ module ActionView
929
1317
  # In that case it is preferable to either use +check_box_tag+ or to use
930
1318
  # hashes instead of arrays.
931
1319
  #
1320
+ # ==== Examples
1321
+ #
932
1322
  # # Let's say that @post.validated? is 1:
933
1323
  # check_box("post", "validated")
934
1324
  # # => <input name="post[validated]" type="hidden" value="0" />
@@ -959,6 +1349,7 @@ module ActionView
959
1349
  # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
960
1350
  # # <input type="radio" id="post_category_java" name="post[category]" value="java" />
961
1351
  #
1352
+ # # Let's say that @user.receive_newsletter returns "no":
962
1353
  # radio_button("user", "receive_newsletter", "yes")
963
1354
  # radio_button("user", "receive_newsletter", "no")
964
1355
  # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
@@ -1014,7 +1405,7 @@ module ActionView
1014
1405
  # date_field("user", "born_on")
1015
1406
  # # => <input id="user_born_on" name="user[born_on]" type="date" />
1016
1407
  #
1017
- # The default value is generated by trying to call "to_date"
1408
+ # The default value is generated by trying to call +strftime+ with "%Y-%m-%d"
1018
1409
  # on the object's value, which makes it behave as expected for instances
1019
1410
  # of DateTime and ActiveSupport::TimeWithZone. You can still override that
1020
1411
  # by passing the "value" option explicitly, e.g.
@@ -1042,8 +1433,9 @@ module ActionView
1042
1433
  # Returns a text_field of type "time".
1043
1434
  #
1044
1435
  # The default value is generated by trying to call +strftime+ with "%T.%L"
1045
- # on the objects's value. It is still possible to override that
1046
- # by passing the "value" option.
1436
+ # on the object's value. If you pass <tt>include_seconds: false</tt>, it will be
1437
+ # formatted by trying to call +strftime+ with "%H:%M" on the object's value.
1438
+ # It is also possible to override this by passing the "value" option.
1047
1439
  #
1048
1440
  # === Options
1049
1441
  # * Accepts same options as time_field_tag
@@ -1064,42 +1456,19 @@ module ActionView
1064
1456
  # time_field("task", "started_at", min: "01:00:00")
1065
1457
  # # => <input id="task_started_at" name="task[started_at]" type="time" min="01:00:00.000" />
1066
1458
  #
1459
+ # By default, provided times will be formatted including seconds. You can render just the hour
1460
+ # and minute by passing <tt>include_seconds: false</tt>. Some browsers will render a simpler UI
1461
+ # if you exclude seconds in the timestamp format.
1462
+ #
1463
+ # time_field("task", "started_at", value: Time.now, include_seconds: false)
1464
+ # # => <input id="task_started_at" name="task[started_at]" type="time" value="01:00" />
1067
1465
  def time_field(object_name, method, options = {})
1068
1466
  Tags::TimeField.new(object_name, method, self, options).render
1069
1467
  end
1070
1468
 
1071
- # Returns a text_field of type "datetime".
1072
- #
1073
- # datetime_field("user", "born_on")
1074
- # # => <input id="user_born_on" name="user[born_on]" type="datetime" />
1075
- #
1076
- # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T.%L%z"
1077
- # on the object's value, which makes it behave as expected for instances
1078
- # of DateTime and ActiveSupport::TimeWithZone.
1079
- #
1080
- # @user.born_on = Date.new(1984, 1, 12)
1081
- # datetime_field("user", "born_on")
1082
- # # => <input id="user_born_on" name="user[born_on]" type="datetime" value="1984-01-12T00:00:00.000+0000" />
1083
- #
1084
- # You can create values for the "min" and "max" attributes by passing
1085
- # instances of Date or Time to the options hash.
1086
- #
1087
- # datetime_field("user", "born_on", min: Date.today)
1088
- # # => <input id="user_born_on" name="user[born_on]" type="datetime" min="2014-05-20T00:00:00.000+0000" />
1089
- #
1090
- # Alternatively, you can pass a String formatted as an ISO8601 datetime
1091
- # with UTC offset as the values for "min" and "max."
1092
- #
1093
- # datetime_field("user", "born_on", min: "2014-05-20T00:00:00+0000")
1094
- # # => <input id="user_born_on" name="user[born_on]" type="datetime" min="2014-05-20T00:00:00.000+0000" />
1095
- #
1096
- def datetime_field(object_name, method, options = {})
1097
- Tags::DatetimeField.new(object_name, method, self, options).render
1098
- end
1099
-
1100
1469
  # Returns a text_field of type "datetime-local".
1101
1470
  #
1102
- # datetime_local_field("user", "born_on")
1471
+ # datetime_field("user", "born_on")
1103
1472
  # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" />
1104
1473
  #
1105
1474
  # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T"
@@ -1107,25 +1476,27 @@ module ActionView
1107
1476
  # of DateTime and ActiveSupport::TimeWithZone.
1108
1477
  #
1109
1478
  # @user.born_on = Date.new(1984, 1, 12)
1110
- # datetime_local_field("user", "born_on")
1479
+ # datetime_field("user", "born_on")
1111
1480
  # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" value="1984-01-12T00:00:00" />
1112
1481
  #
1113
1482
  # You can create values for the "min" and "max" attributes by passing
1114
1483
  # instances of Date or Time to the options hash.
1115
1484
  #
1116
- # datetime_local_field("user", "born_on", min: Date.today)
1485
+ # datetime_field("user", "born_on", min: Date.today)
1117
1486
  # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" />
1118
1487
  #
1119
1488
  # Alternatively, you can pass a String formatted as an ISO8601 datetime as
1120
1489
  # the values for "min" and "max."
1121
1490
  #
1122
- # datetime_local_field("user", "born_on", min: "2014-05-20T00:00:00")
1491
+ # datetime_field("user", "born_on", min: "2014-05-20T00:00:00")
1123
1492
  # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" />
1124
1493
  #
1125
- def datetime_local_field(object_name, method, options = {})
1494
+ def datetime_field(object_name, method, options = {})
1126
1495
  Tags::DatetimeLocalField.new(object_name, method, self, options).render
1127
1496
  end
1128
1497
 
1498
+ alias datetime_local_field datetime_field
1499
+
1129
1500
  # Returns a text_field of type "month".
1130
1501
  #
1131
1502
  # month_field("user", "born_on")
@@ -1194,7 +1565,25 @@ module ActionView
1194
1565
  Tags::RangeField.new(object_name, method, self, options).render
1195
1566
  end
1196
1567
 
1568
+ def _object_for_form_builder(object) # :nodoc:
1569
+ object.is_a?(Array) ? object.last : object
1570
+ end
1571
+
1197
1572
  private
1573
+ def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: !form_with_generates_remote_forms,
1574
+ skip_enforcing_utf8: nil, **options)
1575
+ html_options = options.slice(:id, :class, :multipart, :method, :data, :authenticity_token).merge!(html)
1576
+ html_options[:remote] = html.delete(:remote) || !local
1577
+ html_options[:method] ||= :patch if model.respond_to?(:persisted?) && model.persisted?
1578
+ if skip_enforcing_utf8.nil?
1579
+ if options.key?(:enforce_utf8)
1580
+ html_options[:enforce_utf8] = options[:enforce_utf8]
1581
+ end
1582
+ else
1583
+ html_options[:enforce_utf8] = !skip_enforcing_utf8
1584
+ end
1585
+ html_options_for_form(url_for_options.nil? ? {} : url_for_options, html_options)
1586
+ end
1198
1587
 
1199
1588
  def instantiate_builder(record_name, record_object, options)
1200
1589
  case record_name
@@ -1203,7 +1592,7 @@ module ActionView
1203
1592
  object_name = record_name
1204
1593
  else
1205
1594
  object = record_name
1206
- object_name = model_name_from_record_or_class(object).param_key
1595
+ object_name = model_name_from_record_or_class(object).param_key if object
1207
1596
  end
1208
1597
 
1209
1598
  builder = options[:builder] || default_form_builder_class
@@ -1211,7 +1600,7 @@ module ActionView
1211
1600
  end
1212
1601
 
1213
1602
  def default_form_builder_class
1214
- builder = ActionView::Base.default_form_builder
1603
+ builder = default_form_builder || ActionView::Base.default_form_builder
1215
1604
  builder.respond_to?(:constantize) ? builder.constantize : builder
1216
1605
  end
1217
1606
  end
@@ -1229,7 +1618,7 @@ module ActionView
1229
1618
  # In the above block, a +FormBuilder+ object is yielded as the
1230
1619
  # +person_form+ variable. This allows you to generate the +text_field+
1231
1620
  # and +check_box+ fields by specifying their eponymous methods, which
1232
- # modify the underlying template and associates the +@person+ model object
1621
+ # modify the underlying template and associates the <tt>@person</tt> model object
1233
1622
  # with the form.
1234
1623
  #
1235
1624
  # The +FormBuilder+ object can be thought of as serving as a proxy for the
@@ -1268,14 +1657,15 @@ module ActionView
1268
1657
  include ModelNaming
1269
1658
 
1270
1659
  # The methods which wrap a form helper call.
1271
- class_attribute :field_helpers
1272
- self.field_helpers = [:fields_for, :label, :text_field, :password_field,
1273
- :hidden_field, :file_field, :text_area, :check_box,
1274
- :radio_button, :color_field, :search_field,
1275
- :telephone_field, :phone_field, :date_field,
1276
- :time_field, :datetime_field, :datetime_local_field,
1277
- :month_field, :week_field, :url_field, :email_field,
1278
- :number_field, :range_field]
1660
+ class_attribute :field_helpers, default: [
1661
+ :fields_for, :fields, :label, :text_field, :password_field,
1662
+ :hidden_field, :file_field, :text_area, :check_box,
1663
+ :radio_button, :color_field, :search_field,
1664
+ :telephone_field, :phone_field, :date_field,
1665
+ :time_field, :datetime_field, :datetime_local_field,
1666
+ :month_field, :week_field, :url_field, :email_field,
1667
+ :number_field, :range_field
1668
+ ]
1279
1669
 
1280
1670
  attr_accessor :object_name, :object, :options
1281
1671
 
@@ -1291,7 +1681,7 @@ module ActionView
1291
1681
  end
1292
1682
 
1293
1683
  def self._to_partial_path
1294
- @_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, '')
1684
+ @_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, "")
1295
1685
  end
1296
1686
 
1297
1687
  def to_partial_path
@@ -1305,23 +1695,312 @@ module ActionView
1305
1695
  def initialize(object_name, object, template, options)
1306
1696
  @nested_child_index = {}
1307
1697
  @object_name, @object, @template, @options = object_name, object, template, options
1308
- @default_options = @options ? @options.slice(:index, :namespace) : {}
1309
- if @object_name.to_s.match(/\[\]$/)
1310
- if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
1698
+ @default_options = @options ? @options.slice(:index, :namespace, :skip_default_ids, :allow_method_names_outside_object) : {}
1699
+ @default_html_options = @default_options.except(:skip_default_ids, :allow_method_names_outside_object)
1700
+
1701
+ convert_to_legacy_options(@options)
1702
+
1703
+ if @object_name&.end_with?("[]")
1704
+ if (object ||= @template.instance_variable_get("@#{@object_name[0..-3]}")) && object.respond_to?(:to_param)
1311
1705
  @auto_index = object.to_param
1312
1706
  else
1313
1707
  raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
1314
1708
  end
1315
1709
  end
1710
+
1316
1711
  @multipart = nil
1317
1712
  @index = options[:index] || options[:child_index]
1318
1713
  end
1319
1714
 
1320
- (field_helpers - [:label, :check_box, :radio_button, :fields_for, :hidden_field, :file_field]).each do |selector|
1715
+ # Generate an HTML <tt>id</tt> attribute value.
1716
+ #
1717
+ # return the <tt><form></tt> element's <tt>id</tt> attribute.
1718
+ #
1719
+ # <%= form_for @post do |f| %>
1720
+ # <%# ... %>
1721
+ #
1722
+ # <% content_for :sticky_footer do %>
1723
+ # <%= form.button(form: f.id) %>
1724
+ # <% end %>
1725
+ # <% end %>
1726
+ #
1727
+ # In the example above, the <tt>:sticky_footer</tt> content area will
1728
+ # exist outside of the <tt><form></tt> element. By declaring the
1729
+ # <tt>form</tt> HTML attribute, we hint to the browser that the generated
1730
+ # <tt><button></tt> element should be treated as the <tt><form></tt>
1731
+ # element's submit button, regardless of where it exists in the DOM.
1732
+ def id
1733
+ options.dig(:html, :id)
1734
+ end
1735
+
1736
+ # Generate an HTML <tt>id</tt> attribute value for the given field
1737
+ #
1738
+ # Return the value generated by the <tt>FormBuilder</tt> for the given
1739
+ # attribute name.
1740
+ #
1741
+ # <%= form_for @post do |f| %>
1742
+ # <%= f.label :title %>
1743
+ # <%= f.text_field :title, aria: { describedby: f.field_id(:title, :error) } %>
1744
+ # <%= tag.span("is blank", id: f.field_id(:title, :error) %>
1745
+ # <% end %>
1746
+ #
1747
+ # In the example above, the <tt><input type="text"></tt> element built by
1748
+ # the call to <tt>FormBuilder#text_field</tt> declares an
1749
+ # <tt>aria-describedby</tt> attribute referencing the <tt><span></tt>
1750
+ # element, sharing a common <tt>id</tt> root (<tt>post_title</tt>, in this
1751
+ # case).
1752
+ def field_id(method, *suffixes, namespace: @options[:namespace], index: @index)
1753
+ @template.field_id(@object_name, method, *suffixes, namespace: namespace, index: index)
1754
+ end
1755
+
1756
+ # Generate an HTML <tt>name</tt> attribute value for the given name and
1757
+ # field combination
1758
+ #
1759
+ # Return the value generated by the <tt>FormBuilder</tt> for the given
1760
+ # attribute name.
1761
+ #
1762
+ # <%= form_for @post do |f| %>
1763
+ # <%= f.text_field :title, name: f.field_name(:title, :subtitle) %>
1764
+ # <%# => <input type="text" name="post[title][subtitle]">
1765
+ # <% end %>
1766
+ #
1767
+ # <%= form_for @post do |f| %>
1768
+ # <%= f.field_tag :tag, name: f.field_name(:tag, multiple: true) %>
1769
+ # <%# => <input type="text" name="post[tag][]">
1770
+ # <% end %>
1771
+ #
1772
+ def field_name(method, *methods, multiple: false, index: @index)
1773
+ object_name = @options.fetch(:as) { @object_name }
1774
+
1775
+ @template.field_name(object_name, method, *methods, index: index, multiple: multiple)
1776
+ end
1777
+
1778
+ ##
1779
+ # :method: text_field
1780
+ #
1781
+ # :call-seq: text_field(method, options = {})
1782
+ #
1783
+ # Wraps ActionView::Helpers::FormHelper#text_field for form builders:
1784
+ #
1785
+ # <%= form_with model: @user do |f| %>
1786
+ # <%= f.text_field :name %>
1787
+ # <% end %>
1788
+ #
1789
+ # Please refer to the documentation of the base helper for details.
1790
+
1791
+ ##
1792
+ # :method: password_field
1793
+ #
1794
+ # :call-seq: password_field(method, options = {})
1795
+ #
1796
+ # Wraps ActionView::Helpers::FormHelper#password_field for form builders:
1797
+ #
1798
+ # <%= form_with model: @user do |f| %>
1799
+ # <%= f.password_field :password %>
1800
+ # <% end %>
1801
+ #
1802
+ # Please refer to the documentation of the base helper for details.
1803
+
1804
+ ##
1805
+ # :method: text_area
1806
+ #
1807
+ # :call-seq: text_area(method, options = {})
1808
+ #
1809
+ # Wraps ActionView::Helpers::FormHelper#text_area for form builders:
1810
+ #
1811
+ # <%= form_with model: @user do |f| %>
1812
+ # <%= f.text_area :detail %>
1813
+ # <% end %>
1814
+ #
1815
+ # Please refer to the documentation of the base helper for details.
1816
+
1817
+ ##
1818
+ # :method: color_field
1819
+ #
1820
+ # :call-seq: color_field(method, options = {})
1821
+ #
1822
+ # Wraps ActionView::Helpers::FormHelper#color_field for form builders:
1823
+ #
1824
+ # <%= form_with model: @user do |f| %>
1825
+ # <%= f.color_field :favorite_color %>
1826
+ # <% end %>
1827
+ #
1828
+ # Please refer to the documentation of the base helper for details.
1829
+
1830
+ ##
1831
+ # :method: search_field
1832
+ #
1833
+ # :call-seq: search_field(method, options = {})
1834
+ #
1835
+ # Wraps ActionView::Helpers::FormHelper#search_field for form builders:
1836
+ #
1837
+ # <%= form_with model: @user do |f| %>
1838
+ # <%= f.search_field :name %>
1839
+ # <% end %>
1840
+ #
1841
+ # Please refer to the documentation of the base helper for details.
1842
+
1843
+ ##
1844
+ # :method: telephone_field
1845
+ #
1846
+ # :call-seq: telephone_field(method, options = {})
1847
+ #
1848
+ # Wraps ActionView::Helpers::FormHelper#telephone_field for form builders:
1849
+ #
1850
+ # <%= form_with model: @user do |f| %>
1851
+ # <%= f.telephone_field :phone %>
1852
+ # <% end %>
1853
+ #
1854
+ # Please refer to the documentation of the base helper for details.
1855
+
1856
+ ##
1857
+ # :method: phone_field
1858
+ #
1859
+ # :call-seq: phone_field(method, options = {})
1860
+ #
1861
+ # Wraps ActionView::Helpers::FormHelper#phone_field for form builders:
1862
+ #
1863
+ # <%= form_with model: @user do |f| %>
1864
+ # <%= f.phone_field :phone %>
1865
+ # <% end %>
1866
+ #
1867
+ # Please refer to the documentation of the base helper for details.
1868
+
1869
+ ##
1870
+ # :method: date_field
1871
+ #
1872
+ # :call-seq: date_field(method, options = {})
1873
+ #
1874
+ # Wraps ActionView::Helpers::FormHelper#date_field for form builders:
1875
+ #
1876
+ # <%= form_with model: @user do |f| %>
1877
+ # <%= f.date_field :born_on %>
1878
+ # <% end %>
1879
+ #
1880
+ # Please refer to the documentation of the base helper for details.
1881
+
1882
+ ##
1883
+ # :method: time_field
1884
+ #
1885
+ # :call-seq: time_field(method, options = {})
1886
+ #
1887
+ # Wraps ActionView::Helpers::FormHelper#time_field for form builders:
1888
+ #
1889
+ # <%= form_with model: @user do |f| %>
1890
+ # <%= f.time_field :born_at %>
1891
+ # <% end %>
1892
+ #
1893
+ # Please refer to the documentation of the base helper for details.
1894
+
1895
+ ##
1896
+ # :method: datetime_field
1897
+ #
1898
+ # :call-seq: datetime_field(method, options = {})
1899
+ #
1900
+ # Wraps ActionView::Helpers::FormHelper#datetime_field for form builders:
1901
+ #
1902
+ # <%= form_with model: @user do |f| %>
1903
+ # <%= f.datetime_field :graduation_day %>
1904
+ # <% end %>
1905
+ #
1906
+ # Please refer to the documentation of the base helper for details.
1907
+
1908
+ ##
1909
+ # :method: datetime_local_field
1910
+ #
1911
+ # :call-seq: datetime_local_field(method, options = {})
1912
+ #
1913
+ # Wraps ActionView::Helpers::FormHelper#datetime_local_field for form builders:
1914
+ #
1915
+ # <%= form_with model: @user do |f| %>
1916
+ # <%= f.datetime_local_field :graduation_day %>
1917
+ # <% end %>
1918
+ #
1919
+ # Please refer to the documentation of the base helper for details.
1920
+
1921
+ ##
1922
+ # :method: month_field
1923
+ #
1924
+ # :call-seq: month_field(method, options = {})
1925
+ #
1926
+ # Wraps ActionView::Helpers::FormHelper#month_field for form builders:
1927
+ #
1928
+ # <%= form_with model: @user do |f| %>
1929
+ # <%= f.month_field :birthday_month %>
1930
+ # <% end %>
1931
+ #
1932
+ # Please refer to the documentation of the base helper for details.
1933
+
1934
+ ##
1935
+ # :method: week_field
1936
+ #
1937
+ # :call-seq: week_field(method, options = {})
1938
+ #
1939
+ # Wraps ActionView::Helpers::FormHelper#week_field for form builders:
1940
+ #
1941
+ # <%= form_with model: @user do |f| %>
1942
+ # <%= f.week_field :birthday_week %>
1943
+ # <% end %>
1944
+ #
1945
+ # Please refer to the documentation of the base helper for details.
1946
+
1947
+ ##
1948
+ # :method: url_field
1949
+ #
1950
+ # :call-seq: url_field(method, options = {})
1951
+ #
1952
+ # Wraps ActionView::Helpers::FormHelper#url_field for form builders:
1953
+ #
1954
+ # <%= form_with model: @user do |f| %>
1955
+ # <%= f.url_field :homepage %>
1956
+ # <% end %>
1957
+ #
1958
+ # Please refer to the documentation of the base helper for details.
1959
+
1960
+ ##
1961
+ # :method: email_field
1962
+ #
1963
+ # :call-seq: email_field(method, options = {})
1964
+ #
1965
+ # Wraps ActionView::Helpers::FormHelper#email_field for form builders:
1966
+ #
1967
+ # <%= form_with model: @user do |f| %>
1968
+ # <%= f.email_field :address %>
1969
+ # <% end %>
1970
+ #
1971
+ # Please refer to the documentation of the base helper for details.
1972
+
1973
+ ##
1974
+ # :method: number_field
1975
+ #
1976
+ # :call-seq: number_field(method, options = {})
1977
+ #
1978
+ # Wraps ActionView::Helpers::FormHelper#number_field for form builders:
1979
+ #
1980
+ # <%= form_with model: @user do |f| %>
1981
+ # <%= f.number_field :age %>
1982
+ # <% end %>
1983
+ #
1984
+ # Please refer to the documentation of the base helper for details.
1985
+
1986
+ ##
1987
+ # :method: range_field
1988
+ #
1989
+ # :call-seq: range_field(method, options = {})
1990
+ #
1991
+ # Wraps ActionView::Helpers::FormHelper#range_field for form builders:
1992
+ #
1993
+ # <%= form_with model: @user do |f| %>
1994
+ # <%= f.range_field :age %>
1995
+ # <% end %>
1996
+ #
1997
+ # Please refer to the documentation of the base helper for details.
1998
+
1999
+ (field_helpers - [:label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field]).each do |selector|
1321
2000
  class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
1322
2001
  def #{selector}(method, options = {}) # def text_field(method, options = {})
1323
- @template.send( # @template.send(
1324
- #{selector.inspect}, # "text_field",
2002
+ @template.public_send( # @template.public_send(
2003
+ #{selector.inspect}, # :text_field,
1325
2004
  @object_name, # @object_name,
1326
2005
  method, # method,
1327
2006
  objectify_options(options)) # objectify_options(options))
@@ -1386,9 +2065,9 @@ module ActionView
1386
2065
  # _class_ of the model object, e.g. if <tt>@person.permission</tt>, is
1387
2066
  # of class +Permission+, the field will still be named <tt>permission[admin]</tt>.
1388
2067
  #
1389
- # Note: This also works for the methods in FormOptionHelper and
2068
+ # Note: This also works for the methods in FormOptionsHelper and
1390
2069
  # DateHelper that are designed to work with an object as base, like
1391
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
2070
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
1392
2071
  #
1393
2072
  # === Nested Attributes Examples
1394
2073
  #
@@ -1454,7 +2133,7 @@ module ActionView
1454
2133
  #
1455
2134
  # Now, when you use a form element with the <tt>_destroy</tt> parameter,
1456
2135
  # with a value that evaluates to +true+, you will destroy the associated
1457
- # model (eg. 1, '1', true, or 'true'):
2136
+ # model (e.g. 1, '1', true, or 'true'):
1458
2137
  #
1459
2138
  # <%= form_for @person do |person_form| %>
1460
2139
  # ...
@@ -1543,7 +2222,7 @@ module ActionView
1543
2222
  # This will allow you to specify which models to destroy in the
1544
2223
  # attributes hash by adding a form element for the <tt>_destroy</tt>
1545
2224
  # parameter with a value that evaluates to +true+
1546
- # (eg. 1, '1', true, or 'true'):
2225
+ # (e.g. 1, '1', true, or 'true'):
1547
2226
  #
1548
2227
  # <%= form_for @person do |person_form| %>
1549
2228
  # ...
@@ -1582,23 +2261,40 @@ module ActionView
1582
2261
  return fields_for_with_nested_attributes(record_name, record_object, fields_options, block)
1583
2262
  end
1584
2263
  else
1585
- record_object = record_name.is_a?(Array) ? record_name.last : record_name
2264
+ record_object = @template._object_for_form_builder(record_name)
1586
2265
  record_name = model_name_from_record_or_class(record_object).param_key
1587
2266
  end
1588
2267
 
2268
+ object_name = @object_name
1589
2269
  index = if options.has_key?(:index)
1590
2270
  options[:index]
1591
2271
  elsif defined?(@auto_index)
1592
- self.object_name = @object_name.to_s.sub(/\[\]$/,"")
2272
+ object_name = object_name.to_s.delete_suffix("[]")
1593
2273
  @auto_index
1594
2274
  end
1595
2275
 
1596
- record_name = index ? "#{object_name}[#{index}][#{record_name}]" : "#{object_name}[#{record_name}]"
2276
+ record_name = if index
2277
+ "#{object_name}[#{index}][#{record_name}]"
2278
+ elsif record_name.end_with?("[]")
2279
+ "#{object_name}[#{record_name[0..-3]}][#{record_object.id}]"
2280
+ else
2281
+ "#{object_name}[#{record_name}]"
2282
+ end
1597
2283
  fields_options[:child_index] = index
1598
2284
 
1599
2285
  @template.fields_for(record_name, record_object, fields_options, &block)
1600
2286
  end
1601
2287
 
2288
+ # See the docs for the <tt>ActionView::FormHelper.fields</tt> helper method.
2289
+ def fields(scope = nil, model: nil, **options, &block)
2290
+ options[:allow_method_names_outside_object] = true
2291
+ options[:skip_default_ids] = !FormHelper.form_with_generates_ids
2292
+
2293
+ convert_to_legacy_options(options)
2294
+
2295
+ fields_for(scope || model, model, options, &block)
2296
+ end
2297
+
1602
2298
  # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
1603
2299
  # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
1604
2300
  # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
@@ -1607,7 +2303,7 @@ module ActionView
1607
2303
  # target labels for radio_button tags (where the value is used in the ID of the input tag).
1608
2304
  #
1609
2305
  # ==== Examples
1610
- # label(:post, :title)
2306
+ # label(:title)
1611
2307
  # # => <label for="post_title">Title</label>
1612
2308
  #
1613
2309
  # You can localize your labels based on model and attribute names.
@@ -1620,7 +2316,7 @@ module ActionView
1620
2316
  #
1621
2317
  # Which then will result in
1622
2318
  #
1623
- # label(:post, :body)
2319
+ # label(:body)
1624
2320
  # # => <label for="post_body">Write your entire text here</label>
1625
2321
  #
1626
2322
  # Localization can also be based purely on the translation of the attribute-name
@@ -1631,21 +2327,40 @@ module ActionView
1631
2327
  # post:
1632
2328
  # cost: "Total cost"
1633
2329
  #
1634
- # label(:post, :cost)
2330
+ # label(:cost)
1635
2331
  # # => <label for="post_cost">Total cost</label>
1636
2332
  #
1637
- # label(:post, :title, "A short title")
2333
+ # label(:title, "A short title")
1638
2334
  # # => <label for="post_title">A short title</label>
1639
2335
  #
1640
- # label(:post, :title, "A short title", class: "title_label")
2336
+ # label(:title, "A short title", class: "title_label")
1641
2337
  # # => <label for="post_title" class="title_label">A short title</label>
1642
2338
  #
1643
- # label(:post, :privacy, "Public Post", value: "public")
2339
+ # label(:privacy, "Public Post", value: "public")
1644
2340
  # # => <label for="post_privacy_public">Public Post</label>
1645
2341
  #
1646
- # label(:post, :terms) do
1647
- # 'Accept <a href="/terms">Terms</a>.'.html_safe
2342
+ # label(:cost) do |translation|
2343
+ # content_tag(:span, translation, class: "cost_label")
2344
+ # end
2345
+ # # => <label for="post_cost"><span class="cost_label">Total cost</span></label>
2346
+ #
2347
+ # label(:cost) do |builder|
2348
+ # content_tag(:span, builder.translation, class: "cost_label")
2349
+ # end
2350
+ # # => <label for="post_cost"><span class="cost_label">Total cost</span></label>
2351
+ #
2352
+ # label(:cost) do |builder|
2353
+ # content_tag(:span, builder.translation, class: [
2354
+ # "cost_label",
2355
+ # ("error_label" if builder.object.errors.include?(:cost))
2356
+ # ])
2357
+ # end
2358
+ # # => <label for="post_cost"><span class="cost_label error_label">Total cost</span></label>
2359
+ #
2360
+ # label(:terms) do
2361
+ # raw('Accept <a href="/terms">Terms</a>.')
1648
2362
  # end
2363
+ # # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
1649
2364
  def label(method, text = nil, options = {}, &block)
1650
2365
  @template.label(@object_name, method, text, objectify_options(options), &block)
1651
2366
  end
@@ -1656,6 +2371,12 @@ module ActionView
1656
2371
  # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
1657
2372
  # while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
1658
2373
  #
2374
+ # ==== Options
2375
+ #
2376
+ # * Any standard HTML attributes for the tag can be passed in, for example +:class+.
2377
+ # * <tt>:checked</tt> - +true+ or +false+ forces the state of the checkbox to be checked or not.
2378
+ # * <tt>:include_hidden</tt> - If set to false, the auxiliary hidden field described below will not be generated.
2379
+ #
1659
2380
  # ==== Gotcha
1660
2381
  #
1661
2382
  # The HTML specification says unchecked check boxes are not successful, and
@@ -1669,7 +2390,7 @@ module ActionView
1669
2390
  # wouldn't update the flag.
1670
2391
  #
1671
2392
  # To prevent this the helper generates an auxiliary hidden field before
1672
- # the very check box. The hidden field has the same name and its
2393
+ # every check box. The hidden field has the same name and its
1673
2394
  # attributes mimic an unchecked check box.
1674
2395
  #
1675
2396
  # This way, the client either sends only the hidden field (representing
@@ -1693,17 +2414,20 @@ module ActionView
1693
2414
  # In that case it is preferable to either use +check_box_tag+ or to use
1694
2415
  # hashes instead of arrays.
1695
2416
  #
2417
+ # ==== Examples
2418
+ #
1696
2419
  # # Let's say that @post.validated? is 1:
1697
- # check_box("post", "validated")
2420
+ # check_box("validated")
1698
2421
  # # => <input name="post[validated]" type="hidden" value="0" />
1699
2422
  # # <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" />
1700
2423
  #
1701
2424
  # # Let's say that @puppy.gooddog is "no":
1702
- # check_box("puppy", "gooddog", {}, "yes", "no")
2425
+ # check_box("gooddog", {}, "yes", "no")
1703
2426
  # # => <input name="puppy[gooddog]" type="hidden" value="no" />
1704
2427
  # # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
1705
2428
  #
1706
- # check_box("eula", "accepted", { class: 'eula_check' }, "yes", "no")
2429
+ # # Let's say that @eula.accepted is "no":
2430
+ # check_box("accepted", { class: 'eula_check' }, "yes", "no")
1707
2431
  # # => <input name="eula[accepted]" type="hidden" value="no" />
1708
2432
  # # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
1709
2433
  def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
@@ -1718,13 +2442,14 @@ module ActionView
1718
2442
  # +options+ hash. You may pass HTML options there as well.
1719
2443
  #
1720
2444
  # # Let's say that @post.category returns "rails":
1721
- # radio_button("post", "category", "rails")
1722
- # radio_button("post", "category", "java")
2445
+ # radio_button("category", "rails")
2446
+ # radio_button("category", "java")
1723
2447
  # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
1724
2448
  # # <input type="radio" id="post_category_java" name="post[category]" value="java" />
1725
2449
  #
1726
- # radio_button("user", "receive_newsletter", "yes")
1727
- # radio_button("user", "receive_newsletter", "no")
2450
+ # # Let's say that @user.receive_newsletter returns "no":
2451
+ # radio_button("receive_newsletter", "yes")
2452
+ # radio_button("receive_newsletter", "no")
1728
2453
  # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
1729
2454
  # # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
1730
2455
  def radio_button(method, tag_value, options = {})
@@ -1737,14 +2462,17 @@ module ActionView
1737
2462
  # shown.
1738
2463
  #
1739
2464
  # ==== Examples
1740
- # hidden_field(:signup, :pass_confirm)
1741
- # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
2465
+ # # Let's say that @signup.pass_confirm returns true:
2466
+ # hidden_field(:pass_confirm)
2467
+ # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="true" />
1742
2468
  #
1743
- # hidden_field(:post, :tag_list)
1744
- # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
2469
+ # # Let's say that @post.tag_list returns "blog, ruby":
2470
+ # hidden_field(:tag_list)
2471
+ # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="blog, ruby" />
1745
2472
  #
1746
- # hidden_field(:user, :token)
1747
- # # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
2473
+ # # Let's say that @user.token returns "abcde":
2474
+ # hidden_field(:token)
2475
+ # # => <input type="hidden" id="user_token" name="user[token]" value="abcde" />
1748
2476
  #
1749
2477
  def hidden_field(method, options = {})
1750
2478
  @emitted_hidden_id = true if method == :id
@@ -1756,28 +2484,34 @@ module ActionView
1756
2484
  # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
1757
2485
  # shown.
1758
2486
  #
1759
- # Using this method inside a +form_for+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>.
2487
+ # Using this method inside a +form_with+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>.
1760
2488
  #
1761
2489
  # ==== Options
1762
2490
  # * Creates standard HTML attributes for the tag.
1763
2491
  # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
1764
2492
  # * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files.
2493
+ # * <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.
1765
2494
  # * <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.
1766
2495
  #
1767
2496
  # ==== Examples
1768
- # file_field(:user, :avatar)
2497
+ # # Let's say that @user has avatar:
2498
+ # file_field(:avatar)
1769
2499
  # # => <input type="file" id="user_avatar" name="user[avatar]" />
1770
2500
  #
1771
- # file_field(:post, :image, :multiple => true)
1772
- # # => <input type="file" id="post_image" name="post[image]" multiple="true" />
2501
+ # # Let's say that @post has image:
2502
+ # file_field(:image, :multiple => true)
2503
+ # # => <input type="file" id="post_image" name="post[image][]" multiple="multiple" />
1773
2504
  #
1774
- # file_field(:post, :attached, accept: 'text/html')
2505
+ # # Let's say that @post has attached:
2506
+ # file_field(:attached, accept: 'text/html')
1775
2507
  # # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
1776
2508
  #
1777
- # file_field(:post, :image, accept: 'image/png,image/gif,image/jpeg')
2509
+ # # Let's say that @post has image:
2510
+ # file_field(:image, accept: 'image/png,image/gif,image/jpeg')
1778
2511
  # # => <input type="file" id="post_image" name="post[image]" accept="image/png,image/gif,image/jpeg" />
1779
2512
  #
1780
- # file_field(:attachment, :file, class: 'file_input')
2513
+ # # Let's say that @attachment has file:
2514
+ # file_field(:file, class: 'file_input')
1781
2515
  # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
1782
2516
  def file_field(method, options = {})
1783
2517
  self.multipart = true
@@ -1791,11 +2525,11 @@ module ActionView
1791
2525
  # <%= f.submit %>
1792
2526
  # <% end %>
1793
2527
  #
1794
- # In the example above, if @post is a new record, it will use "Create Post" as
1795
- # submit button label, otherwise, it uses "Update Post".
2528
+ # In the example above, if <tt>@post</tt> is a new record, it will use "Create Post" as
2529
+ # submit button label; otherwise, it uses "Update Post".
1796
2530
  #
1797
- # Those labels can be customized using I18n, under the helpers.submit key and accept
1798
- # the %{model} as translation interpolation:
2531
+ # Those labels can be customized using I18n under the +helpers.submit+ key and using
2532
+ # <tt>%{model}</tt> for translation interpolation:
1799
2533
  #
1800
2534
  # en:
1801
2535
  # helpers:
@@ -1803,7 +2537,7 @@ module ActionView
1803
2537
  # create: "Create a %{model}"
1804
2538
  # update: "Confirm changes to %{model}"
1805
2539
  #
1806
- # It also searches for a key specific for the given object:
2540
+ # It also searches for a key specific to the given object:
1807
2541
  #
1808
2542
  # en:
1809
2543
  # helpers:
@@ -1811,7 +2545,7 @@ module ActionView
1811
2545
  # post:
1812
2546
  # create: "Add %{model}"
1813
2547
  #
1814
- def submit(value=nil, options={})
2548
+ def submit(value = nil, options = {})
1815
2549
  value, options = nil, value if value.is_a?(Hash)
1816
2550
  value ||= submit_default_value
1817
2551
  @template.submit_tag(value, options)
@@ -1824,11 +2558,11 @@ module ActionView
1824
2558
  # <%= f.button %>
1825
2559
  # <% end %>
1826
2560
  #
1827
- # In the example above, if @post is a new record, it will use "Create Post" as
1828
- # button label, otherwise, it uses "Update Post".
2561
+ # In the example above, if <tt>@post</tt> is a new record, it will use "Create Post" as
2562
+ # button label; otherwise, it uses "Update Post".
1829
2563
  #
1830
- # Those labels can be customized using I18n, under the helpers.submit key
1831
- # (the same as submit helper) and accept the %{model} as translation interpolation:
2564
+ # Those labels can be customized using I18n under the +helpers.submit+ key
2565
+ # (the same as submit helper) and using <tt>%{model}</tt> for translation interpolation:
1832
2566
  #
1833
2567
  # en:
1834
2568
  # helpers:
@@ -1836,7 +2570,7 @@ module ActionView
1836
2570
  # create: "Create a %{model}"
1837
2571
  # update: "Confirm changes to %{model}"
1838
2572
  #
1839
- # It also searches for a key specific for the given object:
2573
+ # It also searches for a key specific to the given object:
1840
2574
  #
1841
2575
  # en:
1842
2576
  # helpers:
@@ -1845,9 +2579,12 @@ module ActionView
1845
2579
  # create: "Add %{model}"
1846
2580
  #
1847
2581
  # ==== Examples
1848
- # button("Create a post")
2582
+ # button("Create post")
1849
2583
  # # => <button name='button' type='submit'>Create post</button>
1850
2584
  #
2585
+ # button(:draft, value: true)
2586
+ # # => <button id="post_draft" name="post[draft]" value="true" type="submit">Create post</button>
2587
+ #
1851
2588
  # button do
1852
2589
  # content_tag(:strong, 'Ask me!')
1853
2590
  # end
@@ -1855,19 +2592,50 @@ module ActionView
1855
2592
  # # <strong>Ask me!</strong>
1856
2593
  # # </button>
1857
2594
  #
2595
+ # button do |text|
2596
+ # content_tag(:strong, text)
2597
+ # end
2598
+ # # => <button name='button' type='submit'>
2599
+ # # <strong>Create post</strong>
2600
+ # # </button>
2601
+ #
2602
+ # button(:draft, value: true) do
2603
+ # content_tag(:strong, "Save as draft")
2604
+ # end
2605
+ # # => <button id="post_draft" name="post[draft]" value="true" type="submit">
2606
+ # # <strong>Save as draft</strong>
2607
+ # # </button>
2608
+ #
1858
2609
  def button(value = nil, options = {}, &block)
1859
- value, options = nil, value if value.is_a?(Hash)
2610
+ case value
2611
+ when Hash
2612
+ value, options = nil, value
2613
+ when Symbol
2614
+ value, options = nil, { name: field_name(value), id: field_id(value) }.merge!(options.to_h)
2615
+ end
1860
2616
  value ||= submit_default_value
1861
- @template.button_tag(value, options, &block)
2617
+
2618
+ if block_given?
2619
+ value = @template.capture { yield(value) }
2620
+ end
2621
+
2622
+ formmethod = options[:formmethod]
2623
+ if formmethod.present? && !/post|get/i.match?(formmethod) && !options.key?(:name) && !options.key?(:value)
2624
+ options.merge! formmethod: :post, name: "_method", value: formmethod
2625
+ end
2626
+
2627
+ @template.button_tag(value, options)
1862
2628
  end
1863
2629
 
1864
- def emitted_hidden_id?
2630
+ def emitted_hidden_id? # :nodoc:
1865
2631
  @emitted_hidden_id ||= nil
1866
2632
  end
1867
2633
 
1868
2634
  private
1869
2635
  def objectify_options(options)
1870
- @default_options.merge(options.merge(object: @object))
2636
+ result = @default_options.merge(options)
2637
+ result[:object] = @object
2638
+ result
1871
2639
  end
1872
2640
 
1873
2641
  def submit_default_value
@@ -1881,7 +2649,12 @@ module ActionView
1881
2649
  end
1882
2650
 
1883
2651
  defaults = []
1884
- defaults << :"helpers.submit.#{object_name}.#{key}"
2652
+ # Object is a model and it is not overwritten by as and scope option.
2653
+ if object.respond_to?(:model_name) && object_name.to_s == model.downcase
2654
+ defaults << :"helpers.submit.#{object.model_name.i18n_key}.#{key}"
2655
+ else
2656
+ defaults << :"helpers.submit.#{object_name}.#{key}"
2657
+ end
1885
2658
  defaults << :"helpers.submit.#{key}"
1886
2659
  defaults << "#{key.to_s.humanize} #{model}"
1887
2660
 
@@ -1897,17 +2670,23 @@ module ActionView
1897
2670
  association = convert_to_model(association)
1898
2671
 
1899
2672
  if association.respond_to?(:persisted?)
1900
- association = [association] if @object.send(association_name).respond_to?(:to_ary)
2673
+ association = [association] if @object.public_send(association_name).respond_to?(:to_ary)
1901
2674
  elsif !association.respond_to?(:to_ary)
1902
- association = @object.send(association_name)
2675
+ association = @object.public_send(association_name)
1903
2676
  end
1904
2677
 
1905
2678
  if association.respond_to?(:to_ary)
1906
2679
  explicit_child_index = options[:child_index]
1907
2680
  output = ActiveSupport::SafeBuffer.new
1908
2681
  association.each do |child|
1909
- options[:child_index] = nested_child_index(name) unless explicit_child_index
1910
- output << fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
2682
+ if explicit_child_index
2683
+ options[:child_index] = explicit_child_index.call if explicit_child_index.respond_to?(:call)
2684
+ else
2685
+ options[:child_index] = nested_child_index(name)
2686
+ end
2687
+ if content = fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
2688
+ output << content
2689
+ end
1911
2690
  end
1912
2691
  output
1913
2692
  elsif association
@@ -1932,12 +2711,16 @@ module ActionView
1932
2711
  @nested_child_index[name] ||= -1
1933
2712
  @nested_child_index[name] += 1
1934
2713
  end
2714
+
2715
+ def convert_to_legacy_options(options)
2716
+ if options.key?(:skip_id)
2717
+ options[:include_id] = !options.delete(:skip_id)
2718
+ end
2719
+ end
1935
2720
  end
1936
2721
  end
1937
2722
 
1938
2723
  ActiveSupport.on_load(:action_view) do
1939
- cattr_accessor(:default_form_builder, instance_writer: false, instance_reader: false) do
1940
- ::ActionView::Helpers::FormBuilder
1941
- end
2724
+ cattr_accessor :default_form_builder, instance_writer: false, instance_reader: false, default: ::ActionView::Helpers::FormBuilder
1942
2725
  end
1943
2726
  end