actionview 4.2.11.1 → 6.1.5

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