actionview 6.1.7.9 → 7.0.8.7

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +299 -247
  3. data/MIT-LICENSE +1 -0
  4. data/README.rdoc +2 -2
  5. data/lib/action_view/base.rb +4 -7
  6. data/lib/action_view/buffers.rb +2 -2
  7. data/lib/action_view/cache_expiry.rb +46 -32
  8. data/lib/action_view/dependency_tracker/erb_tracker.rb +154 -0
  9. data/lib/action_view/dependency_tracker/ripper_tracker.rb +59 -0
  10. data/lib/action_view/dependency_tracker.rb +6 -147
  11. data/lib/action_view/digestor.rb +7 -4
  12. data/lib/action_view/flows.rb +4 -4
  13. data/lib/action_view/gem_version.rb +5 -5
  14. data/lib/action_view/helpers/active_model_helper.rb +2 -2
  15. data/lib/action_view/helpers/asset_tag_helper.rb +95 -39
  16. data/lib/action_view/helpers/asset_url_helper.rb +16 -16
  17. data/lib/action_view/helpers/atom_feed_helper.rb +3 -4
  18. data/lib/action_view/helpers/cache_helper.rb +52 -3
  19. data/lib/action_view/helpers/capture_helper.rb +4 -4
  20. data/lib/action_view/helpers/controller_helper.rb +2 -2
  21. data/lib/action_view/helpers/csp_helper.rb +1 -1
  22. data/lib/action_view/helpers/csrf_helper.rb +2 -2
  23. data/lib/action_view/helpers/date_helper.rb +111 -43
  24. data/lib/action_view/helpers/debug_helper.rb +3 -1
  25. data/lib/action_view/helpers/form_helper.rb +211 -84
  26. data/lib/action_view/helpers/form_options_helper.rb +70 -33
  27. data/lib/action_view/helpers/form_tag_helper.rb +150 -53
  28. data/lib/action_view/helpers/javascript_helper.rb +3 -5
  29. data/lib/action_view/helpers/number_helper.rb +17 -16
  30. data/lib/action_view/helpers/output_safety_helper.rb +4 -4
  31. data/lib/action_view/helpers/rendering_helper.rb +5 -6
  32. data/lib/action_view/helpers/sanitize_helper.rb +3 -3
  33. data/lib/action_view/helpers/tag_helper.rb +37 -8
  34. data/lib/action_view/helpers/tags/base.rb +5 -25
  35. data/lib/action_view/helpers/tags/check_box.rb +1 -1
  36. data/lib/action_view/helpers/tags/collection_select.rb +1 -1
  37. data/lib/action_view/helpers/tags/file_field.rb +16 -0
  38. data/lib/action_view/helpers/tags/select.rb +1 -1
  39. data/lib/action_view/helpers/tags/time_field.rb +10 -1
  40. data/lib/action_view/helpers/tags/weekday_select.rb +28 -0
  41. data/lib/action_view/helpers/tags.rb +3 -2
  42. data/lib/action_view/helpers/text_helper.rb +25 -14
  43. data/lib/action_view/helpers/translation_helper.rb +12 -43
  44. data/lib/action_view/helpers/url_helper.rb +194 -123
  45. data/lib/action_view/helpers.rb +25 -25
  46. data/lib/action_view/layouts.rb +7 -4
  47. data/lib/action_view/lookup_context.rb +33 -52
  48. data/lib/action_view/model_naming.rb +2 -2
  49. data/lib/action_view/path_set.rb +16 -22
  50. data/lib/action_view/railtie.rb +19 -7
  51. data/lib/action_view/record_identifier.rb +1 -1
  52. data/lib/action_view/render_parser.rb +188 -0
  53. data/lib/action_view/renderer/abstract_renderer.rb +2 -2
  54. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +20 -3
  55. data/lib/action_view/renderer/partial_renderer.rb +1 -35
  56. data/lib/action_view/renderer/renderer.rb +4 -4
  57. data/lib/action_view/renderer/streaming_template_renderer.rb +3 -3
  58. data/lib/action_view/renderer/template_renderer.rb +6 -2
  59. data/lib/action_view/rendering.rb +3 -3
  60. data/lib/action_view/ripper_ast_parser.rb +198 -0
  61. data/lib/action_view/routing_url_for.rb +8 -5
  62. data/lib/action_view/template/error.rb +108 -13
  63. data/lib/action_view/template/handlers/erb.rb +6 -0
  64. data/lib/action_view/template/handlers.rb +3 -3
  65. data/lib/action_view/template/html.rb +3 -3
  66. data/lib/action_view/template/inline.rb +3 -3
  67. data/lib/action_view/template/raw_file.rb +3 -3
  68. data/lib/action_view/template/resolver.rb +89 -314
  69. data/lib/action_view/template/text.rb +3 -3
  70. data/lib/action_view/template/types.rb +14 -12
  71. data/lib/action_view/template.rb +18 -2
  72. data/lib/action_view/template_details.rb +66 -0
  73. data/lib/action_view/template_path.rb +64 -0
  74. data/lib/action_view/test_case.rb +7 -3
  75. data/lib/action_view/testing/resolvers.rb +11 -12
  76. data/lib/action_view/unbound_template.rb +33 -7
  77. data/lib/action_view/version.rb +1 -1
  78. data/lib/action_view/view_paths.rb +4 -4
  79. data/lib/action_view.rb +2 -3
  80. metadata +20 -13
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "cgi"
4
4
  require "action_view/helpers/date_helper"
5
- require "action_view/helpers/tag_helper"
5
+ require "action_view/helpers/url_helper"
6
6
  require "action_view/helpers/form_tag_helper"
7
7
  require "action_view/helpers/active_model_helper"
8
8
  require "action_view/model_naming"
@@ -11,11 +11,10 @@ require "active_support/core_ext/module/attribute_accessors"
11
11
  require "active_support/core_ext/hash/slice"
12
12
  require "active_support/core_ext/string/output_safety"
13
13
  require "active_support/core_ext/string/inflections"
14
- require "active_support/core_ext/symbol/starts_ends_with"
15
14
 
16
15
  module ActionView
17
16
  # = Action View Form Helpers
18
- module Helpers #:nodoc:
17
+ module Helpers # :nodoc:
19
18
  # Form helpers are designed to make working with resources much easier
20
19
  # compared to using vanilla HTML.
21
20
  #
@@ -283,6 +282,12 @@ module ActionView
283
282
  # ...
284
283
  # <% end %>
285
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
+ #
286
291
  # You can also set the answer format, like this:
287
292
  #
288
293
  # <%= form_for(@post, format: :json) do |f| %>
@@ -427,50 +432,46 @@ module ActionView
427
432
  # <% end %>
428
433
  def form_for(record, options = {}, &block)
429
434
  raise ArgumentError, "Missing block" unless block_given?
430
- html_options = options[:html] ||= {}
431
435
 
432
436
  case record
433
437
  when String, Symbol
438
+ model = nil
434
439
  object_name = record
435
- object = nil
436
440
  else
437
- object = record.is_a?(Array) ? record.last : record
441
+ model = convert_to_model(record)
442
+ object = _object_for_form_builder(record)
438
443
  raise ArgumentError, "First argument in form cannot contain nil or be empty" unless object
439
444
  object_name = options[:as] || model_name_from_record_or_class(object).param_key
440
- apply_form_for_options!(record, object, options)
445
+ apply_form_for_options!(object, options)
441
446
  end
442
447
 
443
- html_options[:data] = options.delete(:data) if options.has_key?(:data)
444
- html_options[:remote] = options.delete(:remote) if options.has_key?(:remote)
445
- html_options[:method] = options.delete(:method) if options.has_key?(:method)
446
- html_options[:enforce_utf8] = options.delete(:enforce_utf8) if options.has_key?(:enforce_utf8)
447
- html_options[:authenticity_token] = options.delete(:authenticity_token)
448
+ remote = options.delete(:remote)
449
+
450
+ if remote && !embed_authenticity_token_in_remote_forms && options[:authenticity_token].blank?
451
+ options[:authenticity_token] = false
452
+ end
448
453
 
449
- builder = instantiate_builder(object_name, object, options)
450
- output = capture(builder, &block)
451
- html_options[:multipart] ||= builder.multipart?
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)
452
459
 
453
- html_options = html_options_for_form(options[:url] || {}, html_options)
454
- form_tag_with_body(html_options, output)
460
+ form_with(**options, &block)
455
461
  end
456
462
 
457
- def apply_form_for_options!(record, object, options) #:nodoc:
463
+ def apply_form_for_options!(object, options) # :nodoc:
458
464
  object = convert_to_model(object)
459
465
 
460
466
  as = options[:as]
461
467
  namespace = options[:namespace]
462
468
  action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :patch] : [:new, :post]
469
+ options[:html] ||= {}
463
470
  options[:html].reverse_merge!(
464
471
  class: as ? "#{action}_#{as}" : dom_class(object, action),
465
472
  id: (as ? [namespace, action, as] : [namespace, dom_id(object, action)]).compact.join("_").presence,
466
- method: method
467
473
  )
468
-
469
- options[:url] ||= if options.key?(:format)
470
- polymorphic_path(record, format: options.delete(:format))
471
- else
472
- polymorphic_path(record, {})
473
- end
474
+ options[:method] ||= method
474
475
  end
475
476
  private :apply_form_for_options!
476
477
 
@@ -478,6 +479,8 @@ module ActionView
478
479
 
479
480
  mattr_accessor :form_with_generates_ids, default: false
480
481
 
482
+ mattr_accessor :multiple_file_field_include_hidden, default: false
483
+
481
484
  # Creates a form tag based on mixing URLs, scopes, or models.
482
485
  #
483
486
  # # Using just a URL:
@@ -485,7 +488,16 @@ module ActionView
485
488
  # <%= form.text_field :title %>
486
489
  # <% end %>
487
490
  # # =>
488
- # <form action="/posts" method="post" data-remote="true">
491
+ # <form action="/posts" method="post">
492
+ # <input type="text" name="title">
493
+ # </form>
494
+ #
495
+ # # With an intentionally empty URL:
496
+ # <%= form_with url: false do |form| %>
497
+ # <%= form.text_field :title %>
498
+ # <% end %>
499
+ # # =>
500
+ # <form method="post" data-remote="true">
489
501
  # <input type="text" name="title">
490
502
  # </form>
491
503
  #
@@ -494,7 +506,7 @@ module ActionView
494
506
  # <%= form.text_field :title %>
495
507
  # <% end %>
496
508
  # # =>
497
- # <form action="/posts" method="post" data-remote="true">
509
+ # <form action="/posts" method="post">
498
510
  # <input type="text" name="post[title]">
499
511
  # </form>
500
512
  #
@@ -503,7 +515,7 @@ module ActionView
503
515
  # <%= form.text_field :title %>
504
516
  # <% end %>
505
517
  # # =>
506
- # <form action="/posts" method="post" data-remote="true">
518
+ # <form action="/posts" method="post">
507
519
  # <input type="text" name="post[title]">
508
520
  # </form>
509
521
  #
@@ -512,7 +524,7 @@ module ActionView
512
524
  # <%= form.text_field :title %>
513
525
  # <% end %>
514
526
  # # =>
515
- # <form action="/posts/1" method="post" data-remote="true">
527
+ # <form action="/posts/1" method="post">
516
528
  # <input type="hidden" name="_method" value="patch">
517
529
  # <input type="text" name="post[title]" value="<the title of the post>">
518
530
  # </form>
@@ -523,7 +535,7 @@ module ActionView
523
535
  # <%= form.text_field :but_in_forms_they_can %>
524
536
  # <% end %>
525
537
  # # =>
526
- # <form action="/cats" method="post" data-remote="true">
538
+ # <form action="/cats" method="post">
527
539
  # <input type="text" name="cat[cats_dont_have_gills]">
528
540
  # <input type="text" name="cat[but_in_forms_they_can]">
529
541
  # </form>
@@ -604,10 +616,16 @@ module ActionView
604
616
  # This is helpful when fragment-caching the form. Remote forms
605
617
  # get the authenticity token from the <tt>meta</tt> tag, so embedding is
606
618
  # unnecessary unless you support browsers without JavaScript.
607
- # * <tt>:local</tt> - By default form submits via typical HTTP requests.
608
- # Enable remote and unobtrusive XHRs submits with <tt>local: false</tt>.
609
- # Remote forms may be enabled by default by setting
610
- # <tt>config.action_view.form_with_generates_remote_forms = true</tt>.
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>).
611
629
  # * <tt>:skip_enforcing_utf8</tt> - If set to true, a hidden input with name
612
630
  # utf8 is not output.
613
631
  # * <tt>:builder</tt> - Override the object used to build the form.
@@ -735,13 +753,14 @@ module ActionView
735
753
  # form_with(**options.merge(builder: LabellingFormBuilder), &block)
736
754
  # end
737
755
  def form_with(model: nil, scope: nil, url: nil, format: nil, **options, &block)
738
- options[:allow_method_names_outside_object] = true
739
- options[:skip_default_ids] = !form_with_generates_ids
756
+ options = { allow_method_names_outside_object: true, skip_default_ids: !form_with_generates_ids }.merge!(options)
740
757
 
741
758
  if model
742
- url ||= polymorphic_path(model, format: format)
759
+ if url != false
760
+ url ||= polymorphic_path(model, format: format)
761
+ end
743
762
 
744
- model = model.last if model.is_a?(Array)
763
+ model = _object_for_form_builder(model)
745
764
  scope ||= model_name_from_record_or_class(model).param_key
746
765
  end
747
766
 
@@ -996,12 +1015,14 @@ module ActionView
996
1015
  # <% end %>
997
1016
  #
998
1017
  # Note that fields_for will automatically generate a hidden field
999
- # to store the ID of the record. There are circumstances where this
1000
- # hidden field is not needed and you can pass <tt>include_id: false</tt>
1001
- # to prevent fields_for from rendering it automatically.
1018
+ # to store the ID of the record if it responds to <tt>persisted?</tt>.
1019
+ # There are circumstances where this hidden field is not needed and you
1020
+ # can pass <tt>include_id: false</tt> to prevent fields_for from
1021
+ # rendering it automatically.
1002
1022
  def fields_for(record_name, record_object = nil, options = {}, &block)
1003
- builder = instantiate_builder(record_name, record_object, options)
1004
- capture(builder, &block)
1023
+ options = { model: record_object, allow_method_names_outside_object: false, skip_default_ids: false }.merge!(options)
1024
+
1025
+ fields(record_name, **options, &block)
1005
1026
  end
1006
1027
 
1007
1028
  # Scopes input fields with either an explicit scope or model.
@@ -1050,10 +1071,10 @@ module ActionView
1050
1071
  # to work with an object as a base, like
1051
1072
  # FormOptionsHelper#collection_select and DateHelper#datetime_select.
1052
1073
  def fields(scope = nil, model: nil, **options, &block)
1053
- options[:allow_method_names_outside_object] = true
1054
- options[:skip_default_ids] = !form_with_generates_ids
1074
+ options = { allow_method_names_outside_object: true, skip_default_ids: !form_with_generates_ids }.merge!(options)
1055
1075
 
1056
1076
  if model
1077
+ model = _object_for_form_builder(model)
1057
1078
  scope ||= model_name_from_record_or_class(model).param_key
1058
1079
  end
1059
1080
 
@@ -1063,7 +1084,7 @@ module ActionView
1063
1084
 
1064
1085
  # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
1065
1086
  # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
1066
- # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
1087
+ # is found in the current I18n locale (through <tt>helpers.label.<modelname>.<attribute></tt>) or you specify it explicitly.
1067
1088
  # Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
1068
1089
  # onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to
1069
1090
  # target labels for radio_button tags (where the value is used in the ID of the input tag).
@@ -1197,6 +1218,7 @@ module ActionView
1197
1218
  # * Creates standard HTML attributes for the tag.
1198
1219
  # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
1199
1220
  # * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files.
1221
+ # * <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.
1200
1222
  # * <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.
1201
1223
  #
1202
1224
  # ==== Examples
@@ -1215,6 +1237,8 @@ module ActionView
1215
1237
  # file_field(:attachment, :file, class: 'file_input')
1216
1238
  # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
1217
1239
  def file_field(object_name, method, options = {})
1240
+ options = { include_hidden: multiple_file_field_include_hidden }.merge!(options)
1241
+
1218
1242
  Tags::FileField.new(object_name, method, self, convert_direct_upload_option_to_url(options.dup)).render
1219
1243
  end
1220
1244
 
@@ -1252,6 +1276,12 @@ module ActionView
1252
1276
  # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
1253
1277
  # while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
1254
1278
  #
1279
+ # ==== Options
1280
+ #
1281
+ # * Any standard HTML attributes for the tag can be passed in, for example +:class+.
1282
+ # * <tt>:checked</tt> - +true+ or +false+ forces the state of the checkbox to be checked or not.
1283
+ # * <tt>:include_hidden</tt> - If set to false, the auxiliary hidden field described below will not be generated.
1284
+ #
1255
1285
  # ==== Gotcha
1256
1286
  #
1257
1287
  # The HTML specification says unchecked check boxes are not successful, and
@@ -1265,7 +1295,7 @@ module ActionView
1265
1295
  # wouldn't update the flag.
1266
1296
  #
1267
1297
  # To prevent this the helper generates an auxiliary hidden field before
1268
- # the very check box. The hidden field has the same name and its
1298
+ # every check box. The hidden field has the same name and its
1269
1299
  # attributes mimic an unchecked check box.
1270
1300
  #
1271
1301
  # This way, the client either sends only the hidden field (representing
@@ -1289,6 +1319,8 @@ module ActionView
1289
1319
  # In that case it is preferable to either use +check_box_tag+ or to use
1290
1320
  # hashes instead of arrays.
1291
1321
  #
1322
+ # ==== Examples
1323
+ #
1292
1324
  # # Let's say that @post.validated? is 1:
1293
1325
  # check_box("post", "validated")
1294
1326
  # # => <input name="post[validated]" type="hidden" value="0" />
@@ -1403,13 +1435,16 @@ module ActionView
1403
1435
  # Returns a text_field of type "time".
1404
1436
  #
1405
1437
  # The default value is generated by trying to call +strftime+ with "%T.%L"
1406
- # on the object's value. It is still possible to override that
1407
- # by passing the "value" option.
1438
+ # on the object's value. If you pass <tt>include_seconds: false</tt>, it will be
1439
+ # formatted by trying to call +strftime+ with "%H:%M" on the object's value.
1440
+ # It is also possible to override this by passing the "value" option.
1441
+ #
1442
+ # ==== Options
1443
+ #
1444
+ # Supports the same options as FormTagHelper#time_field_tag.
1408
1445
  #
1409
- # === Options
1410
- # * Accepts same options as time_field_tag
1446
+ # ==== Examples
1411
1447
  #
1412
- # === Example
1413
1448
  # time_field("task", "started_at")
1414
1449
  # # => <input id="task_started_at" name="task[started_at]" type="time" />
1415
1450
  #
@@ -1425,6 +1460,12 @@ module ActionView
1425
1460
  # time_field("task", "started_at", min: "01:00:00")
1426
1461
  # # => <input id="task_started_at" name="task[started_at]" type="time" min="01:00:00.000" />
1427
1462
  #
1463
+ # By default, provided times will be formatted including seconds. You can render just the hour
1464
+ # and minute by passing <tt>include_seconds: false</tt>. Some browsers will render a simpler UI
1465
+ # if you exclude seconds in the timestamp format.
1466
+ #
1467
+ # time_field("task", "started_at", value: Time.now, include_seconds: false)
1468
+ # # => <input id="task_started_at" name="task[started_at]" type="time" value="01:00" />
1428
1469
  def time_field(object_name, method, options = {})
1429
1470
  Tags::TimeField.new(object_name, method, self, options).render
1430
1471
  end
@@ -1515,7 +1556,8 @@ module ActionView
1515
1556
  # Returns an input tag of type "number".
1516
1557
  #
1517
1558
  # ==== Options
1518
- # * Accepts same options as number_field_tag
1559
+ #
1560
+ # Supports the same options as FormTagHelper#number_field_tag.
1519
1561
  def number_field(object_name, method, options = {})
1520
1562
  Tags::NumberField.new(object_name, method, self, options).render
1521
1563
  end
@@ -1523,39 +1565,30 @@ module ActionView
1523
1565
  # Returns an input tag of type "range".
1524
1566
  #
1525
1567
  # ==== Options
1526
- # * Accepts same options as range_field_tag
1568
+ #
1569
+ # Supports the same options as FormTagHelper#range_field_tag.
1527
1570
  def range_field(object_name, method, options = {})
1528
1571
  Tags::RangeField.new(object_name, method, self, options).render
1529
1572
  end
1530
1573
 
1574
+ def _object_for_form_builder(object) # :nodoc:
1575
+ object.is_a?(Array) ? object.last : object
1576
+ end
1577
+
1531
1578
  private
1532
1579
  def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: !form_with_generates_remote_forms,
1533
1580
  skip_enforcing_utf8: nil, **options)
1534
- html_options = options.slice(:id, :class, :multipart, :method, :data).merge(html)
1581
+ html_options = options.slice(:id, :class, :multipart, :method, :data, :authenticity_token).merge!(html)
1582
+ html_options[:remote] = html.delete(:remote) || !local
1535
1583
  html_options[:method] ||= :patch if model.respond_to?(:persisted?) && model.persisted?
1536
- html_options[:enforce_utf8] = !skip_enforcing_utf8 unless skip_enforcing_utf8.nil?
1537
-
1538
- html_options[:enctype] = "multipart/form-data" if html_options.delete(:multipart)
1539
-
1540
- # The following URL is unescaped, this is just a hash of options, and it is the
1541
- # responsibility of the caller to escape all the values.
1542
- html_options[:action] = url_for(url_for_options || {})
1543
- html_options[:"accept-charset"] = "UTF-8"
1544
- html_options[:"data-remote"] = true unless local
1545
-
1546
- html_options[:authenticity_token] = options.delete(:authenticity_token)
1547
-
1548
- if !local && html_options[:authenticity_token].blank?
1549
- html_options[:authenticity_token] = embed_authenticity_token_in_remote_forms
1550
- end
1551
-
1552
- if html_options[:authenticity_token] == true
1553
- # Include the default authenticity_token, which is only generated when it's set to nil,
1554
- # but we needed the true value to override the default of no authenticity_token on data-remote.
1555
- html_options[:authenticity_token] = nil
1584
+ if skip_enforcing_utf8.nil?
1585
+ if options.key?(:enforce_utf8)
1586
+ html_options[:enforce_utf8] = options[:enforce_utf8]
1587
+ end
1588
+ else
1589
+ html_options[:enforce_utf8] = !skip_enforcing_utf8
1556
1590
  end
1557
-
1558
- html_options.stringify_keys!
1591
+ html_options_for_form(url_for_options.nil? ? {} : url_for_options, html_options)
1559
1592
  end
1560
1593
 
1561
1594
  def instantiate_builder(record_name, record_object, options)
@@ -1685,6 +1718,69 @@ module ActionView
1685
1718
  @index = options[:index] || options[:child_index]
1686
1719
  end
1687
1720
 
1721
+ # Generate an HTML <tt>id</tt> attribute value.
1722
+ #
1723
+ # return the <tt><form></tt> element's <tt>id</tt> attribute.
1724
+ #
1725
+ # <%= form_for @post do |f| %>
1726
+ # <%# ... %>
1727
+ #
1728
+ # <% content_for :sticky_footer do %>
1729
+ # <%= form.button(form: f.id) %>
1730
+ # <% end %>
1731
+ # <% end %>
1732
+ #
1733
+ # In the example above, the <tt>:sticky_footer</tt> content area will
1734
+ # exist outside of the <tt><form></tt> element. By declaring the
1735
+ # <tt>form</tt> HTML attribute, we hint to the browser that the generated
1736
+ # <tt><button></tt> element should be treated as the <tt><form></tt>
1737
+ # element's submit button, regardless of where it exists in the DOM.
1738
+ def id
1739
+ options.dig(:html, :id) || options[:id]
1740
+ end
1741
+
1742
+ # Generate an HTML <tt>id</tt> attribute value for the given field
1743
+ #
1744
+ # Return the value generated by the <tt>FormBuilder</tt> for the given
1745
+ # attribute name.
1746
+ #
1747
+ # <%= form_for @post do |f| %>
1748
+ # <%= f.label :title %>
1749
+ # <%= f.text_field :title, aria: { describedby: f.field_id(:title, :error) } %>
1750
+ # <%= tag.span("is blank", id: f.field_id(:title, :error) %>
1751
+ # <% end %>
1752
+ #
1753
+ # In the example above, the <tt><input type="text"></tt> element built by
1754
+ # the call to <tt>FormBuilder#text_field</tt> declares an
1755
+ # <tt>aria-describedby</tt> attribute referencing the <tt><span></tt>
1756
+ # element, sharing a common <tt>id</tt> root (<tt>post_title</tt>, in this
1757
+ # case).
1758
+ def field_id(method, *suffixes, namespace: @options[:namespace], index: @options[:index])
1759
+ @template.field_id(@object_name, method, *suffixes, namespace: namespace, index: index)
1760
+ end
1761
+
1762
+ # Generate an HTML <tt>name</tt> attribute value for the given name and
1763
+ # field combination
1764
+ #
1765
+ # Return the value generated by the <tt>FormBuilder</tt> for the given
1766
+ # attribute name.
1767
+ #
1768
+ # <%= form_for @post do |f| %>
1769
+ # <%= f.text_field :title, name: f.field_name(:title, :subtitle) %>
1770
+ # <%# => <input type="text" name="post[title][subtitle]">
1771
+ # <% end %>
1772
+ #
1773
+ # <%= form_for @post do |f| %>
1774
+ # <%= f.field_tag :tag, name: f.field_name(:tag, multiple: true) %>
1775
+ # <%# => <input type="text" name="post[tag][]">
1776
+ # <% end %>
1777
+ #
1778
+ def field_name(method, *methods, multiple: false, index: @options[:index])
1779
+ object_name = @options.fetch(:as) { @object_name }
1780
+
1781
+ @template.field_name(object_name, method, *methods, index: index, multiple: multiple)
1782
+ end
1783
+
1688
1784
  ##
1689
1785
  # :method: text_field
1690
1786
  #
@@ -2171,7 +2267,7 @@ module ActionView
2171
2267
  return fields_for_with_nested_attributes(record_name, record_object, fields_options, block)
2172
2268
  end
2173
2269
  else
2174
- record_object = record_name.is_a?(Array) ? record_name.last : record_name
2270
+ record_object = @template._object_for_form_builder(record_name)
2175
2271
  record_name = model_name_from_record_or_class(record_object).param_key
2176
2272
  end
2177
2273
 
@@ -2195,7 +2291,7 @@ module ActionView
2195
2291
  @template.fields_for(record_name, record_object, fields_options, &block)
2196
2292
  end
2197
2293
 
2198
- # See the docs for the <tt>ActionView::FormHelper.fields</tt> helper method.
2294
+ # See the docs for the ActionView::Helpers::FormHelper#fields helper method.
2199
2295
  def fields(scope = nil, model: nil, **options, &block)
2200
2296
  options[:allow_method_names_outside_object] = true
2201
2297
  options[:skip_default_ids] = !FormHelper.form_with_generates_ids
@@ -2207,7 +2303,7 @@ module ActionView
2207
2303
 
2208
2304
  # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
2209
2305
  # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
2210
- # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
2306
+ # is found in the current I18n locale (through <tt>helpers.label.<modelname>.<attribute></tt>) or you specify it explicitly.
2211
2307
  # Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
2212
2308
  # onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to
2213
2309
  # target labels for radio_button tags (where the value is used in the ID of the input tag).
@@ -2281,6 +2377,12 @@ module ActionView
2281
2377
  # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
2282
2378
  # while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
2283
2379
  #
2380
+ # ==== Options
2381
+ #
2382
+ # * Any standard HTML attributes for the tag can be passed in, for example +:class+.
2383
+ # * <tt>:checked</tt> - +true+ or +false+ forces the state of the checkbox to be checked or not.
2384
+ # * <tt>:include_hidden</tt> - If set to false, the auxiliary hidden field described below will not be generated.
2385
+ #
2284
2386
  # ==== Gotcha
2285
2387
  #
2286
2388
  # The HTML specification says unchecked check boxes are not successful, and
@@ -2294,7 +2396,7 @@ module ActionView
2294
2396
  # wouldn't update the flag.
2295
2397
  #
2296
2398
  # To prevent this the helper generates an auxiliary hidden field before
2297
- # the very check box. The hidden field has the same name and its
2399
+ # every check box. The hidden field has the same name and its
2298
2400
  # attributes mimic an unchecked check box.
2299
2401
  #
2300
2402
  # This way, the client either sends only the hidden field (representing
@@ -2318,6 +2420,8 @@ module ActionView
2318
2420
  # In that case it is preferable to either use +check_box_tag+ or to use
2319
2421
  # hashes instead of arrays.
2320
2422
  #
2423
+ # ==== Examples
2424
+ #
2321
2425
  # # Let's say that @post.validated? is 1:
2322
2426
  # check_box("validated")
2323
2427
  # # => <input name="post[validated]" type="hidden" value="0" />
@@ -2386,12 +2490,13 @@ module ActionView
2386
2490
  # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
2387
2491
  # shown.
2388
2492
  #
2389
- # Using this method inside a +form_for+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>.
2493
+ # Using this method inside a +form_with+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>.
2390
2494
  #
2391
2495
  # ==== Options
2392
2496
  # * Creates standard HTML attributes for the tag.
2393
2497
  # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
2394
2498
  # * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files.
2499
+ # * <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.
2395
2500
  # * <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.
2396
2501
  #
2397
2502
  # ==== Examples
@@ -2483,6 +2588,9 @@ module ActionView
2483
2588
  # button("Create post")
2484
2589
  # # => <button name='button' type='submit'>Create post</button>
2485
2590
  #
2591
+ # button(:draft, value: true)
2592
+ # # => <button id="post_draft" name="post[draft]" value="true" type="submit">Create post</button>
2593
+ #
2486
2594
  # button do
2487
2595
  # content_tag(:strong, 'Ask me!')
2488
2596
  # end
@@ -2497,14 +2605,31 @@ module ActionView
2497
2605
  # # <strong>Create post</strong>
2498
2606
  # # </button>
2499
2607
  #
2608
+ # button(:draft, value: true) do
2609
+ # content_tag(:strong, "Save as draft")
2610
+ # end
2611
+ # # => <button id="post_draft" name="post[draft]" value="true" type="submit">
2612
+ # # <strong>Save as draft</strong>
2613
+ # # </button>
2614
+ #
2500
2615
  def button(value = nil, options = {}, &block)
2501
- value, options = nil, value if value.is_a?(Hash)
2616
+ case value
2617
+ when Hash
2618
+ value, options = nil, value
2619
+ when Symbol
2620
+ value, options = nil, { name: field_name(value), id: field_id(value) }.merge!(options.to_h)
2621
+ end
2502
2622
  value ||= submit_default_value
2503
2623
 
2504
2624
  if block_given?
2505
2625
  value = @template.capture { yield(value) }
2506
2626
  end
2507
2627
 
2628
+ formmethod = options[:formmethod]
2629
+ if formmethod.present? && !/post|get/i.match?(formmethod) && !options.key?(:name) && !options.key?(:value)
2630
+ options.merge! formmethod: :post, name: "_method", value: formmethod
2631
+ end
2632
+
2508
2633
  @template.button_tag(value, options)
2509
2634
  end
2510
2635
 
@@ -2565,7 +2690,9 @@ module ActionView
2565
2690
  else
2566
2691
  options[:child_index] = nested_child_index(name)
2567
2692
  end
2568
- output << fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
2693
+ if content = fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
2694
+ output << content
2695
+ end
2569
2696
  end
2570
2697
  output
2571
2698
  elsif association