actionview 5.0.7.2 → 5.1.0.beta1

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 (82) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +92 -384
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/action_view.rb +5 -5
  6. data/lib/action_view/base.rb +19 -19
  7. data/lib/action_view/buffers.rb +1 -1
  8. data/lib/action_view/context.rb +1 -1
  9. data/lib/action_view/dependency_tracker.rb +4 -5
  10. data/lib/action_view/digestor.rb +6 -7
  11. data/lib/action_view/flows.rb +5 -6
  12. data/lib/action_view/gem_version.rb +3 -3
  13. data/lib/action_view/helpers.rb +1 -1
  14. data/lib/action_view/helpers/active_model_helper.rb +8 -8
  15. data/lib/action_view/helpers/asset_tag_helper.rb +62 -36
  16. data/lib/action_view/helpers/asset_url_helper.rb +111 -49
  17. data/lib/action_view/helpers/atom_feed_helper.rb +12 -13
  18. data/lib/action_view/helpers/cache_helper.rb +34 -20
  19. data/lib/action_view/helpers/capture_helper.rb +2 -2
  20. data/lib/action_view/helpers/controller_helper.rb +3 -11
  21. data/lib/action_view/helpers/csrf_helper.rb +3 -3
  22. data/lib/action_view/helpers/date_helper.rb +109 -107
  23. data/lib/action_view/helpers/debug_helper.rb +2 -3
  24. data/lib/action_view/helpers/form_helper.rb +406 -31
  25. data/lib/action_view/helpers/form_options_helper.rb +12 -12
  26. data/lib/action_view/helpers/form_tag_helper.rb +19 -18
  27. data/lib/action_view/helpers/javascript_helper.rb +6 -6
  28. data/lib/action_view/helpers/number_helper.rb +48 -46
  29. data/lib/action_view/helpers/output_safety_helper.rb +8 -8
  30. data/lib/action_view/helpers/rendering_helper.rb +2 -2
  31. data/lib/action_view/helpers/sanitize_helper.rb +6 -8
  32. data/lib/action_view/helpers/tag_helper.rb +194 -77
  33. data/lib/action_view/helpers/tags/base.rb +121 -102
  34. data/lib/action_view/helpers/tags/check_box.rb +17 -17
  35. data/lib/action_view/helpers/tags/collection_check_boxes.rb +8 -8
  36. data/lib/action_view/helpers/tags/collection_helpers.rb +60 -60
  37. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +2 -2
  38. data/lib/action_view/helpers/tags/collection_select.rb +2 -2
  39. data/lib/action_view/helpers/tags/date_select.rb +36 -36
  40. data/lib/action_view/helpers/tags/grouped_collection_select.rb +2 -2
  41. data/lib/action_view/helpers/tags/label.rb +4 -0
  42. data/lib/action_view/helpers/tags/password_field.rb +1 -1
  43. data/lib/action_view/helpers/tags/radio_button.rb +4 -4
  44. data/lib/action_view/helpers/tags/select.rb +9 -9
  45. data/lib/action_view/helpers/tags/text_area.rb +1 -1
  46. data/lib/action_view/helpers/tags/text_field.rb +5 -5
  47. data/lib/action_view/helpers/tags/translator.rb +14 -12
  48. data/lib/action_view/helpers/text_helper.rb +20 -19
  49. data/lib/action_view/helpers/translation_helper.rb +6 -6
  50. data/lib/action_view/helpers/url_helper.rb +42 -38
  51. data/lib/action_view/layouts.rb +51 -47
  52. data/lib/action_view/log_subscriber.rb +24 -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 +3 -3
  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.rb +188 -187
  59. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +7 -1
  60. data/lib/action_view/renderer/streaming_template_renderer.rb +45 -47
  61. data/lib/action_view/renderer/template_renderer.rb +64 -66
  62. data/lib/action_view/rendering.rb +4 -5
  63. data/lib/action_view/routing_url_for.rb +9 -13
  64. data/lib/action_view/tasks/cache_digests.rake +7 -7
  65. data/lib/action_view/template.rb +26 -27
  66. data/lib/action_view/template/error.rb +5 -15
  67. data/lib/action_view/template/handlers.rb +4 -4
  68. data/lib/action_view/template/handlers/builder.rb +7 -7
  69. data/lib/action_view/template/handlers/erb.rb +9 -76
  70. data/lib/action_view/template/handlers/erb/deprecated_erubis.rb +9 -0
  71. data/lib/action_view/template/handlers/erb/erubi.rb +81 -0
  72. data/lib/action_view/template/handlers/erb/erubis.rb +81 -0
  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/test_case.rb +20 -21
  78. data/lib/action_view/testing/resolvers.rb +29 -30
  79. data/lib/action_view/version.rb +1 -1
  80. data/lib/action_view/view_paths.rb +20 -8
  81. data/lib/assets/compiled/rails-ujs.js +648 -0
  82. metadata +19 -14
@@ -4,7 +4,6 @@ module ActionView
4
4
  # Provides a set of methods for making it easier to debug Rails objects.
5
5
  module Helpers
6
6
  module DebugHelper
7
-
8
7
  include TagHelper
9
8
 
10
9
  # Returns a YAML representation of +object+ wrapped with <pre> and </pre>.
@@ -25,10 +24,10 @@ module ActionView
25
24
  def debug(object)
26
25
  Marshal::dump(object)
27
26
  object = ERB::Util.html_escape(object.to_yaml)
28
- content_tag(:pre, object, :class => "debug_dump")
27
+ content_tag(:pre, object, class: "debug_dump")
29
28
  rescue # errors from Marshal or YAML
30
29
  # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
31
- content_tag(:code, object.inspect, :class => "debug_dump")
30
+ content_tag(:code, object.inspect, class: "debug_dump")
32
31
  end
33
32
  end
34
33
  end
@@ -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,273 @@ 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
+ # Creates a form tag based on mixing URLs, scopes, or models.
478
+ #
479
+ # # Using just a URL:
480
+ # <%= form_with url: posts_path do |form| %>
481
+ # <%= form.text_field :title %>
482
+ # <% end %>
483
+ # # =>
484
+ # <form action="/posts" method="post" data-remote="true">
485
+ # <input type="text" name="title">
486
+ # </form>
487
+ #
488
+ # # Adding a scope prefixes the input field names:
489
+ # <%= form_with scope: :post, url: posts_path do |form| %>
490
+ # <%= form.text_field :title %>
491
+ # <% end %>
492
+ # # =>
493
+ # <form action="/posts" method="post" data-remote="true">
494
+ # <input type="text" name="post[title]">
495
+ # </form>
496
+ #
497
+ # # Using a model infers both the URL and scope:
498
+ # <%= form_with model: Post.new do |form| %>
499
+ # <%= form.text_field :title %>
500
+ # <% end %>
501
+ # # =>
502
+ # <form action="/posts" method="post" data-remote="true">
503
+ # <input type="text" name="post[title]">
504
+ # </form>
505
+ #
506
+ # # An existing model makes an update form and fills out field values:
507
+ # <%= form_with model: Post.first do |form| %>
508
+ # <%= form.text_field :title %>
509
+ # <% end %>
510
+ # # =>
511
+ # <form action="/posts/1" method="post" data-remote="true">
512
+ # <input type="hidden" name="_method" value="patch">
513
+ # <input type="text" name="post[title]" value="<the title of the post>">
514
+ # </form>
515
+ #
516
+ # # Though the fields don't have to correspond to model attributes:
517
+ # <%= form_with model: Cat.new do |form| %>
518
+ # <%= form.text_field :cats_dont_have_gills %>
519
+ # <%= form.text_field :but_in_forms_they_can %>
520
+ # <% end %>
521
+ # # =>
522
+ # <form action="/cats" method="post" data-remote="true">
523
+ # <input type="text" name="cat[cats_dont_have_gills]">
524
+ # <input type="text" name="cat[but_in_forms_they_can]">
525
+ # </form>
526
+ #
527
+ # The parameters in the forms are accessible in controllers according to
528
+ # their name nesting. So inputs named +title+ and <tt>post[title]</tt> are
529
+ # accessible as <tt>params[:title]</tt> and <tt>params[:post][:title]</tt>
530
+ # respectively.
531
+ #
532
+ # By default +form_with+ attaches the <tt>data-remote</tt> attribute
533
+ # submitting the form via an XMLHTTPRequest in the background if an
534
+ # Unobtrusive JavaScript driver, like rails-ujs, is used. See the
535
+ # <tt>:local</tt> option for more.
536
+ #
537
+ # For ease of comparison the examples above left out the submit button,
538
+ # as well as the auto generated hidden fields that enable UTF-8 support
539
+ # and adds an authenticity token needed for cross site request forgery
540
+ # protection.
541
+ #
542
+ # ==== +form_with+ options
543
+ #
544
+ # * <tt>:url</tt> - The URL the form submits to. Akin to values passed to
545
+ # +url_for+ or +link_to+. For example, you may use a named route
546
+ # directly. When a <tt>:scope</tt> is passed without a <tt>:url</tt> the
547
+ # form just submits to the current URL.
548
+ # * <tt>:method</tt> - The method to use when submitting the form, usually
549
+ # either "get" or "post". If "patch", "put", "delete", or another verb
550
+ # is used, a hidden input named <tt>_method</tt> is added to
551
+ # simulate the verb over post.
552
+ # * <tt>:format</tt> - The format of the route the form submits to.
553
+ # Useful when submitting to another resource type, like <tt>:json</tt>.
554
+ # Skipped if a <tt>:url</tt> is passed.
555
+ # * <tt>:scope</tt> - The scope to prefix input field names with and
556
+ # thereby how the submitted parameters are grouped in controllers.
557
+ # * <tt>:model</tt> - A model object to infer the <tt>:url</tt> and
558
+ # <tt>:scope</tt> by, plus fill out input field values.
559
+ # So if a +title+ attribute is set to "Ahoy!" then a +title+ input
560
+ # field's value would be "Ahoy!".
561
+ # If the model is a new record a create form is generated, if an
562
+ # existing record, however, an update form is generated.
563
+ # Pass <tt>:scope</tt> or <tt>:url</tt> to override the defaults.
564
+ # E.g. turn <tt>params[:post]</tt> into <tt>params[:article]</tt>.
565
+ # * <tt>:authenticity_token</tt> - Authenticity token to use in the form.
566
+ # Override with a custom authenticity token or pass <tt>false</tt> to
567
+ # skip the authenticity token field altogether.
568
+ # Useful when submitting to an external resource like a payment gateway
569
+ # that might limit the valid fields.
570
+ # Remote forms may omit the embedded authenticity token by setting
571
+ # <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>.
572
+ # This is helpful when fragment-caching the form. Remote forms
573
+ # get the authenticity token from the <tt>meta</tt> tag, so embedding is
574
+ # unnecessary unless you support browsers without JavaScript.
575
+ # * <tt>:local</tt> - By default form submits are remote and unobstrusive XHRs.
576
+ # Disable remote submits with <tt>local: true</tt>.
577
+ # * <tt>:skip_enforcing_utf8</tt> - By default a hidden field named +utf8+
578
+ # is output to enforce UTF-8 submits. Set to true to skip the field.
579
+ # * <tt>:builder</tt> - Override the object used to build the form.
580
+ # * <tt>:id</tt> - Optional HTML id attribute.
581
+ # * <tt>:class</tt> - Optional HTML class attribute.
582
+ # * <tt>:data</tt> - Optional HTML data attributes.
583
+ # * <tt>:html</tt> - Other optional HTML attributes for the form tag.
584
+ #
585
+ # === Examples
586
+ #
587
+ # When not passing a block, +form_with+ just generates an opening form tag.
588
+ #
589
+ # <%= form_with(model: @post, url: super_posts_path) %>
590
+ # <%= form_with(model: @post, scope: :article) %>
591
+ # <%= form_with(model: @post, format: :json) %>
592
+ # <%= form_with(model: @post, authenticity_token: false) %> # Disables the token.
593
+ #
594
+ # For namespaced routes, like +admin_post_url+:
595
+ #
596
+ # <%= form_with(model: [ :admin, @post ]) do |form| %>
597
+ # ...
598
+ # <% end %>
599
+ #
600
+ # If your resource has associations defined, for example, you want to add comments
601
+ # to the document given that the routes are set correctly:
602
+ #
603
+ # <%= form_with(model: [ @document, Comment.new ]) do |form| %>
604
+ # ...
605
+ # <% end %>
606
+ #
607
+ # Where <tt>@document = Document.find(params[:id])</tt>.
608
+ #
609
+ # When using labels +form_with+ requires setting the id on the field being
610
+ # labelled:
611
+ #
612
+ # <%= form_with(model: @post) do |form| %>
613
+ # <%= form.label :title %>
614
+ # <%= form.text_field :title, id: :post_title %>
615
+ # <% end %>
616
+ #
617
+ # See +label+ for more on how the +for+ attribute is derived.
618
+ #
619
+ # === Mixing with other form helpers
620
+ #
621
+ # While +form_with+ uses a FormBuilder object it's possible to mix and
622
+ # match the stand-alone FormHelper methods and methods
623
+ # from FormTagHelper:
624
+ #
625
+ # <%= form_with scope: :person do |form| %>
626
+ # <%= form.text_field :first_name %>
627
+ # <%= form.text_field :last_name %>
628
+ #
629
+ # <%= text_area :person, :biography %>
630
+ # <%= check_box_tag "person[admin]", "1", @person.company.admin? %>
631
+ #
632
+ # <%= form.submit %>
633
+ # <% end %>
634
+ #
635
+ # Same goes for the methods in FormOptionHelper and DateHelper designed
636
+ # to work with an object as a base, like
637
+ # FormOptionHelper#collection_select and DateHelper#datetime_select.
638
+ #
639
+ # === Setting the method
640
+ #
641
+ # You can force the form to use the full array of HTTP verbs by setting
642
+ #
643
+ # method: (:get|:post|:patch|:put|:delete)
644
+ #
645
+ # in the options hash. If the verb is not GET or POST, which are natively
646
+ # supported by HTML forms, the form will be set to POST and a hidden input
647
+ # called _method will carry the intended verb for the server to interpret.
648
+ #
649
+ # === Setting HTML options
650
+ #
651
+ # You can set data attributes directly in a data hash, but HTML options
652
+ # besides id and class must be wrapped in an HTML key:
653
+ #
654
+ # <%= form_with(model: @post, data: { behavior: "autosave" }, html: { name: "go" }) do |form| %>
655
+ # ...
656
+ # <% end %>
657
+ #
658
+ # generates
659
+ #
660
+ # <form action="/posts/123" method="post" data-behavior="autosave" name="go">
661
+ # <input name="_method" type="hidden" value="patch" />
662
+ # ...
663
+ # </form>
664
+ #
665
+ # === Removing hidden model id's
666
+ #
667
+ # The +form_with+ method automatically includes the model id as a hidden field in the form.
668
+ # This is used to maintain the correlation between the form data and its associated model.
669
+ # Some ORM systems do not use IDs on nested models so in this case you want to be able
670
+ # to disable the hidden id.
671
+ #
672
+ # In the following example the Post model has many Comments stored within it in a NoSQL database,
673
+ # thus there is no primary key for comments.
674
+ #
675
+ # <%= form_with(model: @post) do |form| %>
676
+ # <%= form.fields(:comments, skip_id: true) do |fields| %>
677
+ # ...
678
+ # <% end %>
679
+ # <% end %>
680
+ #
681
+ # === Customized form builders
682
+ #
683
+ # You can also build forms using a customized FormBuilder class. Subclass
684
+ # FormBuilder and override or define some more helpers, then use your
685
+ # custom builder. For example, let's say you made a helper to
686
+ # automatically add labels to form inputs.
687
+ #
688
+ # <%= form_with model: @person, url: { action: "create" }, builder: LabellingFormBuilder do |form| %>
689
+ # <%= form.text_field :first_name %>
690
+ # <%= form.text_field :last_name %>
691
+ # <%= form.text_area :biography %>
692
+ # <%= form.check_box :admin %>
693
+ # <%= form.submit %>
694
+ # <% end %>
695
+ #
696
+ # In this case, if you use:
697
+ #
698
+ # <%= render form %>
699
+ #
700
+ # The rendered template is <tt>people/_labelling_form</tt> and the local
701
+ # variable referencing the form builder is called
702
+ # <tt>labelling_form</tt>.
703
+ #
704
+ # The custom FormBuilder class is automatically merged with the options
705
+ # of a nested +fields+ call, unless it's explicitly set.
706
+ #
707
+ # In many cases you will want to wrap the above in another helper, so you
708
+ # could do something like the following:
709
+ #
710
+ # def labelled_form_with(**options, &block)
711
+ # form_with(**options.merge(builder: LabellingFormBuilder), &block)
712
+ # end
713
+ def form_with(model: nil, scope: nil, url: nil, format: nil, **options)
714
+ options[:allow_method_names_outside_object] = true
715
+ options[:skip_default_ids] = true
716
+
717
+ if model
718
+ url ||= polymorphic_path(model, format: format)
719
+
720
+ model = model.last if model.is_a?(Array)
721
+ scope ||= model_name_from_record_or_class(model).param_key
722
+ end
723
+
724
+ if block_given?
725
+ builder = instantiate_builder(scope, model, options)
726
+ output = capture(builder, &Proc.new)
727
+ options[:multipart] ||= builder.multipart?
728
+
729
+ html_options = html_options_for_form_with(url, model, options)
730
+ form_tag_with_body(html_options, output)
731
+ else
732
+ html_options = html_options_for_form_with(url, model, options)
733
+ form_tag_html(html_options)
734
+ end
735
+ end
736
+
477
737
  # Creates a scope around a specific model object like form_for, but
478
738
  # doesn't create the form tags themselves. This makes fields_for suitable
479
739
  # for specifying additional model objects in the same form.
@@ -720,6 +980,74 @@ module ActionView
720
980
  capture(builder, &block)
721
981
  end
722
982
 
983
+ # Scopes input fields with either an explicit scope or model.
984
+ # Like +form_with+ does with <tt>:scope</tt> or <tt>:model</tt>,
985
+ # except it doesn't output the form tags.
986
+ #
987
+ # # Using a scope prefixes the input field names:
988
+ # <%= fields :comment do |fields| %>
989
+ # <%= fields.text_field :body %>
990
+ # <% end %>
991
+ # # => <input type="text" name="comment[body]>
992
+ #
993
+ # # Using a model infers the scope and assigns field values:
994
+ # <%= fields model: Comment.new(body: "full bodied") do |fields| %<
995
+ # <%= fields.text_field :body %>
996
+ # <% end %>
997
+ # # =>
998
+ # <input type="text" name="comment[body] value="full bodied">
999
+ #
1000
+ # # Using +fields+ with +form_with+:
1001
+ # <%= form_with model: @post do |form| %>
1002
+ # <%= form.text_field :title %>
1003
+ #
1004
+ # <%= form.fields :comment do |fields| %>
1005
+ # <%= fields.text_field :body %>
1006
+ # <% end %>
1007
+ # <% end %>
1008
+ #
1009
+ # Much like +form_with+ a FormBuilder instance associated with the scope
1010
+ # or model is yielded, so any generated field names are prefixed with
1011
+ # either the passed scope or the scope inferred from the <tt>:model</tt>.
1012
+ #
1013
+ # When using labels +fields+ requires setting the id on the field being
1014
+ # labelled:
1015
+ #
1016
+ # <%= fields :comment do |fields| %>
1017
+ # <%= fields.label :body %>
1018
+ # <%= fields.text_field :body, id: :comment_body %>
1019
+ # <% end %>
1020
+ #
1021
+ # See +label+ for more on how the +for+ attribute is derived.
1022
+ #
1023
+ # === Mixing with other form helpers
1024
+ #
1025
+ # While +form_with+ uses a FormBuilder object it's possible to mix and
1026
+ # match the stand-alone FormHelper methods and methods
1027
+ # from FormTagHelper:
1028
+ #
1029
+ # <%= fields model: @comment do |fields| %>
1030
+ # <%= fields.text_field :body %>
1031
+ #
1032
+ # <%= text_area :commenter, :biography %>
1033
+ # <%= check_box_tag "comment[all_caps]", "1", @comment.commenter.hulk_mode? %>
1034
+ # <% end %>
1035
+ #
1036
+ # Same goes for the methods in FormOptionHelper and DateHelper designed
1037
+ # to work with an object as a base, like
1038
+ # FormOptionHelper#collection_select and DateHelper#datetime_select.
1039
+ def fields(scope = nil, model: nil, **options, &block)
1040
+ options[:allow_method_names_outside_object] = true
1041
+ options[:skip_default_ids] = true
1042
+
1043
+ if model
1044
+ scope ||= model_name_from_record_or_class(model).param_key
1045
+ end
1046
+
1047
+ builder = instantiate_builder(scope, model, options)
1048
+ capture(builder, &block)
1049
+ end
1050
+
723
1051
  # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
724
1052
  # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
725
1053
  # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
@@ -965,6 +1293,7 @@ module ActionView
965
1293
  # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
966
1294
  # # <input type="radio" id="post_category_java" name="post[category]" value="java" />
967
1295
  #
1296
+ # # Let's say that @user.receive_newsletter returns "no":
968
1297
  # radio_button("user", "receive_newsletter", "yes")
969
1298
  # radio_button("user", "receive_newsletter", "no")
970
1299
  # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
@@ -1048,7 +1377,7 @@ module ActionView
1048
1377
  # Returns a text_field of type "time".
1049
1378
  #
1050
1379
  # 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
1380
+ # on the object's value. It is still possible to override that
1052
1381
  # by passing the "value" option.
1053
1382
  #
1054
1383
  # === Options
@@ -1174,6 +1503,32 @@ module ActionView
1174
1503
  end
1175
1504
 
1176
1505
  private
1506
+ def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: false,
1507
+ skip_enforcing_utf8: false, **options)
1508
+ html_options = options.slice(:id, :class, :multipart, :method, :data).merge(html)
1509
+ html_options[:method] ||= :patch if model.respond_to?(:persisted?) && model.persisted?
1510
+ html_options[:enforce_utf8] = !skip_enforcing_utf8
1511
+
1512
+ html_options[:enctype] = "multipart/form-data" if html_options.delete(:multipart)
1513
+
1514
+ # The following URL is unescaped, this is just a hash of options, and it is the
1515
+ # responsibility of the caller to escape all the values.
1516
+ html_options[:action] = url_for(url_for_options || {})
1517
+ html_options[:"accept-charset"] = "UTF-8"
1518
+ html_options[:"data-remote"] = true unless local
1519
+
1520
+ if !local && !embed_authenticity_token_in_remote_forms &&
1521
+ html_options[:authenticity_token].blank?
1522
+ # The authenticity token is taken from the meta tag in this case
1523
+ html_options[:authenticity_token] = false
1524
+ elsif html_options[:authenticity_token] == true
1525
+ # Include the default authenticity_token, which is only generated when its set to nil,
1526
+ # but we needed the true value to override the default of no authenticity_token on data-remote.
1527
+ html_options[:authenticity_token] = nil
1528
+ end
1529
+
1530
+ html_options.stringify_keys!
1531
+ end
1177
1532
 
1178
1533
  def instantiate_builder(record_name, record_object, options)
1179
1534
  case record_name
@@ -1182,7 +1537,7 @@ module ActionView
1182
1537
  object_name = record_name
1183
1538
  else
1184
1539
  object = record_name
1185
- object_name = model_name_from_record_or_class(object).param_key
1540
+ object_name = model_name_from_record_or_class(object).param_key if object
1186
1541
  end
1187
1542
 
1188
1543
  builder = options[:builder] || default_form_builder_class
@@ -1248,7 +1603,7 @@ module ActionView
1248
1603
 
1249
1604
  # The methods which wrap a form helper call.
1250
1605
  class_attribute :field_helpers
1251
- self.field_helpers = [:fields_for, :label, :text_field, :password_field,
1606
+ self.field_helpers = [:fields_for, :fields, :label, :text_field, :password_field,
1252
1607
  :hidden_field, :file_field, :text_area, :check_box,
1253
1608
  :radio_button, :color_field, :search_field,
1254
1609
  :telephone_field, :phone_field, :date_field,
@@ -1270,7 +1625,7 @@ module ActionView
1270
1625
  end
1271
1626
 
1272
1627
  def self._to_partial_path
1273
- @_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, '')
1628
+ @_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, "")
1274
1629
  end
1275
1630
 
1276
1631
  def to_partial_path
@@ -1284,19 +1639,23 @@ module ActionView
1284
1639
  def initialize(object_name, object, template, options)
1285
1640
  @nested_child_index = {}
1286
1641
  @object_name, @object, @template, @options = object_name, object, template, options
1287
- @default_options = @options ? @options.slice(:index, :namespace) : {}
1642
+ @default_options = @options ? @options.slice(:index, :namespace, :skip_default_ids, :allow_method_names_outside_object) : {}
1643
+
1644
+ convert_to_legacy_options(@options)
1645
+
1288
1646
  if @object_name.to_s.match(/\[\]$/)
1289
- if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
1647
+ if (object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}")) && object.respond_to?(:to_param)
1290
1648
  @auto_index = object.to_param
1291
1649
  else
1292
1650
  raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
1293
1651
  end
1294
1652
  end
1653
+
1295
1654
  @multipart = nil
1296
1655
  @index = options[:index] || options[:child_index]
1297
1656
  end
1298
1657
 
1299
- (field_helpers - [:label, :check_box, :radio_button, :fields_for, :hidden_field, :file_field]).each do |selector|
1658
+ (field_helpers - [:label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field]).each do |selector|
1300
1659
  class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
1301
1660
  def #{selector}(method, options = {}) # def text_field(method, options = {})
1302
1661
  @template.send( # @template.send(
@@ -1574,18 +1933,28 @@ module ActionView
1574
1933
  end
1575
1934
 
1576
1935
  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
1936
+ "#{object_name}[#{index}][#{record_name}]"
1937
+ elsif record_name.to_s.end_with?("[]")
1938
+ record_name = record_name.to_s.sub(/(.*)\[\]$/, "[\\1][#{record_object.id}]")
1939
+ "#{object_name}#{record_name}"
1940
+ else
1941
+ "#{object_name}[#{record_name}]"
1942
+ end
1584
1943
  fields_options[:child_index] = index
1585
1944
 
1586
1945
  @template.fields_for(record_name, record_object, fields_options, &block)
1587
1946
  end
1588
1947
 
1948
+ # See the docs for the <tt>ActionView::FormHelper.fields</tt> helper method.
1949
+ def fields(scope = nil, model: nil, **options, &block)
1950
+ options[:allow_method_names_outside_object] = true
1951
+ options[:skip_default_ids] = true
1952
+
1953
+ convert_to_legacy_options(options)
1954
+
1955
+ fields_for(scope || model, model, **options, &block)
1956
+ end
1957
+
1589
1958
  # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
1590
1959
  # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
1591
1960
  # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
@@ -1712,7 +2081,7 @@ module ActionView
1712
2081
  # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
1713
2082
  # # <input type="radio" id="post_category_java" name="post[category]" value="java" />
1714
2083
  #
1715
- # # Let's say that @user.category returns "no":
2084
+ # # Let's say that @user.receive_newsletter returns "no":
1716
2085
  # radio_button("receive_newsletter", "yes")
1717
2086
  # radio_button("receive_newsletter", "no")
1718
2087
  # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
@@ -1809,7 +2178,7 @@ module ActionView
1809
2178
  # post:
1810
2179
  # create: "Add %{model}"
1811
2180
  #
1812
- def submit(value=nil, options={})
2181
+ def submit(value = nil, options = {})
1813
2182
  value, options = nil, value if value.is_a?(Hash)
1814
2183
  value ||= submit_default_value
1815
2184
  @template.submit_tag(value, options)
@@ -1934,6 +2303,12 @@ module ActionView
1934
2303
  @nested_child_index[name] ||= -1
1935
2304
  @nested_child_index[name] += 1
1936
2305
  end
2306
+
2307
+ def convert_to_legacy_options(options)
2308
+ if options.key?(:skip_id)
2309
+ options[:include_id] = !options.delete(:skip_id)
2310
+ end
2311
+ end
1937
2312
  end
1938
2313
  end
1939
2314