actionview 4.2.11.1 → 6.0.4.8

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 (114) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +242 -186
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +9 -8
  5. data/lib/action_view/base.rb +144 -37
  6. data/lib/action_view/buffers.rb +18 -1
  7. data/lib/action_view/cache_expiry.rb +53 -0
  8. data/lib/action_view/context.rb +8 -12
  9. data/lib/action_view/dependency_tracker.rb +54 -20
  10. data/lib/action_view/digestor.rb +88 -85
  11. data/lib/action_view/flows.rb +11 -12
  12. data/lib/action_view/gem_version.rb +6 -4
  13. data/lib/action_view/helpers/active_model_helper.rb +16 -11
  14. data/lib/action_view/helpers/asset_tag_helper.rb +241 -82
  15. data/lib/action_view/helpers/asset_url_helper.rb +171 -67
  16. data/lib/action_view/helpers/atom_feed_helper.rb +19 -17
  17. data/lib/action_view/helpers/cache_helper.rb +112 -42
  18. data/lib/action_view/helpers/capture_helper.rb +20 -13
  19. data/lib/action_view/helpers/controller_helper.rb +15 -4
  20. data/lib/action_view/helpers/csp_helper.rb +26 -0
  21. data/lib/action_view/helpers/csrf_helper.rb +8 -6
  22. data/lib/action_view/helpers/date_helper.rb +230 -129
  23. data/lib/action_view/helpers/debug_helper.rb +7 -6
  24. data/lib/action_view/helpers/form_helper.rb +755 -129
  25. data/lib/action_view/helpers/form_options_helper.rb +130 -75
  26. data/lib/action_view/helpers/form_tag_helper.rb +116 -71
  27. data/lib/action_view/helpers/javascript_helper.rb +30 -14
  28. data/lib/action_view/helpers/number_helper.rb +84 -59
  29. data/lib/action_view/helpers/output_safety_helper.rb +36 -4
  30. data/lib/action_view/helpers/rendering_helper.rb +11 -8
  31. data/lib/action_view/helpers/sanitize_helper.rb +30 -31
  32. data/lib/action_view/helpers/tag_helper.rb +232 -75
  33. data/lib/action_view/helpers/tags/base.rb +138 -98
  34. data/lib/action_view/helpers/tags/check_box.rb +20 -19
  35. data/lib/action_view/helpers/tags/checkable.rb +4 -2
  36. data/lib/action_view/helpers/tags/collection_check_boxes.rb +12 -34
  37. data/lib/action_view/helpers/tags/collection_helpers.rb +69 -36
  38. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +6 -12
  39. data/lib/action_view/helpers/tags/collection_select.rb +4 -2
  40. data/lib/action_view/helpers/tags/color_field.rb +4 -3
  41. data/lib/action_view/helpers/tags/date_field.rb +2 -1
  42. data/lib/action_view/helpers/tags/date_select.rb +37 -36
  43. data/lib/action_view/helpers/tags/datetime_field.rb +4 -3
  44. data/lib/action_view/helpers/tags/datetime_local_field.rb +2 -1
  45. data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
  46. data/lib/action_view/helpers/tags/email_field.rb +2 -0
  47. data/lib/action_view/helpers/tags/file_field.rb +2 -0
  48. data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
  49. data/lib/action_view/helpers/tags/hidden_field.rb +2 -0
  50. data/lib/action_view/helpers/tags/label.rb +3 -2
  51. data/lib/action_view/helpers/tags/month_field.rb +2 -1
  52. data/lib/action_view/helpers/tags/number_field.rb +2 -0
  53. data/lib/action_view/helpers/tags/password_field.rb +3 -1
  54. data/lib/action_view/helpers/tags/placeholderable.rb +3 -1
  55. data/lib/action_view/helpers/tags/radio_button.rb +7 -6
  56. data/lib/action_view/helpers/tags/range_field.rb +2 -0
  57. data/lib/action_view/helpers/tags/search_field.rb +14 -9
  58. data/lib/action_view/helpers/tags/select.rb +11 -10
  59. data/lib/action_view/helpers/tags/tel_field.rb +2 -0
  60. data/lib/action_view/helpers/tags/text_area.rb +4 -2
  61. data/lib/action_view/helpers/tags/text_field.rb +8 -8
  62. data/lib/action_view/helpers/tags/time_field.rb +2 -1
  63. data/lib/action_view/helpers/tags/time_select.rb +2 -0
  64. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
  65. data/lib/action_view/helpers/tags/translator.rb +15 -16
  66. data/lib/action_view/helpers/tags/url_field.rb +2 -0
  67. data/lib/action_view/helpers/tags/week_field.rb +2 -1
  68. data/lib/action_view/helpers/tags.rb +3 -1
  69. data/lib/action_view/helpers/text_helper.rb +56 -38
  70. data/lib/action_view/helpers/translation_helper.rb +91 -47
  71. data/lib/action_view/helpers/url_helper.rb +160 -105
  72. data/lib/action_view/helpers.rb +5 -3
  73. data/lib/action_view/layouts.rb +65 -61
  74. data/lib/action_view/log_subscriber.rb +61 -10
  75. data/lib/action_view/lookup_context.rb +147 -89
  76. data/lib/action_view/model_naming.rb +3 -1
  77. data/lib/action_view/path_set.rb +28 -23
  78. data/lib/action_view/railtie.rb +62 -6
  79. data/lib/action_view/record_identifier.rb +53 -26
  80. data/lib/action_view/renderer/abstract_renderer.rb +71 -13
  81. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +103 -0
  82. data/lib/action_view/renderer/partial_renderer.rb +239 -225
  83. data/lib/action_view/renderer/renderer.rb +22 -8
  84. data/lib/action_view/renderer/streaming_template_renderer.rb +54 -54
  85. data/lib/action_view/renderer/template_renderer.rb +79 -73
  86. data/lib/action_view/rendering.rb +68 -44
  87. data/lib/action_view/routing_url_for.rb +33 -22
  88. data/lib/action_view/tasks/cache_digests.rake +25 -0
  89. data/lib/action_view/template/error.rb +44 -29
  90. data/lib/action_view/template/handlers/builder.rb +12 -13
  91. data/lib/action_view/template/handlers/erb/erubi.rb +87 -0
  92. data/lib/action_view/template/handlers/erb.rb +24 -86
  93. data/lib/action_view/template/handlers/html.rb +11 -0
  94. data/lib/action_view/template/handlers/raw.rb +4 -4
  95. data/lib/action_view/template/handlers.rb +38 -8
  96. data/lib/action_view/template/html.rb +19 -10
  97. data/lib/action_view/template/inline.rb +22 -0
  98. data/lib/action_view/template/raw_file.rb +28 -0
  99. data/lib/action_view/template/resolver.rb +217 -193
  100. data/lib/action_view/template/sources/file.rb +17 -0
  101. data/lib/action_view/template/sources.rb +13 -0
  102. data/lib/action_view/template/text.rb +11 -10
  103. data/lib/action_view/template/types.rb +18 -18
  104. data/lib/action_view/template.rb +146 -90
  105. data/lib/action_view/test_case.rb +52 -32
  106. data/lib/action_view/testing/resolvers.rb +46 -34
  107. data/lib/action_view/unbound_template.rb +31 -0
  108. data/lib/action_view/version.rb +3 -1
  109. data/lib/action_view/view_paths.rb +48 -31
  110. data/lib/action_view.rb +11 -8
  111. data/lib/assets/compiled/rails-ujs.js +746 -0
  112. metadata +41 -32
  113. data/lib/action_view/helpers/record_tag_helper.rb +0 -108
  114. data/lib/action_view/tasks/dependencies.rake +0 -23
@@ -1,22 +1,25 @@
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
+ # frozen_string_literal: true
2
+
3
+ require "cgi"
4
+ require "action_view/helpers/date_helper"
5
+ require "action_view/helpers/tag_helper"
6
+ require "action_view/helpers/form_tag_helper"
7
+ require "action_view/helpers/active_model_helper"
8
+ require "action_view/model_naming"
9
+ require "action_view/record_identifier"
10
+ require "active_support/core_ext/module/attribute_accessors"
11
+ require "active_support/core_ext/hash/slice"
12
+ require "active_support/core_ext/string/output_safety"
13
+ require "active_support/core_ext/string/inflections"
11
14
 
12
15
  module ActionView
13
16
  # = Action View Form Helpers
14
- module Helpers
17
+ module Helpers #:nodoc:
15
18
  # Form helpers are designed to make working with resources much easier
16
19
  # compared to using vanilla HTML.
17
20
  #
18
21
  # Typically, a form designed to create or update a resource reflects the
19
- # identity of the resource in several ways: (i) the url that the form is
22
+ # identity of the resource in several ways: (i) the URL that the form is
20
23
  # sent to (the form element's +action+ attribute) should result in a request
21
24
  # being routed to the appropriate controller action (with the appropriate <tt>:id</tt>
22
25
  # parameter in the case of an existing resource), (ii) input fields should
@@ -66,9 +69,10 @@ module ActionView
66
69
  #
67
70
  # In particular, thanks to the conventions followed in the generated field names, the
68
71
  # 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>:
72
+ # set in the form. That hash is ready to be passed to <tt>Person.new</tt>:
70
73
  #
71
- # if @person = Person.create(params[:person])
74
+ # @person = Person.new(params[:person])
75
+ # if @person.save
72
76
  # # success
73
77
  # else
74
78
  # # error handling
@@ -110,6 +114,9 @@ module ActionView
110
114
  include FormTagHelper
111
115
  include UrlHelper
112
116
  include ModelNaming
117
+ include RecordIdentifier
118
+
119
+ attr_internal :default_form_builder
113
120
 
114
121
  # Creates a form that allows the user to create or update the attributes
115
122
  # of a specific model object.
@@ -138,6 +145,7 @@ module ActionView
138
145
  # will get expanded to
139
146
  #
140
147
  # <%= text_field :person, :first_name %>
148
+ #
141
149
  # which results in an HTML <tt><input></tt> tag whose +name+ attribute is
142
150
  # <tt>person[first_name]</tt>. This means that when the form is submitted,
143
151
  # the value entered by the user will be available in the controller as
@@ -158,7 +166,7 @@ module ActionView
158
166
  # So for example you may use a named route directly. When the model is
159
167
  # represented by a string or symbol, as in the example above, if the
160
168
  # <tt>:url</tt> option is not specified, by default the form will be
161
- # sent back to the current url (We will describe below an alternative
169
+ # sent back to the current URL (We will describe below an alternative
162
170
  # resource-oriented usage of +form_for+ in which the URL does not need
163
171
  # to be specified explicitly).
164
172
  # * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of
@@ -195,9 +203,9 @@ module ActionView
195
203
  # <%= f.submit %>
196
204
  # <% end %>
197
205
  #
198
- # This also works for the methods in FormOptionHelper and DateHelper that
206
+ # This also works for the methods in FormOptionsHelper and DateHelper that
199
207
  # are designed to work with an object as base, like
200
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
208
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
201
209
  #
202
210
  # === #form_for with a model object
203
211
  #
@@ -410,13 +418,13 @@ module ActionView
410
418
  #
411
419
  # To set an authenticity token you need to pass an <tt>:authenticity_token</tt> parameter
412
420
  #
413
- # <%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f|
421
+ # <%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f| %>
414
422
  # ...
415
423
  # <% end %>
416
424
  #
417
425
  # If you don't want to an authenticity token field be rendered at all just pass <tt>false</tt>:
418
426
  #
419
- # <%= form_for @invoice, url: external_url, authenticity_token: false do |f|
427
+ # <%= form_for @invoice, url: external_url, authenticity_token: false do |f| %>
420
428
  # ...
421
429
  # <% end %>
422
430
  def form_for(record, options = {}, &block)
@@ -461,13 +469,300 @@ module ActionView
461
469
  )
462
470
 
463
471
  options[:url] ||= if options.key?(:format)
464
- polymorphic_path(record, format: options.delete(:format))
465
- else
466
- polymorphic_path(record, {})
467
- end
472
+ polymorphic_path(record, format: options.delete(:format))
473
+ else
474
+ polymorphic_path(record, {})
475
+ end
468
476
  end
469
477
  private :apply_form_for_options!
470
478
 
479
+ mattr_accessor :form_with_generates_remote_forms, default: true
480
+
481
+ mattr_accessor :form_with_generates_ids, default: false
482
+
483
+ # Creates a form tag based on mixing URLs, scopes, or models.
484
+ #
485
+ # # Using just a URL:
486
+ # <%= form_with url: posts_path do |form| %>
487
+ # <%= form.text_field :title %>
488
+ # <% end %>
489
+ # # =>
490
+ # <form action="/posts" method="post" data-remote="true">
491
+ # <input type="text" name="title">
492
+ # </form>
493
+ #
494
+ # # Adding a scope prefixes the input field names:
495
+ # <%= form_with scope: :post, url: posts_path do |form| %>
496
+ # <%= form.text_field :title %>
497
+ # <% end %>
498
+ # # =>
499
+ # <form action="/posts" method="post" data-remote="true">
500
+ # <input type="text" name="post[title]">
501
+ # </form>
502
+ #
503
+ # # Using a model infers both the URL and scope:
504
+ # <%= form_with model: Post.new do |form| %>
505
+ # <%= form.text_field :title %>
506
+ # <% end %>
507
+ # # =>
508
+ # <form action="/posts" method="post" data-remote="true">
509
+ # <input type="text" name="post[title]">
510
+ # </form>
511
+ #
512
+ # # An existing model makes an update form and fills out field values:
513
+ # <%= form_with model: Post.first do |form| %>
514
+ # <%= form.text_field :title %>
515
+ # <% end %>
516
+ # # =>
517
+ # <form action="/posts/1" method="post" data-remote="true">
518
+ # <input type="hidden" name="_method" value="patch">
519
+ # <input type="text" name="post[title]" value="<the title of the post>">
520
+ # </form>
521
+ #
522
+ # # Though the fields don't have to correspond to model attributes:
523
+ # <%= form_with model: Cat.new do |form| %>
524
+ # <%= form.text_field :cats_dont_have_gills %>
525
+ # <%= form.text_field :but_in_forms_they_can %>
526
+ # <% end %>
527
+ # # =>
528
+ # <form action="/cats" method="post" data-remote="true">
529
+ # <input type="text" name="cat[cats_dont_have_gills]">
530
+ # <input type="text" name="cat[but_in_forms_they_can]">
531
+ # </form>
532
+ #
533
+ # The parameters in the forms are accessible in controllers according to
534
+ # their name nesting. So inputs named +title+ and <tt>post[title]</tt> are
535
+ # accessible as <tt>params[:title]</tt> and <tt>params[:post][:title]</tt>
536
+ # respectively.
537
+ #
538
+ # By default +form_with+ attaches the <tt>data-remote</tt> attribute
539
+ # submitting the form via an XMLHTTPRequest in the background if an
540
+ # Unobtrusive JavaScript driver, like rails-ujs, is used. See the
541
+ # <tt>:local</tt> option for more.
542
+ #
543
+ # For ease of comparison the examples above left out the submit button,
544
+ # as well as the auto generated hidden fields that enable UTF-8 support
545
+ # and adds an authenticity token needed for cross site request forgery
546
+ # protection.
547
+ #
548
+ # === Resource-oriented style
549
+ #
550
+ # In many of the examples just shown, the +:model+ passed to +form_with+
551
+ # is a _resource_. It corresponds to a set of RESTful routes, most likely
552
+ # defined via +resources+ in <tt>config/routes.rb</tt>.
553
+ #
554
+ # So when passing such a model record, Rails infers the URL and method.
555
+ #
556
+ # <%= form_with model: @post do |form| %>
557
+ # ...
558
+ # <% end %>
559
+ #
560
+ # is then equivalent to something like:
561
+ #
562
+ # <%= form_with scope: :post, url: post_path(@post), method: :patch do |form| %>
563
+ # ...
564
+ # <% end %>
565
+ #
566
+ # And for a new record
567
+ #
568
+ # <%= form_with model: Post.new do |form| %>
569
+ # ...
570
+ # <% end %>
571
+ #
572
+ # is equivalent to something like:
573
+ #
574
+ # <%= form_with scope: :post, url: posts_path do |form| %>
575
+ # ...
576
+ # <% end %>
577
+ #
578
+ # ==== +form_with+ options
579
+ #
580
+ # * <tt>:url</tt> - The URL the form submits to. Akin to values passed to
581
+ # +url_for+ or +link_to+. For example, you may use a named route
582
+ # directly. When a <tt>:scope</tt> is passed without a <tt>:url</tt> the
583
+ # form just submits to the current URL.
584
+ # * <tt>:method</tt> - The method to use when submitting the form, usually
585
+ # either "get" or "post". If "patch", "put", "delete", or another verb
586
+ # is used, a hidden input named <tt>_method</tt> is added to
587
+ # simulate the verb over post.
588
+ # * <tt>:format</tt> - The format of the route the form submits to.
589
+ # Useful when submitting to another resource type, like <tt>:json</tt>.
590
+ # Skipped if a <tt>:url</tt> is passed.
591
+ # * <tt>:scope</tt> - The scope to prefix input field names with and
592
+ # thereby how the submitted parameters are grouped in controllers.
593
+ # * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of
594
+ # id attributes on form elements. The namespace attribute will be prefixed
595
+ # with underscore on the generated HTML id.
596
+ # * <tt>:model</tt> - A model object to infer the <tt>:url</tt> and
597
+ # <tt>:scope</tt> by, plus fill out input field values.
598
+ # So if a +title+ attribute is set to "Ahoy!" then a +title+ input
599
+ # field's value would be "Ahoy!".
600
+ # If the model is a new record a create form is generated, if an
601
+ # existing record, however, an update form is generated.
602
+ # Pass <tt>:scope</tt> or <tt>:url</tt> to override the defaults.
603
+ # E.g. turn <tt>params[:post]</tt> into <tt>params[:article]</tt>.
604
+ # * <tt>:authenticity_token</tt> - Authenticity token to use in the form.
605
+ # Override with a custom authenticity token or pass <tt>false</tt> to
606
+ # skip the authenticity token field altogether.
607
+ # Useful when submitting to an external resource like a payment gateway
608
+ # that might limit the valid fields.
609
+ # Remote forms may omit the embedded authenticity token by setting
610
+ # <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>.
611
+ # This is helpful when fragment-caching the form. Remote forms
612
+ # get the authenticity token from the <tt>meta</tt> tag, so embedding is
613
+ # unnecessary unless you support browsers without JavaScript.
614
+ # * <tt>:local</tt> - By default form submits are remote and unobtrusive XHRs.
615
+ # Disable remote submits with <tt>local: true</tt>.
616
+ # * <tt>:skip_enforcing_utf8</tt> - If set to true, a hidden input with name
617
+ # utf8 is not output.
618
+ # * <tt>:builder</tt> - Override the object used to build the form.
619
+ # * <tt>:id</tt> - Optional HTML id attribute.
620
+ # * <tt>:class</tt> - Optional HTML class attribute.
621
+ # * <tt>:data</tt> - Optional HTML data attributes.
622
+ # * <tt>:html</tt> - Other optional HTML attributes for the form tag.
623
+ #
624
+ # === Examples
625
+ #
626
+ # When not passing a block, +form_with+ just generates an opening form tag.
627
+ #
628
+ # <%= form_with(model: @post, url: super_posts_path) %>
629
+ # <%= form_with(model: @post, scope: :article) %>
630
+ # <%= form_with(model: @post, format: :json) %>
631
+ # <%= form_with(model: @post, authenticity_token: false) %> # Disables the token.
632
+ #
633
+ # For namespaced routes, like +admin_post_url+:
634
+ #
635
+ # <%= form_with(model: [ :admin, @post ]) do |form| %>
636
+ # ...
637
+ # <% end %>
638
+ #
639
+ # If your resource has associations defined, for example, you want to add comments
640
+ # to the document given that the routes are set correctly:
641
+ #
642
+ # <%= form_with(model: [ @document, Comment.new ]) do |form| %>
643
+ # ...
644
+ # <% end %>
645
+ #
646
+ # Where <tt>@document = Document.find(params[:id])</tt>.
647
+ #
648
+ # === Mixing with other form helpers
649
+ #
650
+ # While +form_with+ uses a FormBuilder object it's possible to mix and
651
+ # match the stand-alone FormHelper methods and methods
652
+ # from FormTagHelper:
653
+ #
654
+ # <%= form_with scope: :person do |form| %>
655
+ # <%= form.text_field :first_name %>
656
+ # <%= form.text_field :last_name %>
657
+ #
658
+ # <%= text_area :person, :biography %>
659
+ # <%= check_box_tag "person[admin]", "1", @person.company.admin? %>
660
+ #
661
+ # <%= form.submit %>
662
+ # <% end %>
663
+ #
664
+ # Same goes for the methods in FormOptionsHelper and DateHelper designed
665
+ # to work with an object as a base, like
666
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
667
+ #
668
+ # === Setting the method
669
+ #
670
+ # You can force the form to use the full array of HTTP verbs by setting
671
+ #
672
+ # method: (:get|:post|:patch|:put|:delete)
673
+ #
674
+ # in the options hash. If the verb is not GET or POST, which are natively
675
+ # supported by HTML forms, the form will be set to POST and a hidden input
676
+ # called _method will carry the intended verb for the server to interpret.
677
+ #
678
+ # === Setting HTML options
679
+ #
680
+ # You can set data attributes directly in a data hash, but HTML options
681
+ # besides id and class must be wrapped in an HTML key:
682
+ #
683
+ # <%= form_with(model: @post, data: { behavior: "autosave" }, html: { name: "go" }) do |form| %>
684
+ # ...
685
+ # <% end %>
686
+ #
687
+ # generates
688
+ #
689
+ # <form action="/posts/123" method="post" data-behavior="autosave" name="go">
690
+ # <input name="_method" type="hidden" value="patch" />
691
+ # ...
692
+ # </form>
693
+ #
694
+ # === Removing hidden model id's
695
+ #
696
+ # The +form_with+ method automatically includes the model id as a hidden field in the form.
697
+ # This is used to maintain the correlation between the form data and its associated model.
698
+ # Some ORM systems do not use IDs on nested models so in this case you want to be able
699
+ # to disable the hidden id.
700
+ #
701
+ # In the following example the Post model has many Comments stored within it in a NoSQL database,
702
+ # thus there is no primary key for comments.
703
+ #
704
+ # <%= form_with(model: @post) do |form| %>
705
+ # <%= form.fields(:comments, skip_id: true) do |fields| %>
706
+ # ...
707
+ # <% end %>
708
+ # <% end %>
709
+ #
710
+ # === Customized form builders
711
+ #
712
+ # You can also build forms using a customized FormBuilder class. Subclass
713
+ # FormBuilder and override or define some more helpers, then use your
714
+ # custom builder. For example, let's say you made a helper to
715
+ # automatically add labels to form inputs.
716
+ #
717
+ # <%= form_with model: @person, url: { action: "create" }, builder: LabellingFormBuilder do |form| %>
718
+ # <%= form.text_field :first_name %>
719
+ # <%= form.text_field :last_name %>
720
+ # <%= form.text_area :biography %>
721
+ # <%= form.check_box :admin %>
722
+ # <%= form.submit %>
723
+ # <% end %>
724
+ #
725
+ # In this case, if you use:
726
+ #
727
+ # <%= render form %>
728
+ #
729
+ # The rendered template is <tt>people/_labelling_form</tt> and the local
730
+ # variable referencing the form builder is called
731
+ # <tt>labelling_form</tt>.
732
+ #
733
+ # The custom FormBuilder class is automatically merged with the options
734
+ # of a nested +fields+ call, unless it's explicitly set.
735
+ #
736
+ # In many cases you will want to wrap the above in another helper, so you
737
+ # could do something like the following:
738
+ #
739
+ # def labelled_form_with(**options, &block)
740
+ # form_with(**options.merge(builder: LabellingFormBuilder), &block)
741
+ # end
742
+ def form_with(model: nil, scope: nil, url: nil, format: nil, **options, &block)
743
+ options[:allow_method_names_outside_object] = true
744
+ options[:skip_default_ids] = !form_with_generates_ids
745
+
746
+ if model
747
+ url ||= polymorphic_path(model, format: format)
748
+
749
+ model = model.last if model.is_a?(Array)
750
+ scope ||= model_name_from_record_or_class(model).param_key
751
+ end
752
+
753
+ if block_given?
754
+ builder = instantiate_builder(scope, model, options)
755
+ output = capture(builder, &block)
756
+ options[:multipart] ||= builder.multipart?
757
+
758
+ html_options = html_options_for_form_with(url, model, **options)
759
+ form_tag_with_body(html_options, output)
760
+ else
761
+ html_options = html_options_for_form_with(url, model, **options)
762
+ form_tag_html(html_options)
763
+ end
764
+ end
765
+
471
766
  # Creates a scope around a specific model object like form_for, but
472
767
  # doesn't create the form tags themselves. This makes fields_for suitable
473
768
  # for specifying additional model objects in the same form.
@@ -525,9 +820,9 @@ module ActionView
525
820
  # _class_ of the model object, e.g. if <tt>@person.permission</tt>, is
526
821
  # of class +Permission+, the field will still be named <tt>permission[admin]</tt>.
527
822
  #
528
- # Note: This also works for the methods in FormOptionHelper and
823
+ # Note: This also works for the methods in FormOptionsHelper and
529
824
  # DateHelper that are designed to work with an object as base, like
530
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
825
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
531
826
  #
532
827
  # === Nested Attributes Examples
533
828
  #
@@ -714,6 +1009,63 @@ module ActionView
714
1009
  capture(builder, &block)
715
1010
  end
716
1011
 
1012
+ # Scopes input fields with either an explicit scope or model.
1013
+ # Like +form_with+ does with <tt>:scope</tt> or <tt>:model</tt>,
1014
+ # except it doesn't output the form tags.
1015
+ #
1016
+ # # Using a scope prefixes the input field names:
1017
+ # <%= fields :comment do |fields| %>
1018
+ # <%= fields.text_field :body %>
1019
+ # <% end %>
1020
+ # # => <input type="text" name="comment[body]">
1021
+ #
1022
+ # # Using a model infers the scope and assigns field values:
1023
+ # <%= fields model: Comment.new(body: "full bodied") do |fields| %>
1024
+ # <%= fields.text_field :body %>
1025
+ # <% end %>
1026
+ # # => <input type="text" name="comment[body]" value="full bodied">
1027
+ #
1028
+ # # Using +fields+ with +form_with+:
1029
+ # <%= form_with model: @post do |form| %>
1030
+ # <%= form.text_field :title %>
1031
+ #
1032
+ # <%= form.fields :comment do |fields| %>
1033
+ # <%= fields.text_field :body %>
1034
+ # <% end %>
1035
+ # <% end %>
1036
+ #
1037
+ # Much like +form_with+ a FormBuilder instance associated with the scope
1038
+ # or model is yielded, so any generated field names are prefixed with
1039
+ # either the passed scope or the scope inferred from the <tt>:model</tt>.
1040
+ #
1041
+ # === Mixing with other form helpers
1042
+ #
1043
+ # While +form_with+ uses a FormBuilder object it's possible to mix and
1044
+ # match the stand-alone FormHelper methods and methods
1045
+ # from FormTagHelper:
1046
+ #
1047
+ # <%= fields model: @comment do |fields| %>
1048
+ # <%= fields.text_field :body %>
1049
+ #
1050
+ # <%= text_area :commenter, :biography %>
1051
+ # <%= check_box_tag "comment[all_caps]", "1", @comment.commenter.hulk_mode? %>
1052
+ # <% end %>
1053
+ #
1054
+ # Same goes for the methods in FormOptionsHelper and DateHelper designed
1055
+ # to work with an object as a base, like
1056
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
1057
+ def fields(scope = nil, model: nil, **options, &block)
1058
+ options[:allow_method_names_outside_object] = true
1059
+ options[:skip_default_ids] = !form_with_generates_ids
1060
+
1061
+ if model
1062
+ scope ||= model_name_from_record_or_class(model).param_key
1063
+ end
1064
+
1065
+ builder = instantiate_builder(scope, model, options)
1066
+ capture(builder, &block)
1067
+ end
1068
+
717
1069
  # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
718
1070
  # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
719
1071
  # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
@@ -759,7 +1111,7 @@ module ActionView
759
1111
  # # => <label for="post_privacy_public">Public Post</label>
760
1112
  #
761
1113
  # label(:post, :terms) do
762
- # 'Accept <a href="/terms">Terms</a>.'.html_safe
1114
+ # raw('Accept <a href="/terms">Terms</a>.')
763
1115
  # end
764
1116
  # # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
765
1117
  def label(object_name, method, content_or_options = nil, options = nil, &block)
@@ -778,6 +1130,9 @@ module ActionView
778
1130
  # text_field(:post, :title, class: "create_input")
779
1131
  # # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
780
1132
  #
1133
+ # text_field(:post, :title, maxlength: 30, class: "title_input")
1134
+ # # => <input type="text" id="post_title" name="post[title]" maxlength="30" size="30" value="#{@post.title}" class="title_input" />
1135
+ #
781
1136
  # text_field(:session, :user, onchange: "if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }")
782
1137
  # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange="if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }"/>
783
1138
  #
@@ -843,8 +1198,8 @@ module ActionView
843
1198
  # file_field(:user, :avatar)
844
1199
  # # => <input type="file" id="user_avatar" name="user[avatar]" />
845
1200
  #
846
- # file_field(:post, :image, :multiple => true)
847
- # # => <input type="file" id="post_image" name="post[image]" multiple="true" />
1201
+ # file_field(:post, :image, multiple: true)
1202
+ # # => <input type="file" id="post_image" name="post[image][]" multiple="multiple" />
848
1203
  #
849
1204
  # file_field(:post, :attached, accept: 'text/html')
850
1205
  # # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
@@ -855,7 +1210,7 @@ module ActionView
855
1210
  # file_field(:attachment, :file, class: 'file_input')
856
1211
  # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
857
1212
  def file_field(object_name, method, options = {})
858
- Tags::FileField.new(object_name, method, self, options).render
1213
+ Tags::FileField.new(object_name, method, self, convert_direct_upload_option_to_url(options.dup)).render
859
1214
  end
860
1215
 
861
1216
  # Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
@@ -959,6 +1314,7 @@ module ActionView
959
1314
  # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
960
1315
  # # <input type="radio" id="post_category_java" name="post[category]" value="java" />
961
1316
  #
1317
+ # # Let's say that @user.receive_newsletter returns "no":
962
1318
  # radio_button("user", "receive_newsletter", "yes")
963
1319
  # radio_button("user", "receive_newsletter", "no")
964
1320
  # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
@@ -1014,7 +1370,7 @@ module ActionView
1014
1370
  # date_field("user", "born_on")
1015
1371
  # # => <input id="user_born_on" name="user[born_on]" type="date" />
1016
1372
  #
1017
- # The default value is generated by trying to call "to_date"
1373
+ # The default value is generated by trying to call +strftime+ with "%Y-%m-%d"
1018
1374
  # on the object's value, which makes it behave as expected for instances
1019
1375
  # of DateTime and ActiveSupport::TimeWithZone. You can still override that
1020
1376
  # by passing the "value" option explicitly, e.g.
@@ -1042,7 +1398,7 @@ module ActionView
1042
1398
  # Returns a text_field of type "time".
1043
1399
  #
1044
1400
  # 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
1401
+ # on the object's value. It is still possible to override that
1046
1402
  # by passing the "value" option.
1047
1403
  #
1048
1404
  # === Options
@@ -1068,38 +1424,9 @@ module ActionView
1068
1424
  Tags::TimeField.new(object_name, method, self, options).render
1069
1425
  end
1070
1426
 
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
1427
  # Returns a text_field of type "datetime-local".
1101
1428
  #
1102
- # datetime_local_field("user", "born_on")
1429
+ # datetime_field("user", "born_on")
1103
1430
  # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" />
1104
1431
  #
1105
1432
  # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T"
@@ -1107,25 +1434,27 @@ module ActionView
1107
1434
  # of DateTime and ActiveSupport::TimeWithZone.
1108
1435
  #
1109
1436
  # @user.born_on = Date.new(1984, 1, 12)
1110
- # datetime_local_field("user", "born_on")
1437
+ # datetime_field("user", "born_on")
1111
1438
  # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" value="1984-01-12T00:00:00" />
1112
1439
  #
1113
1440
  # You can create values for the "min" and "max" attributes by passing
1114
1441
  # instances of Date or Time to the options hash.
1115
1442
  #
1116
- # datetime_local_field("user", "born_on", min: Date.today)
1443
+ # datetime_field("user", "born_on", min: Date.today)
1117
1444
  # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" />
1118
1445
  #
1119
1446
  # Alternatively, you can pass a String formatted as an ISO8601 datetime as
1120
1447
  # the values for "min" and "max."
1121
1448
  #
1122
- # datetime_local_field("user", "born_on", min: "2014-05-20T00:00:00")
1449
+ # datetime_field("user", "born_on", min: "2014-05-20T00:00:00")
1123
1450
  # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" />
1124
1451
  #
1125
- def datetime_local_field(object_name, method, options = {})
1452
+ def datetime_field(object_name, method, options = {})
1126
1453
  Tags::DatetimeLocalField.new(object_name, method, self, options).render
1127
1454
  end
1128
1455
 
1456
+ alias datetime_local_field datetime_field
1457
+
1129
1458
  # Returns a text_field of type "month".
1130
1459
  #
1131
1460
  # month_field("user", "born_on")
@@ -1195,6 +1524,34 @@ module ActionView
1195
1524
  end
1196
1525
 
1197
1526
  private
1527
+ def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: !form_with_generates_remote_forms,
1528
+ skip_enforcing_utf8: nil, **options)
1529
+ html_options = options.slice(:id, :class, :multipart, :method, :data).merge(html)
1530
+ html_options[:method] ||= :patch if model.respond_to?(:persisted?) && model.persisted?
1531
+ html_options[:enforce_utf8] = !skip_enforcing_utf8 unless skip_enforcing_utf8.nil?
1532
+
1533
+ html_options[:enctype] = "multipart/form-data" if html_options.delete(:multipart)
1534
+
1535
+ # The following URL is unescaped, this is just a hash of options, and it is the
1536
+ # responsibility of the caller to escape all the values.
1537
+ html_options[:action] = url_for(url_for_options || {})
1538
+ html_options[:"accept-charset"] = "UTF-8"
1539
+ html_options[:"data-remote"] = true unless local
1540
+
1541
+ html_options[:authenticity_token] = options.delete(:authenticity_token)
1542
+
1543
+ if !local && html_options[:authenticity_token].blank?
1544
+ html_options[:authenticity_token] = embed_authenticity_token_in_remote_forms
1545
+ end
1546
+
1547
+ if html_options[:authenticity_token] == true
1548
+ # Include the default authenticity_token, which is only generated when it's set to nil,
1549
+ # but we needed the true value to override the default of no authenticity_token on data-remote.
1550
+ html_options[:authenticity_token] = nil
1551
+ end
1552
+
1553
+ html_options.stringify_keys!
1554
+ end
1198
1555
 
1199
1556
  def instantiate_builder(record_name, record_object, options)
1200
1557
  case record_name
@@ -1203,7 +1560,7 @@ module ActionView
1203
1560
  object_name = record_name
1204
1561
  else
1205
1562
  object = record_name
1206
- object_name = model_name_from_record_or_class(object).param_key
1563
+ object_name = model_name_from_record_or_class(object).param_key if object
1207
1564
  end
1208
1565
 
1209
1566
  builder = options[:builder] || default_form_builder_class
@@ -1211,7 +1568,7 @@ module ActionView
1211
1568
  end
1212
1569
 
1213
1570
  def default_form_builder_class
1214
- builder = ActionView::Base.default_form_builder
1571
+ builder = default_form_builder || ActionView::Base.default_form_builder
1215
1572
  builder.respond_to?(:constantize) ? builder.constantize : builder
1216
1573
  end
1217
1574
  end
@@ -1229,7 +1586,7 @@ module ActionView
1229
1586
  # In the above block, a +FormBuilder+ object is yielded as the
1230
1587
  # +person_form+ variable. This allows you to generate the +text_field+
1231
1588
  # and +check_box+ fields by specifying their eponymous methods, which
1232
- # modify the underlying template and associates the +@person+ model object
1589
+ # modify the underlying template and associates the <tt>@person</tt> model object
1233
1590
  # with the form.
1234
1591
  #
1235
1592
  # The +FormBuilder+ object can be thought of as serving as a proxy for the
@@ -1268,14 +1625,15 @@ module ActionView
1268
1625
  include ModelNaming
1269
1626
 
1270
1627
  # The methods which wrap a form helper call.
1271
- class_attribute :field_helpers
1272
- self.field_helpers = [:fields_for, :label, :text_field, :password_field,
1273
- :hidden_field, :file_field, :text_area, :check_box,
1274
- :radio_button, :color_field, :search_field,
1275
- :telephone_field, :phone_field, :date_field,
1276
- :time_field, :datetime_field, :datetime_local_field,
1277
- :month_field, :week_field, :url_field, :email_field,
1278
- :number_field, :range_field]
1628
+ class_attribute :field_helpers, default: [
1629
+ :fields_for, :fields, :label, :text_field, :password_field,
1630
+ :hidden_field, :file_field, :text_area, :check_box,
1631
+ :radio_button, :color_field, :search_field,
1632
+ :telephone_field, :phone_field, :date_field,
1633
+ :time_field, :datetime_field, :datetime_local_field,
1634
+ :month_field, :week_field, :url_field, :email_field,
1635
+ :number_field, :range_field
1636
+ ]
1279
1637
 
1280
1638
  attr_accessor :object_name, :object, :options
1281
1639
 
@@ -1291,7 +1649,7 @@ module ActionView
1291
1649
  end
1292
1650
 
1293
1651
  def self._to_partial_path
1294
- @_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, '')
1652
+ @_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, "")
1295
1653
  end
1296
1654
 
1297
1655
  def to_partial_path
@@ -1305,19 +1663,245 @@ module ActionView
1305
1663
  def initialize(object_name, object, template, options)
1306
1664
  @nested_child_index = {}
1307
1665
  @object_name, @object, @template, @options = object_name, object, template, options
1308
- @default_options = @options ? @options.slice(:index, :namespace) : {}
1666
+ @default_options = @options ? @options.slice(:index, :namespace, :skip_default_ids, :allow_method_names_outside_object) : {}
1667
+ @default_html_options = @default_options.except(:skip_default_ids, :allow_method_names_outside_object)
1668
+
1669
+ convert_to_legacy_options(@options)
1670
+
1309
1671
  if @object_name.to_s.match(/\[\]$/)
1310
- if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
1672
+ if (object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}")) && object.respond_to?(:to_param)
1311
1673
  @auto_index = object.to_param
1312
1674
  else
1313
1675
  raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
1314
1676
  end
1315
1677
  end
1678
+
1316
1679
  @multipart = nil
1317
1680
  @index = options[:index] || options[:child_index]
1318
1681
  end
1319
1682
 
1320
- (field_helpers - [:label, :check_box, :radio_button, :fields_for, :hidden_field, :file_field]).each do |selector|
1683
+ ##
1684
+ # :method: text_field
1685
+ #
1686
+ # :call-seq: text_field(method, options = {})
1687
+ #
1688
+ # Wraps ActionView::Helpers::FormHelper#text_field for form builders:
1689
+ #
1690
+ # <%= form_with model: @user do |f| %>
1691
+ # <%= f.text_field :name %>
1692
+ # <% end %>
1693
+ #
1694
+ # Please refer to the documentation of the base helper for details.
1695
+
1696
+ ##
1697
+ # :method: password_field
1698
+ #
1699
+ # :call-seq: password_field(method, options = {})
1700
+ #
1701
+ # Wraps ActionView::Helpers::FormHelper#password_field for form builders:
1702
+ #
1703
+ # <%= form_with model: @user do |f| %>
1704
+ # <%= f.password_field :password %>
1705
+ # <% end %>
1706
+ #
1707
+ # Please refer to the documentation of the base helper for details.
1708
+
1709
+ ##
1710
+ # :method: text_area
1711
+ #
1712
+ # :call-seq: text_area(method, options = {})
1713
+ #
1714
+ # Wraps ActionView::Helpers::FormHelper#text_area for form builders:
1715
+ #
1716
+ # <%= form_with model: @user do |f| %>
1717
+ # <%= f.text_area :detail %>
1718
+ # <% end %>
1719
+ #
1720
+ # Please refer to the documentation of the base helper for details.
1721
+
1722
+ ##
1723
+ # :method: color_field
1724
+ #
1725
+ # :call-seq: color_field(method, options = {})
1726
+ #
1727
+ # Wraps ActionView::Helpers::FormHelper#color_field for form builders:
1728
+ #
1729
+ # <%= form_with model: @user do |f| %>
1730
+ # <%= f.color_field :favorite_color %>
1731
+ # <% end %>
1732
+ #
1733
+ # Please refer to the documentation of the base helper for details.
1734
+
1735
+ ##
1736
+ # :method: search_field
1737
+ #
1738
+ # :call-seq: search_field(method, options = {})
1739
+ #
1740
+ # Wraps ActionView::Helpers::FormHelper#search_field for form builders:
1741
+ #
1742
+ # <%= form_with model: @user do |f| %>
1743
+ # <%= f.search_field :name %>
1744
+ # <% end %>
1745
+ #
1746
+ # Please refer to the documentation of the base helper for details.
1747
+
1748
+ ##
1749
+ # :method: telephone_field
1750
+ #
1751
+ # :call-seq: telephone_field(method, options = {})
1752
+ #
1753
+ # Wraps ActionView::Helpers::FormHelper#telephone_field for form builders:
1754
+ #
1755
+ # <%= form_with model: @user do |f| %>
1756
+ # <%= f.telephone_field :phone %>
1757
+ # <% end %>
1758
+ #
1759
+ # Please refer to the documentation of the base helper for details.
1760
+
1761
+ ##
1762
+ # :method: phone_field
1763
+ #
1764
+ # :call-seq: phone_field(method, options = {})
1765
+ #
1766
+ # Wraps ActionView::Helpers::FormHelper#phone_field for form builders:
1767
+ #
1768
+ # <%= form_with model: @user do |f| %>
1769
+ # <%= f.phone_field :phone %>
1770
+ # <% end %>
1771
+ #
1772
+ # Please refer to the documentation of the base helper for details.
1773
+
1774
+ ##
1775
+ # :method: date_field
1776
+ #
1777
+ # :call-seq: date_field(method, options = {})
1778
+ #
1779
+ # Wraps ActionView::Helpers::FormHelper#date_field for form builders:
1780
+ #
1781
+ # <%= form_with model: @user do |f| %>
1782
+ # <%= f.date_field :born_on %>
1783
+ # <% end %>
1784
+ #
1785
+ # Please refer to the documentation of the base helper for details.
1786
+
1787
+ ##
1788
+ # :method: time_field
1789
+ #
1790
+ # :call-seq: time_field(method, options = {})
1791
+ #
1792
+ # Wraps ActionView::Helpers::FormHelper#time_field for form builders:
1793
+ #
1794
+ # <%= form_with model: @user do |f| %>
1795
+ # <%= f.time_field :borned_at %>
1796
+ # <% end %>
1797
+ #
1798
+ # Please refer to the documentation of the base helper for details.
1799
+
1800
+ ##
1801
+ # :method: datetime_field
1802
+ #
1803
+ # :call-seq: datetime_field(method, options = {})
1804
+ #
1805
+ # Wraps ActionView::Helpers::FormHelper#datetime_field for form builders:
1806
+ #
1807
+ # <%= form_with model: @user do |f| %>
1808
+ # <%= f.datetime_field :graduation_day %>
1809
+ # <% end %>
1810
+ #
1811
+ # Please refer to the documentation of the base helper for details.
1812
+
1813
+ ##
1814
+ # :method: datetime_local_field
1815
+ #
1816
+ # :call-seq: datetime_local_field(method, options = {})
1817
+ #
1818
+ # Wraps ActionView::Helpers::FormHelper#datetime_local_field for form builders:
1819
+ #
1820
+ # <%= form_with model: @user do |f| %>
1821
+ # <%= f.datetime_local_field :graduation_day %>
1822
+ # <% end %>
1823
+ #
1824
+ # Please refer to the documentation of the base helper for details.
1825
+
1826
+ ##
1827
+ # :method: month_field
1828
+ #
1829
+ # :call-seq: month_field(method, options = {})
1830
+ #
1831
+ # Wraps ActionView::Helpers::FormHelper#month_field for form builders:
1832
+ #
1833
+ # <%= form_with model: @user do |f| %>
1834
+ # <%= f.month_field :birthday_month %>
1835
+ # <% end %>
1836
+ #
1837
+ # Please refer to the documentation of the base helper for details.
1838
+
1839
+ ##
1840
+ # :method: week_field
1841
+ #
1842
+ # :call-seq: week_field(method, options = {})
1843
+ #
1844
+ # Wraps ActionView::Helpers::FormHelper#week_field for form builders:
1845
+ #
1846
+ # <%= form_with model: @user do |f| %>
1847
+ # <%= f.week_field :birthday_week %>
1848
+ # <% end %>
1849
+ #
1850
+ # Please refer to the documentation of the base helper for details.
1851
+
1852
+ ##
1853
+ # :method: url_field
1854
+ #
1855
+ # :call-seq: url_field(method, options = {})
1856
+ #
1857
+ # Wraps ActionView::Helpers::FormHelper#url_field for form builders:
1858
+ #
1859
+ # <%= form_with model: @user do |f| %>
1860
+ # <%= f.url_field :homepage %>
1861
+ # <% end %>
1862
+ #
1863
+ # Please refer to the documentation of the base helper for details.
1864
+
1865
+ ##
1866
+ # :method: email_field
1867
+ #
1868
+ # :call-seq: email_field(method, options = {})
1869
+ #
1870
+ # Wraps ActionView::Helpers::FormHelper#email_field for form builders:
1871
+ #
1872
+ # <%= form_with model: @user do |f| %>
1873
+ # <%= f.email_field :address %>
1874
+ # <% end %>
1875
+ #
1876
+ # Please refer to the documentation of the base helper for details.
1877
+
1878
+ ##
1879
+ # :method: number_field
1880
+ #
1881
+ # :call-seq: number_field(method, options = {})
1882
+ #
1883
+ # Wraps ActionView::Helpers::FormHelper#number_field for form builders:
1884
+ #
1885
+ # <%= form_with model: @user do |f| %>
1886
+ # <%= f.number_field :age %>
1887
+ # <% end %>
1888
+ #
1889
+ # Please refer to the documentation of the base helper for details.
1890
+
1891
+ ##
1892
+ # :method: range_field
1893
+ #
1894
+ # :call-seq: range_field(method, options = {})
1895
+ #
1896
+ # Wraps ActionView::Helpers::FormHelper#range_field for form builders:
1897
+ #
1898
+ # <%= form_with model: @user do |f| %>
1899
+ # <%= f.range_field :age %>
1900
+ # <% end %>
1901
+ #
1902
+ # Please refer to the documentation of the base helper for details.
1903
+
1904
+ (field_helpers - [:label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field]).each do |selector|
1321
1905
  class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
1322
1906
  def #{selector}(method, options = {}) # def text_field(method, options = {})
1323
1907
  @template.send( # @template.send(
@@ -1386,9 +1970,9 @@ module ActionView
1386
1970
  # _class_ of the model object, e.g. if <tt>@person.permission</tt>, is
1387
1971
  # of class +Permission+, the field will still be named <tt>permission[admin]</tt>.
1388
1972
  #
1389
- # Note: This also works for the methods in FormOptionHelper and
1973
+ # Note: This also works for the methods in FormOptionsHelper and
1390
1974
  # DateHelper that are designed to work with an object as base, like
1391
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
1975
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
1392
1976
  #
1393
1977
  # === Nested Attributes Examples
1394
1978
  #
@@ -1586,19 +2170,37 @@ module ActionView
1586
2170
  record_name = model_name_from_record_or_class(record_object).param_key
1587
2171
  end
1588
2172
 
2173
+ object_name = @object_name
1589
2174
  index = if options.has_key?(:index)
1590
2175
  options[:index]
1591
2176
  elsif defined?(@auto_index)
1592
- self.object_name = @object_name.to_s.sub(/\[\]$/,"")
2177
+ object_name = object_name.to_s.sub(/\[\]$/, "")
1593
2178
  @auto_index
1594
2179
  end
1595
2180
 
1596
- record_name = index ? "#{object_name}[#{index}][#{record_name}]" : "#{object_name}[#{record_name}]"
2181
+ record_name = if index
2182
+ "#{object_name}[#{index}][#{record_name}]"
2183
+ elsif record_name.to_s.end_with?("[]")
2184
+ record_name = record_name.to_s.sub(/(.*)\[\]$/, "[\\1][#{record_object.id}]")
2185
+ "#{object_name}#{record_name}"
2186
+ else
2187
+ "#{object_name}[#{record_name}]"
2188
+ end
1597
2189
  fields_options[:child_index] = index
1598
2190
 
1599
2191
  @template.fields_for(record_name, record_object, fields_options, &block)
1600
2192
  end
1601
2193
 
2194
+ # See the docs for the <tt>ActionView::FormHelper.fields</tt> helper method.
2195
+ def fields(scope = nil, model: nil, **options, &block)
2196
+ options[:allow_method_names_outside_object] = true
2197
+ options[:skip_default_ids] = !FormHelper.form_with_generates_ids
2198
+
2199
+ convert_to_legacy_options(options)
2200
+
2201
+ fields_for(scope || model, model, options, &block)
2202
+ end
2203
+
1602
2204
  # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
1603
2205
  # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
1604
2206
  # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
@@ -1607,7 +2209,7 @@ module ActionView
1607
2209
  # target labels for radio_button tags (where the value is used in the ID of the input tag).
1608
2210
  #
1609
2211
  # ==== Examples
1610
- # label(:post, :title)
2212
+ # label(:title)
1611
2213
  # # => <label for="post_title">Title</label>
1612
2214
  #
1613
2215
  # You can localize your labels based on model and attribute names.
@@ -1620,7 +2222,7 @@ module ActionView
1620
2222
  #
1621
2223
  # Which then will result in
1622
2224
  #
1623
- # label(:post, :body)
2225
+ # label(:body)
1624
2226
  # # => <label for="post_body">Write your entire text here</label>
1625
2227
  #
1626
2228
  # Localization can also be based purely on the translation of the attribute-name
@@ -1631,21 +2233,22 @@ module ActionView
1631
2233
  # post:
1632
2234
  # cost: "Total cost"
1633
2235
  #
1634
- # label(:post, :cost)
2236
+ # label(:cost)
1635
2237
  # # => <label for="post_cost">Total cost</label>
1636
2238
  #
1637
- # label(:post, :title, "A short title")
2239
+ # label(:title, "A short title")
1638
2240
  # # => <label for="post_title">A short title</label>
1639
2241
  #
1640
- # label(:post, :title, "A short title", class: "title_label")
2242
+ # label(:title, "A short title", class: "title_label")
1641
2243
  # # => <label for="post_title" class="title_label">A short title</label>
1642
2244
  #
1643
- # label(:post, :privacy, "Public Post", value: "public")
2245
+ # label(:privacy, "Public Post", value: "public")
1644
2246
  # # => <label for="post_privacy_public">Public Post</label>
1645
2247
  #
1646
- # label(:post, :terms) do
1647
- # 'Accept <a href="/terms">Terms</a>.'.html_safe
2248
+ # label(:terms) do
2249
+ # raw('Accept <a href="/terms">Terms</a>.')
1648
2250
  # end
2251
+ # # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
1649
2252
  def label(method, text = nil, options = {}, &block)
1650
2253
  @template.label(@object_name, method, text, objectify_options(options), &block)
1651
2254
  end
@@ -1694,16 +2297,17 @@ module ActionView
1694
2297
  # hashes instead of arrays.
1695
2298
  #
1696
2299
  # # Let's say that @post.validated? is 1:
1697
- # check_box("post", "validated")
2300
+ # check_box("validated")
1698
2301
  # # => <input name="post[validated]" type="hidden" value="0" />
1699
2302
  # # <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" />
1700
2303
  #
1701
2304
  # # Let's say that @puppy.gooddog is "no":
1702
- # check_box("puppy", "gooddog", {}, "yes", "no")
2305
+ # check_box("gooddog", {}, "yes", "no")
1703
2306
  # # => <input name="puppy[gooddog]" type="hidden" value="no" />
1704
2307
  # # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
1705
2308
  #
1706
- # check_box("eula", "accepted", { class: 'eula_check' }, "yes", "no")
2309
+ # # Let's say that @eula.accepted is "no":
2310
+ # check_box("accepted", { class: 'eula_check' }, "yes", "no")
1707
2311
  # # => <input name="eula[accepted]" type="hidden" value="no" />
1708
2312
  # # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
1709
2313
  def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
@@ -1718,13 +2322,14 @@ module ActionView
1718
2322
  # +options+ hash. You may pass HTML options there as well.
1719
2323
  #
1720
2324
  # # Let's say that @post.category returns "rails":
1721
- # radio_button("post", "category", "rails")
1722
- # radio_button("post", "category", "java")
2325
+ # radio_button("category", "rails")
2326
+ # radio_button("category", "java")
1723
2327
  # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
1724
2328
  # # <input type="radio" id="post_category_java" name="post[category]" value="java" />
1725
2329
  #
1726
- # radio_button("user", "receive_newsletter", "yes")
1727
- # radio_button("user", "receive_newsletter", "no")
2330
+ # # Let's say that @user.receive_newsletter returns "no":
2331
+ # radio_button("receive_newsletter", "yes")
2332
+ # radio_button("receive_newsletter", "no")
1728
2333
  # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
1729
2334
  # # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
1730
2335
  def radio_button(method, tag_value, options = {})
@@ -1737,14 +2342,17 @@ module ActionView
1737
2342
  # shown.
1738
2343
  #
1739
2344
  # ==== Examples
1740
- # hidden_field(:signup, :pass_confirm)
1741
- # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
2345
+ # # Let's say that @signup.pass_confirm returns true:
2346
+ # hidden_field(:pass_confirm)
2347
+ # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="true" />
1742
2348
  #
1743
- # hidden_field(:post, :tag_list)
1744
- # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
2349
+ # # Let's say that @post.tag_list returns "blog, ruby":
2350
+ # hidden_field(:tag_list)
2351
+ # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="blog, ruby" />
1745
2352
  #
1746
- # hidden_field(:user, :token)
1747
- # # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
2353
+ # # Let's say that @user.token returns "abcde":
2354
+ # hidden_field(:token)
2355
+ # # => <input type="hidden" id="user_token" name="user[token]" value="abcde" />
1748
2356
  #
1749
2357
  def hidden_field(method, options = {})
1750
2358
  @emitted_hidden_id = true if method == :id
@@ -1765,19 +2373,24 @@ module ActionView
1765
2373
  # * <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
2374
  #
1767
2375
  # ==== Examples
1768
- # file_field(:user, :avatar)
2376
+ # # Let's say that @user has avatar:
2377
+ # file_field(:avatar)
1769
2378
  # # => <input type="file" id="user_avatar" name="user[avatar]" />
1770
2379
  #
1771
- # file_field(:post, :image, :multiple => true)
1772
- # # => <input type="file" id="post_image" name="post[image]" multiple="true" />
2380
+ # # Let's say that @post has image:
2381
+ # file_field(:image, :multiple => true)
2382
+ # # => <input type="file" id="post_image" name="post[image][]" multiple="multiple" />
1773
2383
  #
1774
- # file_field(:post, :attached, accept: 'text/html')
2384
+ # # Let's say that @post has attached:
2385
+ # file_field(:attached, accept: 'text/html')
1775
2386
  # # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
1776
2387
  #
1777
- # file_field(:post, :image, accept: 'image/png,image/gif,image/jpeg')
2388
+ # # Let's say that @post has image:
2389
+ # file_field(:image, accept: 'image/png,image/gif,image/jpeg')
1778
2390
  # # => <input type="file" id="post_image" name="post[image]" accept="image/png,image/gif,image/jpeg" />
1779
2391
  #
1780
- # file_field(:attachment, :file, class: 'file_input')
2392
+ # # Let's say that @attachment has file:
2393
+ # file_field(:file, class: 'file_input')
1781
2394
  # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
1782
2395
  def file_field(method, options = {})
1783
2396
  self.multipart = true
@@ -1791,11 +2404,11 @@ module ActionView
1791
2404
  # <%= f.submit %>
1792
2405
  # <% end %>
1793
2406
  #
1794
- # In the example above, if @post is a new record, it will use "Create Post" as
1795
- # submit button label, otherwise, it uses "Update Post".
2407
+ # In the example above, if <tt>@post</tt> is a new record, it will use "Create Post" as
2408
+ # submit button label; otherwise, it uses "Update Post".
1796
2409
  #
1797
- # Those labels can be customized using I18n, under the helpers.submit key and accept
1798
- # the %{model} as translation interpolation:
2410
+ # Those labels can be customized using I18n under the +helpers.submit+ key and using
2411
+ # <tt>%{model}</tt> for translation interpolation:
1799
2412
  #
1800
2413
  # en:
1801
2414
  # helpers:
@@ -1803,7 +2416,7 @@ module ActionView
1803
2416
  # create: "Create a %{model}"
1804
2417
  # update: "Confirm changes to %{model}"
1805
2418
  #
1806
- # It also searches for a key specific for the given object:
2419
+ # It also searches for a key specific to the given object:
1807
2420
  #
1808
2421
  # en:
1809
2422
  # helpers:
@@ -1811,7 +2424,7 @@ module ActionView
1811
2424
  # post:
1812
2425
  # create: "Add %{model}"
1813
2426
  #
1814
- def submit(value=nil, options={})
2427
+ def submit(value = nil, options = {})
1815
2428
  value, options = nil, value if value.is_a?(Hash)
1816
2429
  value ||= submit_default_value
1817
2430
  @template.submit_tag(value, options)
@@ -1824,11 +2437,11 @@ module ActionView
1824
2437
  # <%= f.button %>
1825
2438
  # <% end %>
1826
2439
  #
1827
- # In the example above, if @post is a new record, it will use "Create Post" as
1828
- # button label, otherwise, it uses "Update Post".
2440
+ # In the example above, if <tt>@post</tt> is a new record, it will use "Create Post" as
2441
+ # button label; otherwise, it uses "Update Post".
1829
2442
  #
1830
- # Those labels can be customized using I18n, under the helpers.submit key
1831
- # (the same as submit helper) and accept the %{model} as translation interpolation:
2443
+ # Those labels can be customized using I18n under the +helpers.submit+ key
2444
+ # (the same as submit helper) and using <tt>%{model}</tt> for translation interpolation:
1832
2445
  #
1833
2446
  # en:
1834
2447
  # helpers:
@@ -1836,7 +2449,7 @@ module ActionView
1836
2449
  # create: "Create a %{model}"
1837
2450
  # update: "Confirm changes to %{model}"
1838
2451
  #
1839
- # It also searches for a key specific for the given object:
2452
+ # It also searches for a key specific to the given object:
1840
2453
  #
1841
2454
  # en:
1842
2455
  # helpers:
@@ -1845,7 +2458,7 @@ module ActionView
1845
2458
  # create: "Add %{model}"
1846
2459
  #
1847
2460
  # ==== Examples
1848
- # button("Create a post")
2461
+ # button("Create post")
1849
2462
  # # => <button name='button' type='submit'>Create post</button>
1850
2463
  #
1851
2464
  # button do
@@ -1861,7 +2474,7 @@ module ActionView
1861
2474
  @template.button_tag(value, options, &block)
1862
2475
  end
1863
2476
 
1864
- def emitted_hidden_id?
2477
+ def emitted_hidden_id? # :nodoc:
1865
2478
  @emitted_hidden_id ||= nil
1866
2479
  end
1867
2480
 
@@ -1881,7 +2494,12 @@ module ActionView
1881
2494
  end
1882
2495
 
1883
2496
  defaults = []
1884
- defaults << :"helpers.submit.#{object_name}.#{key}"
2497
+ # Object is a model and it is not overwritten by as and scope option.
2498
+ if object.respond_to?(:model_name) && object_name.to_s == model.downcase
2499
+ defaults << :"helpers.submit.#{object.model_name.i18n_key}.#{key}"
2500
+ else
2501
+ defaults << :"helpers.submit.#{object_name}.#{key}"
2502
+ end
1885
2503
  defaults << :"helpers.submit.#{key}"
1886
2504
  defaults << "#{key.to_s.humanize} #{model}"
1887
2505
 
@@ -1906,7 +2524,11 @@ module ActionView
1906
2524
  explicit_child_index = options[:child_index]
1907
2525
  output = ActiveSupport::SafeBuffer.new
1908
2526
  association.each do |child|
1909
- options[:child_index] = nested_child_index(name) unless explicit_child_index
2527
+ if explicit_child_index
2528
+ options[:child_index] = explicit_child_index.call if explicit_child_index.respond_to?(:call)
2529
+ else
2530
+ options[:child_index] = nested_child_index(name)
2531
+ end
1910
2532
  output << fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
1911
2533
  end
1912
2534
  output
@@ -1932,12 +2554,16 @@ module ActionView
1932
2554
  @nested_child_index[name] ||= -1
1933
2555
  @nested_child_index[name] += 1
1934
2556
  end
2557
+
2558
+ def convert_to_legacy_options(options)
2559
+ if options.key?(:skip_id)
2560
+ options[:include_id] = !options.delete(:skip_id)
2561
+ end
2562
+ end
1935
2563
  end
1936
2564
  end
1937
2565
 
1938
2566
  ActiveSupport.on_load(:action_view) do
1939
- cattr_accessor(:default_form_builder, instance_writer: false, instance_reader: false) do
1940
- ::ActionView::Helpers::FormBuilder
1941
- end
2567
+ cattr_accessor :default_form_builder, instance_writer: false, instance_reader: false, default: ::ActionView::Helpers::FormBuilder
1942
2568
  end
1943
2569
  end