actionview 5.0.7.2 → 5.1.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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +169 -345
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/action_view/base.rb +19 -19
  6. data/lib/action_view/buffers.rb +1 -1
  7. data/lib/action_view/context.rb +1 -1
  8. data/lib/action_view/dependency_tracker.rb +4 -5
  9. data/lib/action_view/digestor.rb +22 -13
  10. data/lib/action_view/flows.rb +5 -6
  11. data/lib/action_view/gem_version.rb +2 -2
  12. data/lib/action_view/helpers/active_model_helper.rb +8 -8
  13. data/lib/action_view/helpers/asset_tag_helper.rb +62 -36
  14. data/lib/action_view/helpers/asset_url_helper.rb +111 -49
  15. data/lib/action_view/helpers/atom_feed_helper.rb +12 -13
  16. data/lib/action_view/helpers/cache_helper.rb +32 -20
  17. data/lib/action_view/helpers/capture_helper.rb +2 -2
  18. data/lib/action_view/helpers/controller_helper.rb +2 -2
  19. data/lib/action_view/helpers/csrf_helper.rb +3 -3
  20. data/lib/action_view/helpers/date_helper.rb +119 -109
  21. data/lib/action_view/helpers/debug_helper.rb +2 -3
  22. data/lib/action_view/helpers/form_helper.rb +440 -31
  23. data/lib/action_view/helpers/form_options_helper.rb +12 -12
  24. data/lib/action_view/helpers/form_tag_helper.rb +20 -19
  25. data/lib/action_view/helpers/javascript_helper.rb +6 -6
  26. data/lib/action_view/helpers/number_helper.rb +48 -46
  27. data/lib/action_view/helpers/output_safety_helper.rb +8 -8
  28. data/lib/action_view/helpers/record_tag_helper.rb +2 -2
  29. data/lib/action_view/helpers/rendering_helper.rb +2 -3
  30. data/lib/action_view/helpers/sanitize_helper.rb +16 -12
  31. data/lib/action_view/helpers/tag_helper.rb +194 -77
  32. data/lib/action_view/helpers/tags/base.rb +121 -102
  33. data/lib/action_view/helpers/tags/check_box.rb +17 -17
  34. data/lib/action_view/helpers/tags/collection_check_boxes.rb +9 -8
  35. data/lib/action_view/helpers/tags/collection_helpers.rb +60 -60
  36. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +3 -2
  37. data/lib/action_view/helpers/tags/collection_select.rb +2 -2
  38. data/lib/action_view/helpers/tags/date_select.rb +36 -36
  39. data/lib/action_view/helpers/tags/grouped_collection_select.rb +2 -2
  40. data/lib/action_view/helpers/tags/label.rb +4 -0
  41. data/lib/action_view/helpers/tags/password_field.rb +1 -1
  42. data/lib/action_view/helpers/tags/radio_button.rb +4 -4
  43. data/lib/action_view/helpers/tags/select.rb +9 -9
  44. data/lib/action_view/helpers/tags/text_area.rb +1 -1
  45. data/lib/action_view/helpers/tags/text_field.rb +5 -5
  46. data/lib/action_view/helpers/tags/translator.rb +14 -12
  47. data/lib/action_view/helpers/text_helper.rb +20 -19
  48. data/lib/action_view/helpers/translation_helper.rb +6 -6
  49. data/lib/action_view/helpers/url_helper.rb +48 -46
  50. data/lib/action_view/helpers.rb +1 -1
  51. data/lib/action_view/layouts.rb +51 -47
  52. data/lib/action_view/log_subscriber.rb +25 -9
  53. data/lib/action_view/lookup_context.rb +19 -25
  54. data/lib/action_view/path_set.rb +19 -19
  55. data/lib/action_view/railtie.rb +13 -4
  56. data/lib/action_view/record_identifier.rb +6 -6
  57. data/lib/action_view/renderer/abstract_renderer.rb +17 -17
  58. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +7 -1
  59. data/lib/action_view/renderer/partial_renderer.rb +188 -187
  60. data/lib/action_view/renderer/renderer.rb +4 -0
  61. data/lib/action_view/renderer/streaming_template_renderer.rb +45 -47
  62. data/lib/action_view/renderer/template_renderer.rb +64 -66
  63. data/lib/action_view/rendering.rb +4 -5
  64. data/lib/action_view/routing_url_for.rb +9 -13
  65. data/lib/action_view/tasks/cache_digests.rake +7 -7
  66. data/lib/action_view/template/error.rb +5 -15
  67. data/lib/action_view/template/handlers/builder.rb +7 -7
  68. data/lib/action_view/template/handlers/erb/deprecated_erubis.rb +9 -0
  69. data/lib/action_view/template/handlers/erb/erubi.rb +81 -0
  70. data/lib/action_view/template/handlers/erb/erubis.rb +81 -0
  71. data/lib/action_view/template/handlers/erb.rb +9 -76
  72. data/lib/action_view/template/handlers.rb +4 -4
  73. data/lib/action_view/template/html.rb +2 -4
  74. data/lib/action_view/template/resolver.rb +107 -90
  75. data/lib/action_view/template/text.rb +5 -8
  76. data/lib/action_view/template/types.rb +1 -1
  77. data/lib/action_view/template.rb +26 -27
  78. data/lib/action_view/test_case.rb +20 -21
  79. data/lib/action_view/testing/resolvers.rb +29 -30
  80. data/lib/action_view/version.rb +1 -1
  81. data/lib/action_view/view_paths.rb +20 -8
  82. data/lib/action_view.rb +5 -5
  83. data/lib/assets/compiled/rails-ujs.js +683 -0
  84. metadata +18 -12
@@ -1,14 +1,14 @@
1
- require 'cgi'
2
- require 'action_view/helpers/date_helper'
3
- require 'action_view/helpers/tag_helper'
4
- require 'action_view/helpers/form_tag_helper'
5
- require 'action_view/helpers/active_model_helper'
6
- require 'action_view/model_naming'
7
- require 'action_view/record_identifier'
8
- require 'active_support/core_ext/module/attribute_accessors'
9
- require 'active_support/core_ext/hash/slice'
10
- require 'active_support/core_ext/string/output_safety'
11
- require 'active_support/core_ext/string/inflections'
1
+ require "cgi"
2
+ require "action_view/helpers/date_helper"
3
+ require "action_view/helpers/tag_helper"
4
+ require "action_view/helpers/form_tag_helper"
5
+ require "action_view/helpers/active_model_helper"
6
+ require "action_view/model_naming"
7
+ require "action_view/record_identifier"
8
+ require "active_support/core_ext/module/attribute_accessors"
9
+ require "active_support/core_ext/hash/slice"
10
+ require "active_support/core_ext/string/output_safety"
11
+ require "active_support/core_ext/string/inflections"
12
12
 
13
13
  module ActionView
14
14
  # = Action View Form Helpers
@@ -467,13 +467,305 @@ module ActionView
467
467
  )
468
468
 
469
469
  options[:url] ||= if options.key?(:format)
470
- polymorphic_path(record, format: options.delete(:format))
471
- else
472
- polymorphic_path(record, {})
473
- end
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
 
477
+ mattr_accessor(:form_with_generates_remote_forms) { true }
478
+
479
+ # Creates a form tag based on mixing URLs, scopes, or models.
480
+ #
481
+ # # Using just a URL:
482
+ # <%= form_with url: posts_path do |form| %>
483
+ # <%= form.text_field :title %>
484
+ # <% end %>
485
+ # # =>
486
+ # <form action="/posts" method="post" data-remote="true">
487
+ # <input type="text" name="title">
488
+ # </form>
489
+ #
490
+ # # Adding a scope prefixes the input field names:
491
+ # <%= form_with scope: :post, url: posts_path do |form| %>
492
+ # <%= form.text_field :title %>
493
+ # <% end %>
494
+ # # =>
495
+ # <form action="/posts" method="post" data-remote="true">
496
+ # <input type="text" name="post[title]">
497
+ # </form>
498
+ #
499
+ # # Using a model infers both the URL and scope:
500
+ # <%= form_with model: Post.new do |form| %>
501
+ # <%= form.text_field :title %>
502
+ # <% end %>
503
+ # # =>
504
+ # <form action="/posts" method="post" data-remote="true">
505
+ # <input type="text" name="post[title]">
506
+ # </form>
507
+ #
508
+ # # An existing model makes an update form and fills out field values:
509
+ # <%= form_with model: Post.first do |form| %>
510
+ # <%= form.text_field :title %>
511
+ # <% end %>
512
+ # # =>
513
+ # <form action="/posts/1" method="post" data-remote="true">
514
+ # <input type="hidden" name="_method" value="patch">
515
+ # <input type="text" name="post[title]" value="<the title of the post>">
516
+ # </form>
517
+ #
518
+ # # Though the fields don't have to correspond to model attributes:
519
+ # <%= form_with model: Cat.new do |form| %>
520
+ # <%= form.text_field :cats_dont_have_gills %>
521
+ # <%= form.text_field :but_in_forms_they_can %>
522
+ # <% end %>
523
+ # # =>
524
+ # <form action="/cats" method="post" data-remote="true">
525
+ # <input type="text" name="cat[cats_dont_have_gills]">
526
+ # <input type="text" name="cat[but_in_forms_they_can]">
527
+ # </form>
528
+ #
529
+ # The parameters in the forms are accessible in controllers according to
530
+ # their name nesting. So inputs named +title+ and <tt>post[title]</tt> are
531
+ # accessible as <tt>params[:title]</tt> and <tt>params[:post][:title]</tt>
532
+ # respectively.
533
+ #
534
+ # By default +form_with+ attaches the <tt>data-remote</tt> attribute
535
+ # submitting the form via an XMLHTTPRequest in the background if an
536
+ # Unobtrusive JavaScript driver, like rails-ujs, is used. See the
537
+ # <tt>:local</tt> option for more.
538
+ #
539
+ # For ease of comparison the examples above left out the submit button,
540
+ # as well as the auto generated hidden fields that enable UTF-8 support
541
+ # and adds an authenticity token needed for cross site request forgery
542
+ # protection.
543
+ #
544
+ # === Resource-oriented style
545
+ #
546
+ # In many of the examples just shown, the +:model+ passed to +form_with+
547
+ # is a _resource_. It corresponds to a set of RESTful routes, most likely
548
+ # defined via +resources+ in <tt>config/routes.rb</tt>.
549
+ #
550
+ # So when passing such a model record, Rails infers the URL and method.
551
+ #
552
+ # <%= form_with model: @post do |form| %>
553
+ # ...
554
+ # <% end %>
555
+ #
556
+ # is then equivalent to something like:
557
+ #
558
+ # <%= form_with scope: :post, url: post_path(@post), method: :patch do |form| %>
559
+ # ...
560
+ # <% end %>
561
+ #
562
+ # And for a new record
563
+ #
564
+ # <%= form_with model: Post.new do |form| %>
565
+ # ...
566
+ # <% end %>
567
+ #
568
+ # is equivalent to something like:
569
+ #
570
+ # <%= form_with scope: :post, url: posts_path do |form| %>
571
+ # ...
572
+ # <% end %>
573
+ #
574
+ # ==== +form_with+ options
575
+ #
576
+ # * <tt>:url</tt> - The URL the form submits to. Akin to values passed to
577
+ # +url_for+ or +link_to+. For example, you may use a named route
578
+ # directly. When a <tt>:scope</tt> is passed without a <tt>:url</tt> the
579
+ # form just submits to the current URL.
580
+ # * <tt>:method</tt> - The method to use when submitting the form, usually
581
+ # either "get" or "post". If "patch", "put", "delete", or another verb
582
+ # is used, a hidden input named <tt>_method</tt> is added to
583
+ # simulate the verb over post.
584
+ # * <tt>:format</tt> - The format of the route the form submits to.
585
+ # Useful when submitting to another resource type, like <tt>:json</tt>.
586
+ # Skipped if a <tt>:url</tt> is passed.
587
+ # * <tt>:scope</tt> - The scope to prefix input field names with and
588
+ # thereby how the submitted parameters are grouped in controllers.
589
+ # * <tt>:model</tt> - A model object to infer the <tt>:url</tt> and
590
+ # <tt>:scope</tt> by, plus fill out input field values.
591
+ # So if a +title+ attribute is set to "Ahoy!" then a +title+ input
592
+ # field's value would be "Ahoy!".
593
+ # If the model is a new record a create form is generated, if an
594
+ # existing record, however, an update form is generated.
595
+ # Pass <tt>:scope</tt> or <tt>:url</tt> to override the defaults.
596
+ # E.g. turn <tt>params[:post]</tt> into <tt>params[:article]</tt>.
597
+ # * <tt>:authenticity_token</tt> - Authenticity token to use in the form.
598
+ # Override with a custom authenticity token or pass <tt>false</tt> to
599
+ # skip the authenticity token field altogether.
600
+ # Useful when submitting to an external resource like a payment gateway
601
+ # that might limit the valid fields.
602
+ # Remote forms may omit the embedded authenticity token by setting
603
+ # <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>.
604
+ # This is helpful when fragment-caching the form. Remote forms
605
+ # get the authenticity token from the <tt>meta</tt> tag, so embedding is
606
+ # unnecessary unless you support browsers without JavaScript.
607
+ # * <tt>:local</tt> - By default form submits are remote and unobstrusive XHRs.
608
+ # Disable remote submits with <tt>local: true</tt>.
609
+ # * <tt>:skip_enforcing_utf8</tt> - By default a hidden field named +utf8+
610
+ # is output to enforce UTF-8 submits. Set to true to skip the field.
611
+ # * <tt>:builder</tt> - Override the object used to build the form.
612
+ # * <tt>:id</tt> - Optional HTML id attribute.
613
+ # * <tt>:class</tt> - Optional HTML class attribute.
614
+ # * <tt>:data</tt> - Optional HTML data attributes.
615
+ # * <tt>:html</tt> - Other optional HTML attributes for the form tag.
616
+ #
617
+ # === Examples
618
+ #
619
+ # When not passing a block, +form_with+ just generates an opening form tag.
620
+ #
621
+ # <%= form_with(model: @post, url: super_posts_path) %>
622
+ # <%= form_with(model: @post, scope: :article) %>
623
+ # <%= form_with(model: @post, format: :json) %>
624
+ # <%= form_with(model: @post, authenticity_token: false) %> # Disables the token.
625
+ #
626
+ # For namespaced routes, like +admin_post_url+:
627
+ #
628
+ # <%= form_with(model: [ :admin, @post ]) do |form| %>
629
+ # ...
630
+ # <% end %>
631
+ #
632
+ # If your resource has associations defined, for example, you want to add comments
633
+ # to the document given that the routes are set correctly:
634
+ #
635
+ # <%= form_with(model: [ @document, Comment.new ]) do |form| %>
636
+ # ...
637
+ # <% end %>
638
+ #
639
+ # Where <tt>@document = Document.find(params[:id])</tt>.
640
+ #
641
+ # When using labels +form_with+ requires setting the id on the field being
642
+ # labelled:
643
+ #
644
+ # <%= form_with(model: @post) do |form| %>
645
+ # <%= form.label :title %>
646
+ # <%= form.text_field :title, id: :post_title %>
647
+ # <% end %>
648
+ #
649
+ # See +label+ for more on how the +for+ attribute is derived.
650
+ #
651
+ # === Mixing with other form helpers
652
+ #
653
+ # While +form_with+ uses a FormBuilder object it's possible to mix and
654
+ # match the stand-alone FormHelper methods and methods
655
+ # from FormTagHelper:
656
+ #
657
+ # <%= form_with scope: :person do |form| %>
658
+ # <%= form.text_field :first_name %>
659
+ # <%= form.text_field :last_name %>
660
+ #
661
+ # <%= text_area :person, :biography %>
662
+ # <%= check_box_tag "person[admin]", "1", @person.company.admin? %>
663
+ #
664
+ # <%= form.submit %>
665
+ # <% end %>
666
+ #
667
+ # Same goes for the methods in FormOptionHelper and DateHelper designed
668
+ # to work with an object as a base, like
669
+ # FormOptionHelper#collection_select and DateHelper#datetime_select.
670
+ #
671
+ # === Setting the method
672
+ #
673
+ # You can force the form to use the full array of HTTP verbs by setting
674
+ #
675
+ # method: (:get|:post|:patch|:put|:delete)
676
+ #
677
+ # in the options hash. If the verb is not GET or POST, which are natively
678
+ # supported by HTML forms, the form will be set to POST and a hidden input
679
+ # called _method will carry the intended verb for the server to interpret.
680
+ #
681
+ # === Setting HTML options
682
+ #
683
+ # You can set data attributes directly in a data hash, but HTML options
684
+ # besides id and class must be wrapped in an HTML key:
685
+ #
686
+ # <%= form_with(model: @post, data: { behavior: "autosave" }, html: { name: "go" }) do |form| %>
687
+ # ...
688
+ # <% end %>
689
+ #
690
+ # generates
691
+ #
692
+ # <form action="/posts/123" method="post" data-behavior="autosave" name="go">
693
+ # <input name="_method" type="hidden" value="patch" />
694
+ # ...
695
+ # </form>
696
+ #
697
+ # === Removing hidden model id's
698
+ #
699
+ # The +form_with+ method automatically includes the model id as a hidden field in the form.
700
+ # This is used to maintain the correlation between the form data and its associated model.
701
+ # Some ORM systems do not use IDs on nested models so in this case you want to be able
702
+ # to disable the hidden id.
703
+ #
704
+ # In the following example the Post model has many Comments stored within it in a NoSQL database,
705
+ # thus there is no primary key for comments.
706
+ #
707
+ # <%= form_with(model: @post) do |form| %>
708
+ # <%= form.fields(:comments, skip_id: true) do |fields| %>
709
+ # ...
710
+ # <% end %>
711
+ # <% end %>
712
+ #
713
+ # === Customized form builders
714
+ #
715
+ # You can also build forms using a customized FormBuilder class. Subclass
716
+ # FormBuilder and override or define some more helpers, then use your
717
+ # custom builder. For example, let's say you made a helper to
718
+ # automatically add labels to form inputs.
719
+ #
720
+ # <%= form_with model: @person, url: { action: "create" }, builder: LabellingFormBuilder do |form| %>
721
+ # <%= form.text_field :first_name %>
722
+ # <%= form.text_field :last_name %>
723
+ # <%= form.text_area :biography %>
724
+ # <%= form.check_box :admin %>
725
+ # <%= form.submit %>
726
+ # <% end %>
727
+ #
728
+ # In this case, if you use:
729
+ #
730
+ # <%= render form %>
731
+ #
732
+ # The rendered template is <tt>people/_labelling_form</tt> and the local
733
+ # variable referencing the form builder is called
734
+ # <tt>labelling_form</tt>.
735
+ #
736
+ # The custom FormBuilder class is automatically merged with the options
737
+ # of a nested +fields+ call, unless it's explicitly set.
738
+ #
739
+ # In many cases you will want to wrap the above in another helper, so you
740
+ # could do something like the following:
741
+ #
742
+ # def labelled_form_with(**options, &block)
743
+ # form_with(**options.merge(builder: LabellingFormBuilder), &block)
744
+ # end
745
+ def form_with(model: nil, scope: nil, url: nil, format: nil, **options)
746
+ options[:allow_method_names_outside_object] = true
747
+ options[:skip_default_ids] = true
748
+
749
+ if model
750
+ url ||= polymorphic_path(model, format: format)
751
+
752
+ model = model.last if model.is_a?(Array)
753
+ scope ||= model_name_from_record_or_class(model).param_key
754
+ end
755
+
756
+ if block_given?
757
+ builder = instantiate_builder(scope, model, options)
758
+ output = capture(builder, &Proc.new)
759
+ options[:multipart] ||= builder.multipart?
760
+
761
+ html_options = html_options_for_form_with(url, model, options)
762
+ form_tag_with_body(html_options, output)
763
+ else
764
+ html_options = html_options_for_form_with(url, model, options)
765
+ form_tag_html(html_options)
766
+ end
767
+ end
768
+
477
769
  # Creates a scope around a specific model object like form_for, but
478
770
  # doesn't create the form tags themselves. This makes fields_for suitable
479
771
  # for specifying additional model objects in the same form.
@@ -720,6 +1012,74 @@ module ActionView
720
1012
  capture(builder, &block)
721
1013
  end
722
1014
 
1015
+ # Scopes input fields with either an explicit scope or model.
1016
+ # Like +form_with+ does with <tt>:scope</tt> or <tt>:model</tt>,
1017
+ # except it doesn't output the form tags.
1018
+ #
1019
+ # # Using a scope prefixes the input field names:
1020
+ # <%= fields :comment do |fields| %>
1021
+ # <%= fields.text_field :body %>
1022
+ # <% end %>
1023
+ # # => <input type="text" name="comment[body]>
1024
+ #
1025
+ # # Using a model infers the scope and assigns field values:
1026
+ # <%= fields model: Comment.new(body: "full bodied") do |fields| %<
1027
+ # <%= fields.text_field :body %>
1028
+ # <% end %>
1029
+ # # =>
1030
+ # <input type="text" name="comment[body] value="full bodied">
1031
+ #
1032
+ # # Using +fields+ with +form_with+:
1033
+ # <%= form_with model: @post do |form| %>
1034
+ # <%= form.text_field :title %>
1035
+ #
1036
+ # <%= form.fields :comment do |fields| %>
1037
+ # <%= fields.text_field :body %>
1038
+ # <% end %>
1039
+ # <% end %>
1040
+ #
1041
+ # Much like +form_with+ a FormBuilder instance associated with the scope
1042
+ # or model is yielded, so any generated field names are prefixed with
1043
+ # either the passed scope or the scope inferred from the <tt>:model</tt>.
1044
+ #
1045
+ # When using labels +fields+ requires setting the id on the field being
1046
+ # labelled:
1047
+ #
1048
+ # <%= fields :comment do |fields| %>
1049
+ # <%= fields.label :body %>
1050
+ # <%= fields.text_field :body, id: :comment_body %>
1051
+ # <% end %>
1052
+ #
1053
+ # See +label+ for more on how the +for+ attribute is derived.
1054
+ #
1055
+ # === Mixing with other form helpers
1056
+ #
1057
+ # While +form_with+ uses a FormBuilder object it's possible to mix and
1058
+ # match the stand-alone FormHelper methods and methods
1059
+ # from FormTagHelper:
1060
+ #
1061
+ # <%= fields model: @comment do |fields| %>
1062
+ # <%= fields.text_field :body %>
1063
+ #
1064
+ # <%= text_area :commenter, :biography %>
1065
+ # <%= check_box_tag "comment[all_caps]", "1", @comment.commenter.hulk_mode? %>
1066
+ # <% end %>
1067
+ #
1068
+ # Same goes for the methods in FormOptionHelper and DateHelper designed
1069
+ # to work with an object as a base, like
1070
+ # FormOptionHelper#collection_select and DateHelper#datetime_select.
1071
+ def fields(scope = nil, model: nil, **options, &block)
1072
+ options[:allow_method_names_outside_object] = true
1073
+ options[:skip_default_ids] = true
1074
+
1075
+ if model
1076
+ scope ||= model_name_from_record_or_class(model).param_key
1077
+ end
1078
+
1079
+ builder = instantiate_builder(scope, model, options)
1080
+ capture(builder, &block)
1081
+ end
1082
+
723
1083
  # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
724
1084
  # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
725
1085
  # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
@@ -965,6 +1325,7 @@ module ActionView
965
1325
  # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
966
1326
  # # <input type="radio" id="post_category_java" name="post[category]" value="java" />
967
1327
  #
1328
+ # # Let's say that @user.receive_newsletter returns "no":
968
1329
  # radio_button("user", "receive_newsletter", "yes")
969
1330
  # radio_button("user", "receive_newsletter", "no")
970
1331
  # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
@@ -1048,7 +1409,7 @@ module ActionView
1048
1409
  # Returns a text_field of type "time".
1049
1410
  #
1050
1411
  # The default value is generated by trying to call +strftime+ with "%T.%L"
1051
- # on the objects's value. It is still possible to override that
1412
+ # on the object's value. It is still possible to override that
1052
1413
  # by passing the "value" option.
1053
1414
  #
1054
1415
  # === Options
@@ -1174,6 +1535,34 @@ module ActionView
1174
1535
  end
1175
1536
 
1176
1537
  private
1538
+ def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: !form_with_generates_remote_forms,
1539
+ skip_enforcing_utf8: false, **options)
1540
+ html_options = options.slice(:id, :class, :multipart, :method, :data).merge(html)
1541
+ html_options[:method] ||= :patch if model.respond_to?(:persisted?) && model.persisted?
1542
+ html_options[:enforce_utf8] = !skip_enforcing_utf8
1543
+
1544
+ html_options[:enctype] = "multipart/form-data" if html_options.delete(:multipart)
1545
+
1546
+ # The following URL is unescaped, this is just a hash of options, and it is the
1547
+ # responsibility of the caller to escape all the values.
1548
+ html_options[:action] = url_for(url_for_options || {})
1549
+ html_options[:"accept-charset"] = "UTF-8"
1550
+ html_options[:"data-remote"] = true unless local
1551
+
1552
+ html_options[:authenticity_token] = options.delete(:authenticity_token)
1553
+
1554
+ if !local && html_options[:authenticity_token].blank?
1555
+ html_options[:authenticity_token] = embed_authenticity_token_in_remote_forms
1556
+ end
1557
+
1558
+ if html_options[:authenticity_token] == true
1559
+ # Include the default authenticity_token, which is only generated when it's set to nil,
1560
+ # but we needed the true value to override the default of no authenticity_token on data-remote.
1561
+ html_options[:authenticity_token] = nil
1562
+ end
1563
+
1564
+ html_options.stringify_keys!
1565
+ end
1177
1566
 
1178
1567
  def instantiate_builder(record_name, record_object, options)
1179
1568
  case record_name
@@ -1182,7 +1571,7 @@ module ActionView
1182
1571
  object_name = record_name
1183
1572
  else
1184
1573
  object = record_name
1185
- object_name = model_name_from_record_or_class(object).param_key
1574
+ object_name = model_name_from_record_or_class(object).param_key if object
1186
1575
  end
1187
1576
 
1188
1577
  builder = options[:builder] || default_form_builder_class
@@ -1248,7 +1637,7 @@ module ActionView
1248
1637
 
1249
1638
  # The methods which wrap a form helper call.
1250
1639
  class_attribute :field_helpers
1251
- self.field_helpers = [:fields_for, :label, :text_field, :password_field,
1640
+ self.field_helpers = [:fields_for, :fields, :label, :text_field, :password_field,
1252
1641
  :hidden_field, :file_field, :text_area, :check_box,
1253
1642
  :radio_button, :color_field, :search_field,
1254
1643
  :telephone_field, :phone_field, :date_field,
@@ -1270,7 +1659,7 @@ module ActionView
1270
1659
  end
1271
1660
 
1272
1661
  def self._to_partial_path
1273
- @_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, '')
1662
+ @_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, "")
1274
1663
  end
1275
1664
 
1276
1665
  def to_partial_path
@@ -1284,19 +1673,23 @@ module ActionView
1284
1673
  def initialize(object_name, object, template, options)
1285
1674
  @nested_child_index = {}
1286
1675
  @object_name, @object, @template, @options = object_name, object, template, options
1287
- @default_options = @options ? @options.slice(:index, :namespace) : {}
1676
+ @default_options = @options ? @options.slice(:index, :namespace, :skip_default_ids, :allow_method_names_outside_object) : {}
1677
+
1678
+ convert_to_legacy_options(@options)
1679
+
1288
1680
  if @object_name.to_s.match(/\[\]$/)
1289
- if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
1681
+ if (object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}")) && object.respond_to?(:to_param)
1290
1682
  @auto_index = object.to_param
1291
1683
  else
1292
1684
  raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
1293
1685
  end
1294
1686
  end
1687
+
1295
1688
  @multipart = nil
1296
1689
  @index = options[:index] || options[:child_index]
1297
1690
  end
1298
1691
 
1299
- (field_helpers - [:label, :check_box, :radio_button, :fields_for, :hidden_field, :file_field]).each do |selector|
1692
+ (field_helpers - [:label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field]).each do |selector|
1300
1693
  class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
1301
1694
  def #{selector}(method, options = {}) # def text_field(method, options = {})
1302
1695
  @template.send( # @template.send(
@@ -1574,18 +1967,28 @@ module ActionView
1574
1967
  end
1575
1968
 
1576
1969
  record_name = if index
1577
- "#{object_name}[#{index}][#{record_name}]"
1578
- elsif record_name.to_s.end_with?('[]')
1579
- record_name = record_name.to_s.sub(/(.*)\[\]$/, "[\\1][#{record_object.id}]")
1580
- "#{object_name}#{record_name}"
1581
- else
1582
- "#{object_name}[#{record_name}]"
1583
- end
1970
+ "#{object_name}[#{index}][#{record_name}]"
1971
+ elsif record_name.to_s.end_with?("[]")
1972
+ record_name = record_name.to_s.sub(/(.*)\[\]$/, "[\\1][#{record_object.id}]")
1973
+ "#{object_name}#{record_name}"
1974
+ else
1975
+ "#{object_name}[#{record_name}]"
1976
+ end
1584
1977
  fields_options[:child_index] = index
1585
1978
 
1586
1979
  @template.fields_for(record_name, record_object, fields_options, &block)
1587
1980
  end
1588
1981
 
1982
+ # See the docs for the <tt>ActionView::FormHelper.fields</tt> helper method.
1983
+ def fields(scope = nil, model: nil, **options, &block)
1984
+ options[:allow_method_names_outside_object] = true
1985
+ options[:skip_default_ids] = true
1986
+
1987
+ convert_to_legacy_options(options)
1988
+
1989
+ fields_for(scope || model, model, **options, &block)
1990
+ end
1991
+
1589
1992
  # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
1590
1993
  # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
1591
1994
  # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
@@ -1712,7 +2115,7 @@ module ActionView
1712
2115
  # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
1713
2116
  # # <input type="radio" id="post_category_java" name="post[category]" value="java" />
1714
2117
  #
1715
- # # Let's say that @user.category returns "no":
2118
+ # # Let's say that @user.receive_newsletter returns "no":
1716
2119
  # radio_button("receive_newsletter", "yes")
1717
2120
  # radio_button("receive_newsletter", "no")
1718
2121
  # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
@@ -1809,7 +2212,7 @@ module ActionView
1809
2212
  # post:
1810
2213
  # create: "Add %{model}"
1811
2214
  #
1812
- def submit(value=nil, options={})
2215
+ def submit(value = nil, options = {})
1813
2216
  value, options = nil, value if value.is_a?(Hash)
1814
2217
  value ||= submit_default_value
1815
2218
  @template.submit_tag(value, options)
@@ -1934,6 +2337,12 @@ module ActionView
1934
2337
  @nested_child_index[name] ||= -1
1935
2338
  @nested_child_index[name] += 1
1936
2339
  end
2340
+
2341
+ def convert_to_legacy_options(options)
2342
+ if options.key?(:skip_id)
2343
+ options[:include_id] = !options.delete(:skip_id)
2344
+ end
2345
+ end
1937
2346
  end
1938
2347
  end
1939
2348
 
@@ -1,9 +1,9 @@
1
- require 'cgi'
2
- require 'erb'
3
- require 'action_view/helpers/form_helper'
4
- require 'active_support/core_ext/string/output_safety'
5
- require 'active_support/core_ext/array/extract_options'
6
- require 'active_support/core_ext/array/wrap'
1
+ require "cgi"
2
+ require "erb"
3
+ require "action_view/helpers/form_helper"
4
+ require "active_support/core_ext/string/output_safety"
5
+ require "active_support/core_ext/array/extract_options"
6
+ require "active_support/core_ext/array/wrap"
7
7
 
8
8
  module ActionView
9
9
  # = Action View Form Option Helpers
@@ -363,7 +363,7 @@ module ActionView
363
363
  html_attributes[:disabled] ||= disabled && option_value_selected?(value, disabled)
364
364
  html_attributes[:value] = value
365
365
 
366
- content_tag_string(:option, text, html_attributes)
366
+ tag_builder.content_tag_string(:option, text, html_attributes)
367
367
  end.join("\n").html_safe
368
368
  end
369
369
 
@@ -578,7 +578,7 @@ module ActionView
578
578
  end
579
579
 
580
580
  zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected)
581
- zone_options.safe_concat content_tag("option".freeze, '-------------', value: '', disabled: true)
581
+ zone_options.safe_concat content_tag("option".freeze, "-------------", value: "", disabled: true)
582
582
  zone_options.safe_concat "\n"
583
583
 
584
584
  zones = zones - priority_zones
@@ -651,12 +651,12 @@ module ActionView
651
651
  # The HTML specification says when nothing is select on a collection of radio buttons
652
652
  # web browsers do not send any value to server.
653
653
  # Unfortunately this introduces a gotcha:
654
- # if a +User+ model has a +category_id+ field, and in the form none category is selected no +category_id+ parameter is sent. So,
655
- # any strong parameters idiom like
654
+ # if a +User+ model has a +category_id+ field and in the form no category is selected, no +category_id+ parameter is sent. So,
655
+ # any strong parameters idiom like:
656
656
  #
657
657
  # params.require(:user).permit(...)
658
658
  #
659
- # will raise an error since no +{user: ...}+ will be present.
659
+ # will raise an error since no <tt>{user: ...}</tt> will be present.
660
660
  #
661
661
  # To prevent this the helper generates an auxiliary hidden field before
662
662
  # every collection of radio buttons. The hidden field has the same name as collection radio button and blank value.
@@ -800,7 +800,7 @@ module ActionView
800
800
  end
801
801
 
802
802
  def prompt_text(prompt)
803
- prompt.kind_of?(String) ? prompt : I18n.translate('helpers.select.prompt', default: 'Please select')
803
+ prompt.kind_of?(String) ? prompt : I18n.translate("helpers.select.prompt", default: "Please select")
804
804
  end
805
805
  end
806
806