actionview 6.1.7.2 → 7.0.5

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