actionview 6.1.4.1 → 7.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +189 -248
  3. data/MIT-LICENSE +1 -1
  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 +4 -4
  13. data/lib/action_view/helpers/active_model_helper.rb +2 -2
  14. data/lib/action_view/helpers/asset_tag_helper.rb +84 -29
  15. data/lib/action_view/helpers/asset_url_helper.rb +9 -9
  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 +2 -2
  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 +1 -1
  22. data/lib/action_view/helpers/date_helper.rb +62 -7
  23. data/lib/action_view/helpers/debug_helper.rb +3 -1
  24. data/lib/action_view/helpers/form_helper.rb +190 -75
  25. data/lib/action_view/helpers/form_options_helper.rb +68 -33
  26. data/lib/action_view/helpers/form_tag_helper.rb +126 -36
  27. data/lib/action_view/helpers/javascript_helper.rb +3 -5
  28. data/lib/action_view/helpers/number_helper.rb +3 -4
  29. data/lib/action_view/helpers/output_safety_helper.rb +2 -2
  30. data/lib/action_view/helpers/rendering_helper.rb +1 -1
  31. data/lib/action_view/helpers/sanitize_helper.rb +2 -2
  32. data/lib/action_view/helpers/tag_helper.rb +34 -6
  33. data/lib/action_view/helpers/tags/base.rb +4 -24
  34. data/lib/action_view/helpers/tags/check_box.rb +2 -2
  35. data/lib/action_view/helpers/tags/collection_select.rb +1 -1
  36. data/lib/action_view/helpers/tags/hidden_field.rb +4 -0
  37. data/lib/action_view/helpers/tags/time_field.rb +10 -1
  38. data/lib/action_view/helpers/tags/weekday_select.rb +28 -0
  39. data/lib/action_view/helpers/tags.rb +3 -2
  40. data/lib/action_view/helpers/text_helper.rb +24 -13
  41. data/lib/action_view/helpers/translation_helper.rb +10 -41
  42. data/lib/action_view/helpers/url_helper.rb +166 -91
  43. data/lib/action_view/helpers.rb +25 -25
  44. data/lib/action_view/lookup_context.rb +33 -52
  45. data/lib/action_view/model_naming.rb +2 -2
  46. data/lib/action_view/path_set.rb +16 -22
  47. data/lib/action_view/railtie.rb +19 -7
  48. data/lib/action_view/render_parser.rb +188 -0
  49. data/lib/action_view/renderer/abstract_renderer.rb +2 -2
  50. data/lib/action_view/renderer/partial_renderer.rb +0 -34
  51. data/lib/action_view/renderer/renderer.rb +4 -4
  52. data/lib/action_view/renderer/streaming_template_renderer.rb +3 -3
  53. data/lib/action_view/renderer/template_renderer.rb +6 -2
  54. data/lib/action_view/rendering.rb +2 -2
  55. data/lib/action_view/ripper_ast_parser.rb +198 -0
  56. data/lib/action_view/routing_url_for.rb +1 -1
  57. data/lib/action_view/template/error.rb +108 -13
  58. data/lib/action_view/template/handlers/erb.rb +6 -0
  59. data/lib/action_view/template/handlers.rb +3 -3
  60. data/lib/action_view/template/html.rb +3 -3
  61. data/lib/action_view/template/inline.rb +3 -3
  62. data/lib/action_view/template/raw_file.rb +3 -3
  63. data/lib/action_view/template/resolver.rb +84 -311
  64. data/lib/action_view/template/text.rb +3 -3
  65. data/lib/action_view/template/types.rb +14 -12
  66. data/lib/action_view/template.rb +18 -2
  67. data/lib/action_view/template_details.rb +66 -0
  68. data/lib/action_view/template_path.rb +64 -0
  69. data/lib/action_view/test_case.rb +6 -2
  70. data/lib/action_view/testing/resolvers.rb +11 -12
  71. data/lib/action_view/unbound_template.rb +33 -7
  72. data/lib/action_view.rb +3 -4
  73. metadata +22 -14
@@ -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 = 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
 
@@ -485,7 +485,16 @@ module ActionView
485
485
  # <%= form.text_field :title %>
486
486
  # <% end %>
487
487
  # # =>
488
- # <form action="/posts" method="post" data-remote="true">
488
+ # <form action="/posts" method="post">
489
+ # <input type="text" name="title">
490
+ # </form>
491
+ #
492
+ # # With an intentionally empty URL:
493
+ # <%= form_with url: false do |form| %>
494
+ # <%= form.text_field :title %>
495
+ # <% end %>
496
+ # # =>
497
+ # <form method="post" data-remote="true">
489
498
  # <input type="text" name="title">
490
499
  # </form>
491
500
  #
@@ -494,7 +503,7 @@ module ActionView
494
503
  # <%= form.text_field :title %>
495
504
  # <% end %>
496
505
  # # =>
497
- # <form action="/posts" method="post" data-remote="true">
506
+ # <form action="/posts" method="post">
498
507
  # <input type="text" name="post[title]">
499
508
  # </form>
500
509
  #
@@ -503,7 +512,7 @@ module ActionView
503
512
  # <%= form.text_field :title %>
504
513
  # <% end %>
505
514
  # # =>
506
- # <form action="/posts" method="post" data-remote="true">
515
+ # <form action="/posts" method="post">
507
516
  # <input type="text" name="post[title]">
508
517
  # </form>
509
518
  #
@@ -512,7 +521,7 @@ module ActionView
512
521
  # <%= form.text_field :title %>
513
522
  # <% end %>
514
523
  # # =>
515
- # <form action="/posts/1" method="post" data-remote="true">
524
+ # <form action="/posts/1" method="post">
516
525
  # <input type="hidden" name="_method" value="patch">
517
526
  # <input type="text" name="post[title]" value="<the title of the post>">
518
527
  # </form>
@@ -523,7 +532,7 @@ module ActionView
523
532
  # <%= form.text_field :but_in_forms_they_can %>
524
533
  # <% end %>
525
534
  # # =>
526
- # <form action="/cats" method="post" data-remote="true">
535
+ # <form action="/cats" method="post">
527
536
  # <input type="text" name="cat[cats_dont_have_gills]">
528
537
  # <input type="text" name="cat[but_in_forms_they_can]">
529
538
  # </form>
@@ -604,10 +613,16 @@ module ActionView
604
613
  # This is helpful when fragment-caching the form. Remote forms
605
614
  # get the authenticity token from the <tt>meta</tt> tag, so embedding is
606
615
  # 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>.
616
+ # * <tt>:local</tt> - Whether to use standard HTTP form submission.
617
+ # When set to <tt>true</tt>, the form is submitted via standard HTTP.
618
+ # When set to <tt>false</tt>, the form is submitted as a "remote form", which
619
+ # is handled by Rails UJS as an XHR. When unspecified, the behavior is derived
620
+ # from <tt>config.action_view.form_with_generates_remote_forms</tt> where the
621
+ # config's value is actually the inverse of what <tt>local</tt>'s value would be.
622
+ # As of Rails 6.1, that configuration option defaults to <tt>false</tt>
623
+ # (which has the equivalent effect of passing <tt>local: true</tt>).
624
+ # In previous versions of Rails, that configuration option defaults to
625
+ # <tt>true</tt> (the equivalent of passing <tt>local: false</tt>).
611
626
  # * <tt>:skip_enforcing_utf8</tt> - If set to true, a hidden input with name
612
627
  # utf8 is not output.
613
628
  # * <tt>:builder</tt> - Override the object used to build the form.
@@ -735,13 +750,14 @@ module ActionView
735
750
  # form_with(**options.merge(builder: LabellingFormBuilder), &block)
736
751
  # end
737
752
  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
753
+ options = { allow_method_names_outside_object: true, skip_default_ids: !form_with_generates_ids }.merge!(options)
740
754
 
741
755
  if model
742
- url ||= polymorphic_path(model, format: format)
756
+ if url != false
757
+ url ||= polymorphic_path(model, format: format)
758
+ end
743
759
 
744
- model = model.last if model.is_a?(Array)
760
+ model = _object_for_form_builder(model)
745
761
  scope ||= model_name_from_record_or_class(model).param_key
746
762
  end
747
763
 
@@ -1000,8 +1016,9 @@ module ActionView
1000
1016
  # hidden field is not needed and you can pass <tt>include_id: false</tt>
1001
1017
  # to prevent fields_for from rendering it automatically.
1002
1018
  def fields_for(record_name, record_object = nil, options = {}, &block)
1003
- builder = instantiate_builder(record_name, record_object, options)
1004
- capture(builder, &block)
1019
+ options = { model: record_object, allow_method_names_outside_object: false, skip_default_ids: false }.merge!(options)
1020
+
1021
+ fields(record_name, **options, &block)
1005
1022
  end
1006
1023
 
1007
1024
  # Scopes input fields with either an explicit scope or model.
@@ -1050,10 +1067,10 @@ module ActionView
1050
1067
  # to work with an object as a base, like
1051
1068
  # FormOptionsHelper#collection_select and DateHelper#datetime_select.
1052
1069
  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
1070
+ options = { allow_method_names_outside_object: true, skip_default_ids: !form_with_generates_ids }.merge!(options)
1055
1071
 
1056
1072
  if model
1073
+ model = _object_for_form_builder(model)
1057
1074
  scope ||= model_name_from_record_or_class(model).param_key
1058
1075
  end
1059
1076
 
@@ -1215,7 +1232,7 @@ module ActionView
1215
1232
  # file_field(:attachment, :file, class: 'file_input')
1216
1233
  # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
1217
1234
  def file_field(object_name, method, options = {})
1218
- Tags::FileField.new(object_name, method, self, convert_direct_upload_option_to_url(options.dup)).render
1235
+ Tags::FileField.new(object_name, method, self, convert_direct_upload_option_to_url(method, options.dup)).render
1219
1236
  end
1220
1237
 
1221
1238
  # Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
@@ -1252,6 +1269,12 @@ module ActionView
1252
1269
  # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
1253
1270
  # while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
1254
1271
  #
1272
+ # ==== Options
1273
+ #
1274
+ # * Any standard HTML attributes for the tag can be passed in, for example +:class+.
1275
+ # * <tt>:checked</tt> - +true+ or +false+ forces the state of the checkbox to be checked or not.
1276
+ # * <tt>:include_hidden</tt> - If set to false, the auxiliary hidden field described below will not be generated.
1277
+ #
1255
1278
  # ==== Gotcha
1256
1279
  #
1257
1280
  # The HTML specification says unchecked check boxes are not successful, and
@@ -1265,7 +1288,7 @@ module ActionView
1265
1288
  # wouldn't update the flag.
1266
1289
  #
1267
1290
  # 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
1291
+ # every check box. The hidden field has the same name and its
1269
1292
  # attributes mimic an unchecked check box.
1270
1293
  #
1271
1294
  # This way, the client either sends only the hidden field (representing
@@ -1289,6 +1312,8 @@ module ActionView
1289
1312
  # In that case it is preferable to either use +check_box_tag+ or to use
1290
1313
  # hashes instead of arrays.
1291
1314
  #
1315
+ # ==== Examples
1316
+ #
1292
1317
  # # Let's say that @post.validated? is 1:
1293
1318
  # check_box("post", "validated")
1294
1319
  # # => <input name="post[validated]" type="hidden" value="0" />
@@ -1403,8 +1428,9 @@ module ActionView
1403
1428
  # Returns a text_field of type "time".
1404
1429
  #
1405
1430
  # 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.
1431
+ # on the object's value. If you pass <tt>include_seconds: false</tt>, it will be
1432
+ # formatted by trying to call +strftime+ with "%H:%M" on the object's value.
1433
+ # It is also possible to override this by passing the "value" option.
1408
1434
  #
1409
1435
  # === Options
1410
1436
  # * Accepts same options as time_field_tag
@@ -1425,6 +1451,12 @@ module ActionView
1425
1451
  # time_field("task", "started_at", min: "01:00:00")
1426
1452
  # # => <input id="task_started_at" name="task[started_at]" type="time" min="01:00:00.000" />
1427
1453
  #
1454
+ # By default, provided times will be formatted including seconds. You can render just the hour
1455
+ # and minute by passing <tt>include_seconds: false</tt>. Some browsers will render a simpler UI
1456
+ # if you exclude seconds in the timestamp format.
1457
+ #
1458
+ # time_field("task", "started_at", value: Time.now, include_seconds: false)
1459
+ # # => <input id="task_started_at" name="task[started_at]" type="time" value="01:00" />
1428
1460
  def time_field(object_name, method, options = {})
1429
1461
  Tags::TimeField.new(object_name, method, self, options).render
1430
1462
  end
@@ -1528,34 +1560,24 @@ module ActionView
1528
1560
  Tags::RangeField.new(object_name, method, self, options).render
1529
1561
  end
1530
1562
 
1563
+ def _object_for_form_builder(object) # :nodoc:
1564
+ object.is_a?(Array) ? object.last : object
1565
+ end
1566
+
1531
1567
  private
1532
1568
  def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: !form_with_generates_remote_forms,
1533
1569
  skip_enforcing_utf8: nil, **options)
1534
- html_options = options.slice(:id, :class, :multipart, :method, :data).merge(html)
1570
+ html_options = options.slice(:id, :class, :multipart, :method, :data, :authenticity_token).merge!(html)
1571
+ html_options[:remote] = html.delete(:remote) || !local
1535
1572
  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
1573
+ if skip_enforcing_utf8.nil?
1574
+ if options.key?(:enforce_utf8)
1575
+ html_options[:enforce_utf8] = options[:enforce_utf8]
1576
+ end
1577
+ else
1578
+ html_options[:enforce_utf8] = !skip_enforcing_utf8
1556
1579
  end
1557
-
1558
- html_options.stringify_keys!
1580
+ html_options_for_form(url_for_options.nil? ? {} : url_for_options, html_options)
1559
1581
  end
1560
1582
 
1561
1583
  def instantiate_builder(record_name, record_object, options)
@@ -1685,6 +1707,69 @@ module ActionView
1685
1707
  @index = options[:index] || options[:child_index]
1686
1708
  end
1687
1709
 
1710
+ # Generate an HTML <tt>id</tt> attribute value.
1711
+ #
1712
+ # return the <tt><form></tt> element's <tt>id</tt> attribute.
1713
+ #
1714
+ # <%= form_for @post do |f| %>
1715
+ # <%# ... %>
1716
+ #
1717
+ # <% content_for :sticky_footer do %>
1718
+ # <%= form.button(form: f.id) %>
1719
+ # <% end %>
1720
+ # <% end %>
1721
+ #
1722
+ # In the example above, the <tt>:sticky_footer</tt> content area will
1723
+ # exist outside of the <tt><form></tt> element. By declaring the
1724
+ # <tt>form</tt> HTML attribute, we hint to the browser that the generated
1725
+ # <tt><button></tt> element should be treated as the <tt><form></tt>
1726
+ # element's submit button, regardless of where it exists in the DOM.
1727
+ def id
1728
+ options.dig(:html, :id)
1729
+ end
1730
+
1731
+ # Generate an HTML <tt>id</tt> attribute value for the given field
1732
+ #
1733
+ # Return the value generated by the <tt>FormBuilder</tt> for the given
1734
+ # attribute name.
1735
+ #
1736
+ # <%= form_for @post do |f| %>
1737
+ # <%= f.label :title %>
1738
+ # <%= f.text_field :title, aria: { describedby: f.field_id(:title, :error) } %>
1739
+ # <%= tag.span("is blank", id: f.field_id(:title, :error) %>
1740
+ # <% end %>
1741
+ #
1742
+ # In the example above, the <tt><input type="text"></tt> element built by
1743
+ # the call to <tt>FormBuilder#text_field</tt> declares an
1744
+ # <tt>aria-describedby</tt> attribute referencing the <tt><span></tt>
1745
+ # element, sharing a common <tt>id</tt> root (<tt>post_title</tt>, in this
1746
+ # case).
1747
+ def field_id(method, *suffixes, index: @index)
1748
+ @template.field_id(@object_name, method, *suffixes, index: index)
1749
+ end
1750
+
1751
+ # Generate an HTML <tt>name</tt> attribute value for the given name and
1752
+ # field combination
1753
+ #
1754
+ # Return the value generated by the <tt>FormBuilder</tt> for the given
1755
+ # attribute name.
1756
+ #
1757
+ # <%= form_for @post do |f| %>
1758
+ # <%= f.text_field :title, name: f.field_name(:title, :subtitle) %>
1759
+ # <%# => <input type="text" name="post[title][subtitle]">
1760
+ # <% end %>
1761
+ #
1762
+ # <%= form_for @post do |f| %>
1763
+ # <%= f.field_tag :tag, name: f.field_name(:tag, multiple: true) %>
1764
+ # <%# => <input type="text" name="post[tag][]">
1765
+ # <% end %>
1766
+ #
1767
+ def field_name(method, *methods, multiple: false, index: @index)
1768
+ object_name = @options.fetch(:as) { @object_name }
1769
+
1770
+ @template.field_name(object_name, method, *methods, index: index, multiple: multiple)
1771
+ end
1772
+
1688
1773
  ##
1689
1774
  # :method: text_field
1690
1775
  #
@@ -2171,7 +2256,7 @@ module ActionView
2171
2256
  return fields_for_with_nested_attributes(record_name, record_object, fields_options, block)
2172
2257
  end
2173
2258
  else
2174
- record_object = record_name.is_a?(Array) ? record_name.last : record_name
2259
+ record_object = @template._object_for_form_builder(record_name)
2175
2260
  record_name = model_name_from_record_or_class(record_object).param_key
2176
2261
  end
2177
2262
 
@@ -2281,6 +2366,12 @@ module ActionView
2281
2366
  # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
2282
2367
  # while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
2283
2368
  #
2369
+ # ==== Options
2370
+ #
2371
+ # * Any standard HTML attributes for the tag can be passed in, for example +:class+.
2372
+ # * <tt>:checked</tt> - +true+ or +false+ forces the state of the checkbox to be checked or not.
2373
+ # * <tt>:include_hidden</tt> - If set to false, the auxiliary hidden field described below will not be generated.
2374
+ #
2284
2375
  # ==== Gotcha
2285
2376
  #
2286
2377
  # The HTML specification says unchecked check boxes are not successful, and
@@ -2294,7 +2385,7 @@ module ActionView
2294
2385
  # wouldn't update the flag.
2295
2386
  #
2296
2387
  # 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
2388
+ # every check box. The hidden field has the same name and its
2298
2389
  # attributes mimic an unchecked check box.
2299
2390
  #
2300
2391
  # This way, the client either sends only the hidden field (representing
@@ -2318,6 +2409,8 @@ module ActionView
2318
2409
  # In that case it is preferable to either use +check_box_tag+ or to use
2319
2410
  # hashes instead of arrays.
2320
2411
  #
2412
+ # ==== Examples
2413
+ #
2321
2414
  # # Let's say that @post.validated? is 1:
2322
2415
  # check_box("validated")
2323
2416
  # # => <input name="post[validated]" type="hidden" value="0" />
@@ -2386,7 +2479,7 @@ module ActionView
2386
2479
  # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
2387
2480
  # shown.
2388
2481
  #
2389
- # Using this method inside a +form_for+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>.
2482
+ # Using this method inside a +form_with+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>.
2390
2483
  #
2391
2484
  # ==== Options
2392
2485
  # * Creates standard HTML attributes for the tag.
@@ -2483,6 +2576,9 @@ module ActionView
2483
2576
  # button("Create post")
2484
2577
  # # => <button name='button' type='submit'>Create post</button>
2485
2578
  #
2579
+ # button(:draft, value: true)
2580
+ # # => <button name="post[draft]" value="true" type="submit">Create post</button>
2581
+ #
2486
2582
  # button do
2487
2583
  # content_tag(:strong, 'Ask me!')
2488
2584
  # end
@@ -2497,14 +2593,31 @@ module ActionView
2497
2593
  # # <strong>Create post</strong>
2498
2594
  # # </button>
2499
2595
  #
2596
+ # button(:draft, value: true) do
2597
+ # content_tag(:strong, "Save as draft")
2598
+ # end
2599
+ # # => <button name="post[draft]" value="true" type="submit">
2600
+ # # <strong>Save as draft</strong>
2601
+ # # </button>
2602
+ #
2500
2603
  def button(value = nil, options = {}, &block)
2501
- value, options = nil, value if value.is_a?(Hash)
2604
+ case value
2605
+ when Hash
2606
+ value, options = nil, value
2607
+ when Symbol
2608
+ value, options[:name] = nil, field_name(value)
2609
+ end
2502
2610
  value ||= submit_default_value
2503
2611
 
2504
2612
  if block_given?
2505
2613
  value = @template.capture { yield(value) }
2506
2614
  end
2507
2615
 
2616
+ formmethod = options[:formmethod]
2617
+ if formmethod.present? && !/post|get/i.match?(formmethod) && !options.key?(:name) && !options.key?(:value)
2618
+ options.merge! formmethod: :post, name: "_method", value: formmethod
2619
+ end
2620
+
2508
2621
  @template.button_tag(value, options)
2509
2622
  end
2510
2623
 
@@ -2565,7 +2678,9 @@ module ActionView
2565
2678
  else
2566
2679
  options[:child_index] = nested_child_index(name)
2567
2680
  end
2568
- output << fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
2681
+ if content = fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
2682
+ output << content
2683
+ end
2569
2684
  end
2570
2685
  output
2571
2686
  elsif association