actionview 4.2.10 → 5.1.0

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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +141 -272
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -3
  5. data/lib/action_view/base.rb +33 -21
  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 +52 -20
  9. data/lib/action_view/digestor.rb +86 -83
  10. data/lib/action_view/flows.rb +9 -11
  11. data/lib/action_view/gem_version.rb +3 -3
  12. data/lib/action_view/helpers/active_model_helper.rb +8 -8
  13. data/lib/action_view/helpers/asset_tag_helper.rb +74 -38
  14. data/lib/action_view/helpers/asset_url_helper.rb +160 -59
  15. data/lib/action_view/helpers/atom_feed_helper.rb +16 -16
  16. data/lib/action_view/helpers/cache_helper.rb +90 -35
  17. data/lib/action_view/helpers/capture_helper.rb +7 -6
  18. data/lib/action_view/helpers/controller_helper.rb +3 -2
  19. data/lib/action_view/helpers/csrf_helper.rb +3 -3
  20. data/lib/action_view/helpers/date_helper.rb +156 -108
  21. data/lib/action_view/helpers/debug_helper.rb +3 -4
  22. data/lib/action_view/helpers/form_helper.rb +475 -94
  23. data/lib/action_view/helpers/form_options_helper.rb +87 -47
  24. data/lib/action_view/helpers/form_tag_helper.rb +88 -57
  25. data/lib/action_view/helpers/javascript_helper.rb +10 -10
  26. data/lib/action_view/helpers/number_helper.rb +76 -59
  27. data/lib/action_view/helpers/output_safety_helper.rb +34 -4
  28. data/lib/action_view/helpers/record_tag_helper.rb +12 -99
  29. data/lib/action_view/helpers/rendering_helper.rb +3 -3
  30. data/lib/action_view/helpers/sanitize_helper.rb +17 -14
  31. data/lib/action_view/helpers/tag_helper.rb +198 -73
  32. data/lib/action_view/helpers/tags/base.rb +132 -97
  33. data/lib/action_view/helpers/tags/check_box.rb +17 -17
  34. data/lib/action_view/helpers/tags/collection_check_boxes.rb +9 -33
  35. data/lib/action_view/helpers/tags/collection_helpers.rb +68 -36
  36. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +3 -11
  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/datetime_field.rb +1 -1
  40. data/lib/action_view/helpers/tags/grouped_collection_select.rb +2 -2
  41. data/lib/action_view/helpers/tags/label.rb +5 -1
  42. data/lib/action_view/helpers/tags/password_field.rb +1 -1
  43. data/lib/action_view/helpers/tags/placeholderable.rb +1 -1
  44. data/lib/action_view/helpers/tags/radio_button.rb +4 -4
  45. data/lib/action_view/helpers/tags/search_field.rb +12 -9
  46. data/lib/action_view/helpers/tags/select.rb +9 -9
  47. data/lib/action_view/helpers/tags/text_area.rb +1 -1
  48. data/lib/action_view/helpers/tags/text_field.rb +5 -6
  49. data/lib/action_view/helpers/tags/translator.rb +15 -13
  50. data/lib/action_view/helpers/text_helper.rb +47 -30
  51. data/lib/action_view/helpers/translation_helper.rb +60 -30
  52. data/lib/action_view/helpers/url_helper.rb +132 -104
  53. data/lib/action_view/helpers.rb +1 -1
  54. data/lib/action_view/layouts.rb +59 -54
  55. data/lib/action_view/log_subscriber.rb +56 -7
  56. data/lib/action_view/lookup_context.rb +76 -61
  57. data/lib/action_view/model_naming.rb +1 -1
  58. data/lib/action_view/path_set.rb +28 -19
  59. data/lib/action_view/railtie.rb +30 -6
  60. data/lib/action_view/record_identifier.rb +51 -25
  61. data/lib/action_view/renderer/abstract_renderer.rb +19 -15
  62. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +55 -0
  63. data/lib/action_view/renderer/partial_renderer.rb +208 -206
  64. data/lib/action_view/renderer/renderer.rb +2 -6
  65. data/lib/action_view/renderer/streaming_template_renderer.rb +46 -48
  66. data/lib/action_view/renderer/template_renderer.rb +65 -66
  67. data/lib/action_view/rendering.rb +16 -9
  68. data/lib/action_view/routing_url_for.rb +25 -17
  69. data/lib/action_view/tasks/cache_digests.rake +23 -0
  70. data/lib/action_view/template/error.rb +14 -13
  71. data/lib/action_view/template/handlers/builder.rb +7 -7
  72. data/lib/action_view/template/handlers/erb/deprecated_erubis.rb +9 -0
  73. data/lib/action_view/template/handlers/erb/erubi.rb +81 -0
  74. data/lib/action_view/template/handlers/erb/erubis.rb +81 -0
  75. data/lib/action_view/template/handlers/erb.rb +9 -76
  76. data/lib/action_view/template/handlers/html.rb +9 -0
  77. data/lib/action_view/template/handlers/raw.rb +1 -3
  78. data/lib/action_view/template/handlers.rb +8 -6
  79. data/lib/action_view/template/html.rb +2 -4
  80. data/lib/action_view/template/resolver.rb +133 -109
  81. data/lib/action_view/template/text.rb +5 -8
  82. data/lib/action_view/template/types.rb +15 -17
  83. data/lib/action_view/template.rb +51 -28
  84. data/lib/action_view/test_case.rb +32 -27
  85. data/lib/action_view/testing/resolvers.rb +29 -31
  86. data/lib/action_view/version.rb +1 -1
  87. data/lib/action_view/view_paths.rb +26 -32
  88. data/lib/action_view.rb +5 -5
  89. data/lib/assets/compiled/rails-ujs.js +685 -0
  90. metadata +23 -23
  91. data/lib/action_view/tasks/dependencies.rake +0 -23
@@ -1,13 +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 'active_support/core_ext/module/attribute_accessors'
8
- require 'active_support/core_ext/hash/slice'
9
- require 'active_support/core_ext/string/output_safety'
10
- 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"
11
12
 
12
13
  module ActionView
13
14
  # = Action View Form Helpers
@@ -66,9 +67,10 @@ module ActionView
66
67
  #
67
68
  # In particular, thanks to the conventions followed in the generated field names, the
68
69
  # controller gets a nested hash <tt>params[:person]</tt> with the person attributes
69
- # set in the form. That hash is ready to be passed to <tt>Person.create</tt>:
70
+ # set in the form. That hash is ready to be passed to <tt>Person.new</tt>:
70
71
  #
71
- # if @person = Person.create(params[:person])
72
+ # @person = Person.new(params[:person])
73
+ # if @person.save
72
74
  # # success
73
75
  # else
74
76
  # # error handling
@@ -110,6 +112,9 @@ module ActionView
110
112
  include FormTagHelper
111
113
  include UrlHelper
112
114
  include ModelNaming
115
+ include RecordIdentifier
116
+
117
+ attr_internal :default_form_builder
113
118
 
114
119
  # Creates a form that allows the user to create or update the attributes
115
120
  # of a specific model object.
@@ -138,6 +143,7 @@ module ActionView
138
143
  # will get expanded to
139
144
  #
140
145
  # <%= text_field :person, :first_name %>
146
+ #
141
147
  # which results in an HTML <tt><input></tt> tag whose +name+ attribute is
142
148
  # <tt>person[first_name]</tt>. This means that when the form is submitted,
143
149
  # the value entered by the user will be available in the controller as
@@ -461,13 +467,275 @@ module ActionView
461
467
  )
462
468
 
463
469
  options[:url] ||= if options.key?(:format)
464
- polymorphic_path(record, format: options.delete(:format))
465
- else
466
- polymorphic_path(record, {})
467
- end
470
+ polymorphic_path(record, format: options.delete(:format))
471
+ else
472
+ polymorphic_path(record, {})
473
+ end
468
474
  end
469
475
  private :apply_form_for_options!
470
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
+ # ==== +form_with+ options
545
+ #
546
+ # * <tt>:url</tt> - The URL the form submits to. Akin to values passed to
547
+ # +url_for+ or +link_to+. For example, you may use a named route
548
+ # directly. When a <tt>:scope</tt> is passed without a <tt>:url</tt> the
549
+ # form just submits to the current URL.
550
+ # * <tt>:method</tt> - The method to use when submitting the form, usually
551
+ # either "get" or "post". If "patch", "put", "delete", or another verb
552
+ # is used, a hidden input named <tt>_method</tt> is added to
553
+ # simulate the verb over post.
554
+ # * <tt>:format</tt> - The format of the route the form submits to.
555
+ # Useful when submitting to another resource type, like <tt>:json</tt>.
556
+ # Skipped if a <tt>:url</tt> is passed.
557
+ # * <tt>:scope</tt> - The scope to prefix input field names with and
558
+ # thereby how the submitted parameters are grouped in controllers.
559
+ # * <tt>:model</tt> - A model object to infer the <tt>:url</tt> and
560
+ # <tt>:scope</tt> by, plus fill out input field values.
561
+ # So if a +title+ attribute is set to "Ahoy!" then a +title+ input
562
+ # field's value would be "Ahoy!".
563
+ # If the model is a new record a create form is generated, if an
564
+ # existing record, however, an update form is generated.
565
+ # Pass <tt>:scope</tt> or <tt>:url</tt> to override the defaults.
566
+ # E.g. turn <tt>params[:post]</tt> into <tt>params[:article]</tt>.
567
+ # * <tt>:authenticity_token</tt> - Authenticity token to use in the form.
568
+ # Override with a custom authenticity token or pass <tt>false</tt> to
569
+ # skip the authenticity token field altogether.
570
+ # Useful when submitting to an external resource like a payment gateway
571
+ # that might limit the valid fields.
572
+ # Remote forms may omit the embedded authenticity token by setting
573
+ # <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>.
574
+ # This is helpful when fragment-caching the form. Remote forms
575
+ # get the authenticity token from the <tt>meta</tt> tag, so embedding is
576
+ # unnecessary unless you support browsers without JavaScript.
577
+ # * <tt>:local</tt> - By default form submits are remote and unobstrusive XHRs.
578
+ # Disable remote submits with <tt>local: true</tt>.
579
+ # * <tt>:skip_enforcing_utf8</tt> - By default a hidden field named +utf8+
580
+ # is output to enforce UTF-8 submits. Set to true to skip the field.
581
+ # * <tt>:builder</tt> - Override the object used to build the form.
582
+ # * <tt>:id</tt> - Optional HTML id attribute.
583
+ # * <tt>:class</tt> - Optional HTML class attribute.
584
+ # * <tt>:data</tt> - Optional HTML data attributes.
585
+ # * <tt>:html</tt> - Other optional HTML attributes for the form tag.
586
+ #
587
+ # === Examples
588
+ #
589
+ # When not passing a block, +form_with+ just generates an opening form tag.
590
+ #
591
+ # <%= form_with(model: @post, url: super_posts_path) %>
592
+ # <%= form_with(model: @post, scope: :article) %>
593
+ # <%= form_with(model: @post, format: :json) %>
594
+ # <%= form_with(model: @post, authenticity_token: false) %> # Disables the token.
595
+ #
596
+ # For namespaced routes, like +admin_post_url+:
597
+ #
598
+ # <%= form_with(model: [ :admin, @post ]) do |form| %>
599
+ # ...
600
+ # <% end %>
601
+ #
602
+ # If your resource has associations defined, for example, you want to add comments
603
+ # to the document given that the routes are set correctly:
604
+ #
605
+ # <%= form_with(model: [ @document, Comment.new ]) do |form| %>
606
+ # ...
607
+ # <% end %>
608
+ #
609
+ # Where <tt>@document = Document.find(params[:id])</tt>.
610
+ #
611
+ # When using labels +form_with+ requires setting the id on the field being
612
+ # labelled:
613
+ #
614
+ # <%= form_with(model: @post) do |form| %>
615
+ # <%= form.label :title %>
616
+ # <%= form.text_field :title, id: :post_title %>
617
+ # <% end %>
618
+ #
619
+ # See +label+ for more on how the +for+ attribute is derived.
620
+ #
621
+ # === Mixing with other form helpers
622
+ #
623
+ # While +form_with+ uses a FormBuilder object it's possible to mix and
624
+ # match the stand-alone FormHelper methods and methods
625
+ # from FormTagHelper:
626
+ #
627
+ # <%= form_with scope: :person do |form| %>
628
+ # <%= form.text_field :first_name %>
629
+ # <%= form.text_field :last_name %>
630
+ #
631
+ # <%= text_area :person, :biography %>
632
+ # <%= check_box_tag "person[admin]", "1", @person.company.admin? %>
633
+ #
634
+ # <%= form.submit %>
635
+ # <% end %>
636
+ #
637
+ # Same goes for the methods in FormOptionHelper and DateHelper designed
638
+ # to work with an object as a base, like
639
+ # FormOptionHelper#collection_select and DateHelper#datetime_select.
640
+ #
641
+ # === Setting the method
642
+ #
643
+ # You can force the form to use the full array of HTTP verbs by setting
644
+ #
645
+ # method: (:get|:post|:patch|:put|:delete)
646
+ #
647
+ # in the options hash. If the verb is not GET or POST, which are natively
648
+ # supported by HTML forms, the form will be set to POST and a hidden input
649
+ # called _method will carry the intended verb for the server to interpret.
650
+ #
651
+ # === Setting HTML options
652
+ #
653
+ # You can set data attributes directly in a data hash, but HTML options
654
+ # besides id and class must be wrapped in an HTML key:
655
+ #
656
+ # <%= form_with(model: @post, data: { behavior: "autosave" }, html: { name: "go" }) do |form| %>
657
+ # ...
658
+ # <% end %>
659
+ #
660
+ # generates
661
+ #
662
+ # <form action="/posts/123" method="post" data-behavior="autosave" name="go">
663
+ # <input name="_method" type="hidden" value="patch" />
664
+ # ...
665
+ # </form>
666
+ #
667
+ # === Removing hidden model id's
668
+ #
669
+ # The +form_with+ method automatically includes the model id as a hidden field in the form.
670
+ # This is used to maintain the correlation between the form data and its associated model.
671
+ # Some ORM systems do not use IDs on nested models so in this case you want to be able
672
+ # to disable the hidden id.
673
+ #
674
+ # In the following example the Post model has many Comments stored within it in a NoSQL database,
675
+ # thus there is no primary key for comments.
676
+ #
677
+ # <%= form_with(model: @post) do |form| %>
678
+ # <%= form.fields(:comments, skip_id: true) do |fields| %>
679
+ # ...
680
+ # <% end %>
681
+ # <% end %>
682
+ #
683
+ # === Customized form builders
684
+ #
685
+ # You can also build forms using a customized FormBuilder class. Subclass
686
+ # FormBuilder and override or define some more helpers, then use your
687
+ # custom builder. For example, let's say you made a helper to
688
+ # automatically add labels to form inputs.
689
+ #
690
+ # <%= form_with model: @person, url: { action: "create" }, builder: LabellingFormBuilder do |form| %>
691
+ # <%= form.text_field :first_name %>
692
+ # <%= form.text_field :last_name %>
693
+ # <%= form.text_area :biography %>
694
+ # <%= form.check_box :admin %>
695
+ # <%= form.submit %>
696
+ # <% end %>
697
+ #
698
+ # In this case, if you use:
699
+ #
700
+ # <%= render form %>
701
+ #
702
+ # The rendered template is <tt>people/_labelling_form</tt> and the local
703
+ # variable referencing the form builder is called
704
+ # <tt>labelling_form</tt>.
705
+ #
706
+ # The custom FormBuilder class is automatically merged with the options
707
+ # of a nested +fields+ call, unless it's explicitly set.
708
+ #
709
+ # In many cases you will want to wrap the above in another helper, so you
710
+ # could do something like the following:
711
+ #
712
+ # def labelled_form_with(**options, &block)
713
+ # form_with(**options.merge(builder: LabellingFormBuilder), &block)
714
+ # end
715
+ def form_with(model: nil, scope: nil, url: nil, format: nil, **options)
716
+ options[:allow_method_names_outside_object] = true
717
+ options[:skip_default_ids] = true
718
+
719
+ if model
720
+ url ||= polymorphic_path(model, format: format)
721
+
722
+ model = model.last if model.is_a?(Array)
723
+ scope ||= model_name_from_record_or_class(model).param_key
724
+ end
725
+
726
+ if block_given?
727
+ builder = instantiate_builder(scope, model, options)
728
+ output = capture(builder, &Proc.new)
729
+ options[:multipart] ||= builder.multipart?
730
+
731
+ html_options = html_options_for_form_with(url, model, options)
732
+ form_tag_with_body(html_options, output)
733
+ else
734
+ html_options = html_options_for_form_with(url, model, options)
735
+ form_tag_html(html_options)
736
+ end
737
+ end
738
+
471
739
  # Creates a scope around a specific model object like form_for, but
472
740
  # doesn't create the form tags themselves. This makes fields_for suitable
473
741
  # for specifying additional model objects in the same form.
@@ -714,6 +982,74 @@ module ActionView
714
982
  capture(builder, &block)
715
983
  end
716
984
 
985
+ # Scopes input fields with either an explicit scope or model.
986
+ # Like +form_with+ does with <tt>:scope</tt> or <tt>:model</tt>,
987
+ # except it doesn't output the form tags.
988
+ #
989
+ # # Using a scope prefixes the input field names:
990
+ # <%= fields :comment do |fields| %>
991
+ # <%= fields.text_field :body %>
992
+ # <% end %>
993
+ # # => <input type="text" name="comment[body]>
994
+ #
995
+ # # Using a model infers the scope and assigns field values:
996
+ # <%= fields model: Comment.new(body: "full bodied") do |fields| %<
997
+ # <%= fields.text_field :body %>
998
+ # <% end %>
999
+ # # =>
1000
+ # <input type="text" name="comment[body] value="full bodied">
1001
+ #
1002
+ # # Using +fields+ with +form_with+:
1003
+ # <%= form_with model: @post do |form| %>
1004
+ # <%= form.text_field :title %>
1005
+ #
1006
+ # <%= form.fields :comment do |fields| %>
1007
+ # <%= fields.text_field :body %>
1008
+ # <% end %>
1009
+ # <% end %>
1010
+ #
1011
+ # Much like +form_with+ a FormBuilder instance associated with the scope
1012
+ # or model is yielded, so any generated field names are prefixed with
1013
+ # either the passed scope or the scope inferred from the <tt>:model</tt>.
1014
+ #
1015
+ # When using labels +fields+ requires setting the id on the field being
1016
+ # labelled:
1017
+ #
1018
+ # <%= fields :comment do |fields| %>
1019
+ # <%= fields.label :body %>
1020
+ # <%= fields.text_field :body, id: :comment_body %>
1021
+ # <% end %>
1022
+ #
1023
+ # See +label+ for more on how the +for+ attribute is derived.
1024
+ #
1025
+ # === Mixing with other form helpers
1026
+ #
1027
+ # While +form_with+ uses a FormBuilder object it's possible to mix and
1028
+ # match the stand-alone FormHelper methods and methods
1029
+ # from FormTagHelper:
1030
+ #
1031
+ # <%= fields model: @comment do |fields| %>
1032
+ # <%= fields.text_field :body %>
1033
+ #
1034
+ # <%= text_area :commenter, :biography %>
1035
+ # <%= check_box_tag "comment[all_caps]", "1", @comment.commenter.hulk_mode? %>
1036
+ # <% end %>
1037
+ #
1038
+ # Same goes for the methods in FormOptionHelper and DateHelper designed
1039
+ # to work with an object as a base, like
1040
+ # FormOptionHelper#collection_select and DateHelper#datetime_select.
1041
+ def fields(scope = nil, model: nil, **options, &block)
1042
+ options[:allow_method_names_outside_object] = true
1043
+ options[:skip_default_ids] = true
1044
+
1045
+ if model
1046
+ scope ||= model_name_from_record_or_class(model).param_key
1047
+ end
1048
+
1049
+ builder = instantiate_builder(scope, model, options)
1050
+ capture(builder, &block)
1051
+ end
1052
+
717
1053
  # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
718
1054
  # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
719
1055
  # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
@@ -759,7 +1095,7 @@ module ActionView
759
1095
  # # => <label for="post_privacy_public">Public Post</label>
760
1096
  #
761
1097
  # label(:post, :terms) do
762
- # 'Accept <a href="/terms">Terms</a>.'.html_safe
1098
+ # raw('Accept <a href="/terms">Terms</a>.')
763
1099
  # end
764
1100
  # # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
765
1101
  def label(object_name, method, content_or_options = nil, options = nil, &block)
@@ -843,8 +1179,8 @@ module ActionView
843
1179
  # file_field(:user, :avatar)
844
1180
  # # => <input type="file" id="user_avatar" name="user[avatar]" />
845
1181
  #
846
- # file_field(:post, :image, :multiple => true)
847
- # # => <input type="file" id="post_image" name="post[image]" multiple="true" />
1182
+ # file_field(:post, :image, multiple: true)
1183
+ # # => <input type="file" id="post_image" name="post[image][]" multiple="multiple" />
848
1184
  #
849
1185
  # file_field(:post, :attached, accept: 'text/html')
850
1186
  # # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
@@ -959,6 +1295,7 @@ module ActionView
959
1295
  # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
960
1296
  # # <input type="radio" id="post_category_java" name="post[category]" value="java" />
961
1297
  #
1298
+ # # Let's say that @user.receive_newsletter returns "no":
962
1299
  # radio_button("user", "receive_newsletter", "yes")
963
1300
  # radio_button("user", "receive_newsletter", "no")
964
1301
  # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
@@ -1014,7 +1351,7 @@ module ActionView
1014
1351
  # date_field("user", "born_on")
1015
1352
  # # => <input id="user_born_on" name="user[born_on]" type="date" />
1016
1353
  #
1017
- # The default value is generated by trying to call "to_date"
1354
+ # The default value is generated by trying to call +strftime+ with "%Y-%m-%d"
1018
1355
  # on the object's value, which makes it behave as expected for instances
1019
1356
  # of DateTime and ActiveSupport::TimeWithZone. You can still override that
1020
1357
  # by passing the "value" option explicitly, e.g.
@@ -1042,7 +1379,7 @@ module ActionView
1042
1379
  # Returns a text_field of type "time".
1043
1380
  #
1044
1381
  # The default value is generated by trying to call +strftime+ with "%T.%L"
1045
- # on the objects's value. It is still possible to override that
1382
+ # on the object's value. It is still possible to override that
1046
1383
  # by passing the "value" option.
1047
1384
  #
1048
1385
  # === Options
@@ -1068,38 +1405,9 @@ module ActionView
1068
1405
  Tags::TimeField.new(object_name, method, self, options).render
1069
1406
  end
1070
1407
 
1071
- # Returns a text_field of type "datetime".
1072
- #
1073
- # datetime_field("user", "born_on")
1074
- # # => <input id="user_born_on" name="user[born_on]" type="datetime" />
1075
- #
1076
- # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T.%L%z"
1077
- # on the object's value, which makes it behave as expected for instances
1078
- # of DateTime and ActiveSupport::TimeWithZone.
1079
- #
1080
- # @user.born_on = Date.new(1984, 1, 12)
1081
- # datetime_field("user", "born_on")
1082
- # # => <input id="user_born_on" name="user[born_on]" type="datetime" value="1984-01-12T00:00:00.000+0000" />
1083
- #
1084
- # You can create values for the "min" and "max" attributes by passing
1085
- # instances of Date or Time to the options hash.
1086
- #
1087
- # datetime_field("user", "born_on", min: Date.today)
1088
- # # => <input id="user_born_on" name="user[born_on]" type="datetime" min="2014-05-20T00:00:00.000+0000" />
1089
- #
1090
- # Alternatively, you can pass a String formatted as an ISO8601 datetime
1091
- # with UTC offset as the values for "min" and "max."
1092
- #
1093
- # datetime_field("user", "born_on", min: "2014-05-20T00:00:00+0000")
1094
- # # => <input id="user_born_on" name="user[born_on]" type="datetime" min="2014-05-20T00:00:00.000+0000" />
1095
- #
1096
- def datetime_field(object_name, method, options = {})
1097
- Tags::DatetimeField.new(object_name, method, self, options).render
1098
- end
1099
-
1100
1408
  # Returns a text_field of type "datetime-local".
1101
1409
  #
1102
- # datetime_local_field("user", "born_on")
1410
+ # datetime_field("user", "born_on")
1103
1411
  # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" />
1104
1412
  #
1105
1413
  # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T"
@@ -1107,25 +1415,27 @@ module ActionView
1107
1415
  # of DateTime and ActiveSupport::TimeWithZone.
1108
1416
  #
1109
1417
  # @user.born_on = Date.new(1984, 1, 12)
1110
- # datetime_local_field("user", "born_on")
1418
+ # datetime_field("user", "born_on")
1111
1419
  # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" value="1984-01-12T00:00:00" />
1112
1420
  #
1113
1421
  # You can create values for the "min" and "max" attributes by passing
1114
1422
  # instances of Date or Time to the options hash.
1115
1423
  #
1116
- # datetime_local_field("user", "born_on", min: Date.today)
1424
+ # datetime_field("user", "born_on", min: Date.today)
1117
1425
  # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" />
1118
1426
  #
1119
1427
  # Alternatively, you can pass a String formatted as an ISO8601 datetime as
1120
1428
  # the values for "min" and "max."
1121
1429
  #
1122
- # datetime_local_field("user", "born_on", min: "2014-05-20T00:00:00")
1430
+ # datetime_field("user", "born_on", min: "2014-05-20T00:00:00")
1123
1431
  # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" />
1124
1432
  #
1125
- def datetime_local_field(object_name, method, options = {})
1433
+ def datetime_field(object_name, method, options = {})
1126
1434
  Tags::DatetimeLocalField.new(object_name, method, self, options).render
1127
1435
  end
1128
1436
 
1437
+ alias datetime_local_field datetime_field
1438
+
1129
1439
  # Returns a text_field of type "month".
1130
1440
  #
1131
1441
  # month_field("user", "born_on")
@@ -1195,6 +1505,34 @@ module ActionView
1195
1505
  end
1196
1506
 
1197
1507
  private
1508
+ def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: !form_with_generates_remote_forms,
1509
+ skip_enforcing_utf8: false, **options)
1510
+ html_options = options.slice(:id, :class, :multipart, :method, :data).merge(html)
1511
+ html_options[:method] ||= :patch if model.respond_to?(:persisted?) && model.persisted?
1512
+ html_options[:enforce_utf8] = !skip_enforcing_utf8
1513
+
1514
+ html_options[:enctype] = "multipart/form-data" if html_options.delete(:multipart)
1515
+
1516
+ # The following URL is unescaped, this is just a hash of options, and it is the
1517
+ # responsibility of the caller to escape all the values.
1518
+ html_options[:action] = url_for(url_for_options || {})
1519
+ html_options[:"accept-charset"] = "UTF-8"
1520
+ html_options[:"data-remote"] = true unless local
1521
+
1522
+ html_options[:authenticity_token] = options.delete(:authenticity_token)
1523
+
1524
+ if !local && html_options[:authenticity_token].blank?
1525
+ html_options[:authenticity_token] = embed_authenticity_token_in_remote_forms
1526
+ end
1527
+
1528
+ if html_options[:authenticity_token] == true
1529
+ # Include the default authenticity_token, which is only generated when it's set to nil,
1530
+ # but we needed the true value to override the default of no authenticity_token on data-remote.
1531
+ html_options[:authenticity_token] = nil
1532
+ end
1533
+
1534
+ html_options.stringify_keys!
1535
+ end
1198
1536
 
1199
1537
  def instantiate_builder(record_name, record_object, options)
1200
1538
  case record_name
@@ -1203,7 +1541,7 @@ module ActionView
1203
1541
  object_name = record_name
1204
1542
  else
1205
1543
  object = record_name
1206
- object_name = model_name_from_record_or_class(object).param_key
1544
+ object_name = model_name_from_record_or_class(object).param_key if object
1207
1545
  end
1208
1546
 
1209
1547
  builder = options[:builder] || default_form_builder_class
@@ -1211,7 +1549,7 @@ module ActionView
1211
1549
  end
1212
1550
 
1213
1551
  def default_form_builder_class
1214
- builder = ActionView::Base.default_form_builder
1552
+ builder = default_form_builder || ActionView::Base.default_form_builder
1215
1553
  builder.respond_to?(:constantize) ? builder.constantize : builder
1216
1554
  end
1217
1555
  end
@@ -1269,7 +1607,7 @@ module ActionView
1269
1607
 
1270
1608
  # The methods which wrap a form helper call.
1271
1609
  class_attribute :field_helpers
1272
- self.field_helpers = [:fields_for, :label, :text_field, :password_field,
1610
+ self.field_helpers = [:fields_for, :fields, :label, :text_field, :password_field,
1273
1611
  :hidden_field, :file_field, :text_area, :check_box,
1274
1612
  :radio_button, :color_field, :search_field,
1275
1613
  :telephone_field, :phone_field, :date_field,
@@ -1291,7 +1629,7 @@ module ActionView
1291
1629
  end
1292
1630
 
1293
1631
  def self._to_partial_path
1294
- @_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, '')
1632
+ @_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, "")
1295
1633
  end
1296
1634
 
1297
1635
  def to_partial_path
@@ -1305,19 +1643,23 @@ module ActionView
1305
1643
  def initialize(object_name, object, template, options)
1306
1644
  @nested_child_index = {}
1307
1645
  @object_name, @object, @template, @options = object_name, object, template, options
1308
- @default_options = @options ? @options.slice(:index, :namespace) : {}
1646
+ @default_options = @options ? @options.slice(:index, :namespace, :skip_default_ids, :allow_method_names_outside_object) : {}
1647
+
1648
+ convert_to_legacy_options(@options)
1649
+
1309
1650
  if @object_name.to_s.match(/\[\]$/)
1310
- if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
1651
+ if (object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}")) && object.respond_to?(:to_param)
1311
1652
  @auto_index = object.to_param
1312
1653
  else
1313
1654
  raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
1314
1655
  end
1315
1656
  end
1657
+
1316
1658
  @multipart = nil
1317
1659
  @index = options[:index] || options[:child_index]
1318
1660
  end
1319
1661
 
1320
- (field_helpers - [:label, :check_box, :radio_button, :fields_for, :hidden_field, :file_field]).each do |selector|
1662
+ (field_helpers - [:label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field]).each do |selector|
1321
1663
  class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
1322
1664
  def #{selector}(method, options = {}) # def text_field(method, options = {})
1323
1665
  @template.send( # @template.send(
@@ -1586,19 +1928,37 @@ module ActionView
1586
1928
  record_name = model_name_from_record_or_class(record_object).param_key
1587
1929
  end
1588
1930
 
1931
+ object_name = @object_name
1589
1932
  index = if options.has_key?(:index)
1590
1933
  options[:index]
1591
1934
  elsif defined?(@auto_index)
1592
- self.object_name = @object_name.to_s.sub(/\[\]$/,"")
1935
+ object_name = object_name.to_s.sub(/\[\]$/, "")
1593
1936
  @auto_index
1594
1937
  end
1595
1938
 
1596
- record_name = index ? "#{object_name}[#{index}][#{record_name}]" : "#{object_name}[#{record_name}]"
1939
+ record_name = if index
1940
+ "#{object_name}[#{index}][#{record_name}]"
1941
+ elsif record_name.to_s.end_with?("[]")
1942
+ record_name = record_name.to_s.sub(/(.*)\[\]$/, "[\\1][#{record_object.id}]")
1943
+ "#{object_name}#{record_name}"
1944
+ else
1945
+ "#{object_name}[#{record_name}]"
1946
+ end
1597
1947
  fields_options[:child_index] = index
1598
1948
 
1599
1949
  @template.fields_for(record_name, record_object, fields_options, &block)
1600
1950
  end
1601
1951
 
1952
+ # See the docs for the <tt>ActionView::FormHelper.fields</tt> helper method.
1953
+ def fields(scope = nil, model: nil, **options, &block)
1954
+ options[:allow_method_names_outside_object] = true
1955
+ options[:skip_default_ids] = true
1956
+
1957
+ convert_to_legacy_options(options)
1958
+
1959
+ fields_for(scope || model, model, **options, &block)
1960
+ end
1961
+
1602
1962
  # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
1603
1963
  # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
1604
1964
  # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
@@ -1607,7 +1967,7 @@ module ActionView
1607
1967
  # target labels for radio_button tags (where the value is used in the ID of the input tag).
1608
1968
  #
1609
1969
  # ==== Examples
1610
- # label(:post, :title)
1970
+ # label(:title)
1611
1971
  # # => <label for="post_title">Title</label>
1612
1972
  #
1613
1973
  # You can localize your labels based on model and attribute names.
@@ -1620,7 +1980,7 @@ module ActionView
1620
1980
  #
1621
1981
  # Which then will result in
1622
1982
  #
1623
- # label(:post, :body)
1983
+ # label(:body)
1624
1984
  # # => <label for="post_body">Write your entire text here</label>
1625
1985
  #
1626
1986
  # Localization can also be based purely on the translation of the attribute-name
@@ -1631,21 +1991,22 @@ module ActionView
1631
1991
  # post:
1632
1992
  # cost: "Total cost"
1633
1993
  #
1634
- # label(:post, :cost)
1994
+ # label(:cost)
1635
1995
  # # => <label for="post_cost">Total cost</label>
1636
1996
  #
1637
- # label(:post, :title, "A short title")
1997
+ # label(:title, "A short title")
1638
1998
  # # => <label for="post_title">A short title</label>
1639
1999
  #
1640
- # label(:post, :title, "A short title", class: "title_label")
2000
+ # label(:title, "A short title", class: "title_label")
1641
2001
  # # => <label for="post_title" class="title_label">A short title</label>
1642
2002
  #
1643
- # label(:post, :privacy, "Public Post", value: "public")
2003
+ # label(:privacy, "Public Post", value: "public")
1644
2004
  # # => <label for="post_privacy_public">Public Post</label>
1645
2005
  #
1646
- # label(:post, :terms) do
1647
- # 'Accept <a href="/terms">Terms</a>.'.html_safe
2006
+ # label(:terms) do
2007
+ # raw('Accept <a href="/terms">Terms</a>.')
1648
2008
  # end
2009
+ # # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
1649
2010
  def label(method, text = nil, options = {}, &block)
1650
2011
  @template.label(@object_name, method, text, objectify_options(options), &block)
1651
2012
  end
@@ -1694,16 +2055,17 @@ module ActionView
1694
2055
  # hashes instead of arrays.
1695
2056
  #
1696
2057
  # # Let's say that @post.validated? is 1:
1697
- # check_box("post", "validated")
2058
+ # check_box("validated")
1698
2059
  # # => <input name="post[validated]" type="hidden" value="0" />
1699
2060
  # # <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" />
1700
2061
  #
1701
2062
  # # Let's say that @puppy.gooddog is "no":
1702
- # check_box("puppy", "gooddog", {}, "yes", "no")
2063
+ # check_box("gooddog", {}, "yes", "no")
1703
2064
  # # => <input name="puppy[gooddog]" type="hidden" value="no" />
1704
2065
  # # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
1705
2066
  #
1706
- # check_box("eula", "accepted", { class: 'eula_check' }, "yes", "no")
2067
+ # # Let's say that @eula.accepted is "no":
2068
+ # check_box("accepted", { class: 'eula_check' }, "yes", "no")
1707
2069
  # # => <input name="eula[accepted]" type="hidden" value="no" />
1708
2070
  # # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
1709
2071
  def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
@@ -1718,13 +2080,14 @@ module ActionView
1718
2080
  # +options+ hash. You may pass HTML options there as well.
1719
2081
  #
1720
2082
  # # Let's say that @post.category returns "rails":
1721
- # radio_button("post", "category", "rails")
1722
- # radio_button("post", "category", "java")
2083
+ # radio_button("category", "rails")
2084
+ # radio_button("category", "java")
1723
2085
  # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
1724
2086
  # # <input type="radio" id="post_category_java" name="post[category]" value="java" />
1725
2087
  #
1726
- # radio_button("user", "receive_newsletter", "yes")
1727
- # radio_button("user", "receive_newsletter", "no")
2088
+ # # Let's say that @user.receive_newsletter returns "no":
2089
+ # radio_button("receive_newsletter", "yes")
2090
+ # radio_button("receive_newsletter", "no")
1728
2091
  # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
1729
2092
  # # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
1730
2093
  def radio_button(method, tag_value, options = {})
@@ -1737,14 +2100,17 @@ module ActionView
1737
2100
  # shown.
1738
2101
  #
1739
2102
  # ==== Examples
1740
- # hidden_field(:signup, :pass_confirm)
1741
- # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
2103
+ # # Let's say that @signup.pass_confirm returns true:
2104
+ # hidden_field(:pass_confirm)
2105
+ # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="true" />
1742
2106
  #
1743
- # hidden_field(:post, :tag_list)
1744
- # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
2107
+ # # Let's say that @post.tag_list returns "blog, ruby":
2108
+ # hidden_field(:tag_list)
2109
+ # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="blog, ruby" />
1745
2110
  #
1746
- # hidden_field(:user, :token)
1747
- # # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
2111
+ # # Let's say that @user.token returns "abcde":
2112
+ # hidden_field(:token)
2113
+ # # => <input type="hidden" id="user_token" name="user[token]" value="abcde" />
1748
2114
  #
1749
2115
  def hidden_field(method, options = {})
1750
2116
  @emitted_hidden_id = true if method == :id
@@ -1765,19 +2131,24 @@ module ActionView
1765
2131
  # * <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.
1766
2132
  #
1767
2133
  # ==== Examples
1768
- # file_field(:user, :avatar)
2134
+ # # Let's say that @user has avatar:
2135
+ # file_field(:avatar)
1769
2136
  # # => <input type="file" id="user_avatar" name="user[avatar]" />
1770
2137
  #
1771
- # file_field(:post, :image, :multiple => true)
1772
- # # => <input type="file" id="post_image" name="post[image]" multiple="true" />
2138
+ # # Let's say that @post has image:
2139
+ # file_field(:image, :multiple => true)
2140
+ # # => <input type="file" id="post_image" name="post[image][]" multiple="multiple" />
1773
2141
  #
1774
- # file_field(:post, :attached, accept: 'text/html')
2142
+ # # Let's say that @post has attached:
2143
+ # file_field(:attached, accept: 'text/html')
1775
2144
  # # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
1776
2145
  #
1777
- # file_field(:post, :image, accept: 'image/png,image/gif,image/jpeg')
2146
+ # # Let's say that @post has image:
2147
+ # file_field(:image, accept: 'image/png,image/gif,image/jpeg')
1778
2148
  # # => <input type="file" id="post_image" name="post[image]" accept="image/png,image/gif,image/jpeg" />
1779
2149
  #
1780
- # file_field(:attachment, :file, class: 'file_input')
2150
+ # # Let's say that @attachment has file:
2151
+ # file_field(:file, class: 'file_input')
1781
2152
  # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
1782
2153
  def file_field(method, options = {})
1783
2154
  self.multipart = true
@@ -1811,7 +2182,7 @@ module ActionView
1811
2182
  # post:
1812
2183
  # create: "Add %{model}"
1813
2184
  #
1814
- def submit(value=nil, options={})
2185
+ def submit(value = nil, options = {})
1815
2186
  value, options = nil, value if value.is_a?(Hash)
1816
2187
  value ||= submit_default_value
1817
2188
  @template.submit_tag(value, options)
@@ -1845,7 +2216,7 @@ module ActionView
1845
2216
  # create: "Add %{model}"
1846
2217
  #
1847
2218
  # ==== Examples
1848
- # button("Create a post")
2219
+ # button("Create post")
1849
2220
  # # => <button name='button' type='submit'>Create post</button>
1850
2221
  #
1851
2222
  # button do
@@ -1906,7 +2277,11 @@ module ActionView
1906
2277
  explicit_child_index = options[:child_index]
1907
2278
  output = ActiveSupport::SafeBuffer.new
1908
2279
  association.each do |child|
1909
- options[:child_index] = nested_child_index(name) unless explicit_child_index
2280
+ if explicit_child_index
2281
+ options[:child_index] = explicit_child_index.call if explicit_child_index.respond_to?(:call)
2282
+ else
2283
+ options[:child_index] = nested_child_index(name)
2284
+ end
1910
2285
  output << fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
1911
2286
  end
1912
2287
  output
@@ -1932,6 +2307,12 @@ module ActionView
1932
2307
  @nested_child_index[name] ||= -1
1933
2308
  @nested_child_index[name] += 1
1934
2309
  end
2310
+
2311
+ def convert_to_legacy_options(options)
2312
+ if options.key?(:skip_id)
2313
+ options[:include_id] = !options.delete(:skip_id)
2314
+ end
2315
+ end
1935
2316
  end
1936
2317
  end
1937
2318