actionview 4.2.11.3 → 5.2.7.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +115 -245
- data/MIT-LICENSE +1 -1
- data/README.rdoc +5 -6
- data/lib/action_view/base.rb +38 -28
- data/lib/action_view/buffers.rb +3 -1
- data/lib/action_view/context.rb +3 -3
- data/lib/action_view/dependency_tracker.rb +54 -20
- data/lib/action_view/digestor.rb +94 -83
- data/lib/action_view/flows.rb +11 -11
- data/lib/action_view/gem_version.rb +5 -3
- data/lib/action_view/helpers/active_model_helper.rb +17 -11
- data/lib/action_view/helpers/asset_tag_helper.rb +244 -62
- data/lib/action_view/helpers/asset_url_helper.rb +170 -67
- data/lib/action_view/helpers/atom_feed_helper.rb +19 -17
- data/lib/action_view/helpers/cache_helper.rb +105 -42
- data/lib/action_view/helpers/capture_helper.rb +16 -13
- data/lib/action_view/helpers/controller_helper.rb +15 -4
- data/lib/action_view/helpers/csp_helper.rb +24 -0
- data/lib/action_view/helpers/csrf_helper.rb +7 -5
- data/lib/action_view/helpers/date_helper.rb +170 -112
- data/lib/action_view/helpers/debug_helper.rb +7 -6
- data/lib/action_view/helpers/form_helper.rb +521 -127
- data/lib/action_view/helpers/form_options_helper.rb +109 -63
- data/lib/action_view/helpers/form_tag_helper.rb +110 -67
- data/lib/action_view/helpers/javascript_helper.rb +27 -12
- data/lib/action_view/helpers/number_helper.rb +77 -58
- data/lib/action_view/helpers/output_safety_helper.rb +36 -4
- data/lib/action_view/helpers/record_tag_helper.rb +14 -99
- data/lib/action_view/helpers/rendering_helper.rb +6 -5
- data/lib/action_view/helpers/sanitize_helper.rb +20 -15
- data/lib/action_view/helpers/tag_helper.rb +229 -73
- data/lib/action_view/helpers/tags/base.rb +134 -97
- data/lib/action_view/helpers/tags/check_box.rb +20 -18
- data/lib/action_view/helpers/tags/checkable.rb +4 -2
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +12 -33
- data/lib/action_view/helpers/tags/collection_helpers.rb +70 -36
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +6 -11
- data/lib/action_view/helpers/tags/collection_select.rb +4 -2
- data/lib/action_view/helpers/tags/color_field.rb +3 -1
- data/lib/action_view/helpers/tags/date_field.rb +2 -0
- data/lib/action_view/helpers/tags/date_select.rb +38 -36
- data/lib/action_view/helpers/tags/datetime_field.rb +4 -2
- data/lib/action_view/helpers/tags/datetime_local_field.rb +2 -0
- data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
- data/lib/action_view/helpers/tags/email_field.rb +2 -0
- data/lib/action_view/helpers/tags/file_field.rb +2 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
- data/lib/action_view/helpers/tags/hidden_field.rb +2 -0
- data/lib/action_view/helpers/tags/label.rb +3 -1
- data/lib/action_view/helpers/tags/month_field.rb +2 -0
- data/lib/action_view/helpers/tags/number_field.rb +2 -0
- data/lib/action_view/helpers/tags/password_field.rb +3 -1
- data/lib/action_view/helpers/tags/placeholderable.rb +3 -1
- data/lib/action_view/helpers/tags/radio_button.rb +7 -5
- data/lib/action_view/helpers/tags/range_field.rb +2 -0
- data/lib/action_view/helpers/tags/search_field.rb +14 -9
- data/lib/action_view/helpers/tags/select.rb +11 -9
- data/lib/action_view/helpers/tags/tel_field.rb +2 -0
- data/lib/action_view/helpers/tags/text_area.rb +4 -2
- data/lib/action_view/helpers/tags/text_field.rb +8 -7
- data/lib/action_view/helpers/tags/time_field.rb +2 -0
- data/lib/action_view/helpers/tags/time_select.rb +2 -0
- data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
- data/lib/action_view/helpers/tags/translator.rb +17 -13
- data/lib/action_view/helpers/tags/url_field.rb +2 -0
- data/lib/action_view/helpers/tags/week_field.rb +2 -0
- data/lib/action_view/helpers/tags.rb +3 -1
- data/lib/action_view/helpers/text_helper.rb +55 -36
- data/lib/action_view/helpers/translation_helper.rb +74 -32
- data/lib/action_view/helpers/url_helper.rb +159 -104
- data/lib/action_view/helpers.rb +5 -1
- data/lib/action_view/layouts.rb +65 -58
- data/lib/action_view/log_subscriber.rb +60 -8
- data/lib/action_view/lookup_context.rb +80 -65
- data/lib/action_view/model_naming.rb +3 -1
- data/lib/action_view/path_set.rb +30 -19
- data/lib/action_view/railtie.rb +39 -6
- data/lib/action_view/record_identifier.rb +53 -25
- data/lib/action_view/renderer/abstract_renderer.rb +21 -15
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +57 -0
- data/lib/action_view/renderer/partial_renderer.rb +218 -214
- data/lib/action_view/renderer/renderer.rb +8 -6
- data/lib/action_view/renderer/streaming_template_renderer.rb +50 -48
- data/lib/action_view/renderer/template_renderer.rb +67 -66
- data/lib/action_view/rendering.rb +19 -14
- data/lib/action_view/routing_url_for.rb +27 -17
- data/lib/action_view/tasks/cache_digests.rake +25 -0
- data/lib/action_view/template/error.rb +16 -16
- data/lib/action_view/template/handlers/builder.rb +10 -11
- data/lib/action_view/template/handlers/erb/erubi.rb +83 -0
- data/lib/action_view/template/handlers/erb.rb +9 -80
- data/lib/action_view/template/handlers/html.rb +11 -0
- data/lib/action_view/template/handlers/raw.rb +3 -3
- data/lib/action_view/template/handlers.rb +11 -7
- data/lib/action_view/template/html.rb +5 -5
- data/lib/action_view/template/resolver.rb +140 -115
- data/lib/action_view/template/text.rb +8 -9
- data/lib/action_view/template/types.rb +18 -18
- data/lib/action_view/template.rb +54 -33
- data/lib/action_view/test_case.rb +50 -29
- data/lib/action_view/testing/resolvers.rb +31 -31
- data/lib/action_view/version.rb +3 -1
- data/lib/action_view/view_paths.rb +28 -34
- data/lib/action_view.rb +8 -7
- data/lib/assets/compiled/rails-ujs.js +720 -0
- metadata +28 -27
- data/lib/action_view/tasks/dependencies.rake +0 -23
@@ -1,22 +1,25 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
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
|
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.
|
72
|
+
# set in the form. That hash is ready to be passed to <tt>Person.new</tt>:
|
70
73
|
#
|
71
|
-
#
|
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
|
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
|
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
|
-
#
|
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,297 @@ module ActionView
|
|
461
469
|
)
|
462
470
|
|
463
471
|
options[:url] ||= if options.key?(:format)
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
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>:model</tt> - A model object to infer the <tt>:url</tt> and
|
594
|
+
# <tt>:scope</tt> by, plus fill out input field values.
|
595
|
+
# So if a +title+ attribute is set to "Ahoy!" then a +title+ input
|
596
|
+
# field's value would be "Ahoy!".
|
597
|
+
# If the model is a new record a create form is generated, if an
|
598
|
+
# existing record, however, an update form is generated.
|
599
|
+
# Pass <tt>:scope</tt> or <tt>:url</tt> to override the defaults.
|
600
|
+
# E.g. turn <tt>params[:post]</tt> into <tt>params[:article]</tt>.
|
601
|
+
# * <tt>:authenticity_token</tt> - Authenticity token to use in the form.
|
602
|
+
# Override with a custom authenticity token or pass <tt>false</tt> to
|
603
|
+
# skip the authenticity token field altogether.
|
604
|
+
# Useful when submitting to an external resource like a payment gateway
|
605
|
+
# that might limit the valid fields.
|
606
|
+
# Remote forms may omit the embedded authenticity token by setting
|
607
|
+
# <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>.
|
608
|
+
# This is helpful when fragment-caching the form. Remote forms
|
609
|
+
# get the authenticity token from the <tt>meta</tt> tag, so embedding is
|
610
|
+
# unnecessary unless you support browsers without JavaScript.
|
611
|
+
# * <tt>:local</tt> - By default form submits are remote and unobtrusive XHRs.
|
612
|
+
# Disable remote submits with <tt>local: true</tt>.
|
613
|
+
# * <tt>:skip_enforcing_utf8</tt> - By default a hidden field named +utf8+
|
614
|
+
# is output to enforce UTF-8 submits. Set to true to skip the field.
|
615
|
+
# * <tt>:builder</tt> - Override the object used to build the form.
|
616
|
+
# * <tt>:id</tt> - Optional HTML id attribute.
|
617
|
+
# * <tt>:class</tt> - Optional HTML class attribute.
|
618
|
+
# * <tt>:data</tt> - Optional HTML data attributes.
|
619
|
+
# * <tt>:html</tt> - Other optional HTML attributes for the form tag.
|
620
|
+
#
|
621
|
+
# === Examples
|
622
|
+
#
|
623
|
+
# When not passing a block, +form_with+ just generates an opening form tag.
|
624
|
+
#
|
625
|
+
# <%= form_with(model: @post, url: super_posts_path) %>
|
626
|
+
# <%= form_with(model: @post, scope: :article) %>
|
627
|
+
# <%= form_with(model: @post, format: :json) %>
|
628
|
+
# <%= form_with(model: @post, authenticity_token: false) %> # Disables the token.
|
629
|
+
#
|
630
|
+
# For namespaced routes, like +admin_post_url+:
|
631
|
+
#
|
632
|
+
# <%= form_with(model: [ :admin, @post ]) do |form| %>
|
633
|
+
# ...
|
634
|
+
# <% end %>
|
635
|
+
#
|
636
|
+
# If your resource has associations defined, for example, you want to add comments
|
637
|
+
# to the document given that the routes are set correctly:
|
638
|
+
#
|
639
|
+
# <%= form_with(model: [ @document, Comment.new ]) do |form| %>
|
640
|
+
# ...
|
641
|
+
# <% end %>
|
642
|
+
#
|
643
|
+
# Where <tt>@document = Document.find(params[:id])</tt>.
|
644
|
+
#
|
645
|
+
# === Mixing with other form helpers
|
646
|
+
#
|
647
|
+
# While +form_with+ uses a FormBuilder object it's possible to mix and
|
648
|
+
# match the stand-alone FormHelper methods and methods
|
649
|
+
# from FormTagHelper:
|
650
|
+
#
|
651
|
+
# <%= form_with scope: :person do |form| %>
|
652
|
+
# <%= form.text_field :first_name %>
|
653
|
+
# <%= form.text_field :last_name %>
|
654
|
+
#
|
655
|
+
# <%= text_area :person, :biography %>
|
656
|
+
# <%= check_box_tag "person[admin]", "1", @person.company.admin? %>
|
657
|
+
#
|
658
|
+
# <%= form.submit %>
|
659
|
+
# <% end %>
|
660
|
+
#
|
661
|
+
# Same goes for the methods in FormOptionsHelper and DateHelper designed
|
662
|
+
# to work with an object as a base, like
|
663
|
+
# FormOptionsHelper#collection_select and DateHelper#datetime_select.
|
664
|
+
#
|
665
|
+
# === Setting the method
|
666
|
+
#
|
667
|
+
# You can force the form to use the full array of HTTP verbs by setting
|
668
|
+
#
|
669
|
+
# method: (:get|:post|:patch|:put|:delete)
|
670
|
+
#
|
671
|
+
# in the options hash. If the verb is not GET or POST, which are natively
|
672
|
+
# supported by HTML forms, the form will be set to POST and a hidden input
|
673
|
+
# called _method will carry the intended verb for the server to interpret.
|
674
|
+
#
|
675
|
+
# === Setting HTML options
|
676
|
+
#
|
677
|
+
# You can set data attributes directly in a data hash, but HTML options
|
678
|
+
# besides id and class must be wrapped in an HTML key:
|
679
|
+
#
|
680
|
+
# <%= form_with(model: @post, data: { behavior: "autosave" }, html: { name: "go" }) do |form| %>
|
681
|
+
# ...
|
682
|
+
# <% end %>
|
683
|
+
#
|
684
|
+
# generates
|
685
|
+
#
|
686
|
+
# <form action="/posts/123" method="post" data-behavior="autosave" name="go">
|
687
|
+
# <input name="_method" type="hidden" value="patch" />
|
688
|
+
# ...
|
689
|
+
# </form>
|
690
|
+
#
|
691
|
+
# === Removing hidden model id's
|
692
|
+
#
|
693
|
+
# The +form_with+ method automatically includes the model id as a hidden field in the form.
|
694
|
+
# This is used to maintain the correlation between the form data and its associated model.
|
695
|
+
# Some ORM systems do not use IDs on nested models so in this case you want to be able
|
696
|
+
# to disable the hidden id.
|
697
|
+
#
|
698
|
+
# In the following example the Post model has many Comments stored within it in a NoSQL database,
|
699
|
+
# thus there is no primary key for comments.
|
700
|
+
#
|
701
|
+
# <%= form_with(model: @post) do |form| %>
|
702
|
+
# <%= form.fields(:comments, skip_id: true) do |fields| %>
|
703
|
+
# ...
|
704
|
+
# <% end %>
|
705
|
+
# <% end %>
|
706
|
+
#
|
707
|
+
# === Customized form builders
|
708
|
+
#
|
709
|
+
# You can also build forms using a customized FormBuilder class. Subclass
|
710
|
+
# FormBuilder and override or define some more helpers, then use your
|
711
|
+
# custom builder. For example, let's say you made a helper to
|
712
|
+
# automatically add labels to form inputs.
|
713
|
+
#
|
714
|
+
# <%= form_with model: @person, url: { action: "create" }, builder: LabellingFormBuilder do |form| %>
|
715
|
+
# <%= form.text_field :first_name %>
|
716
|
+
# <%= form.text_field :last_name %>
|
717
|
+
# <%= form.text_area :biography %>
|
718
|
+
# <%= form.check_box :admin %>
|
719
|
+
# <%= form.submit %>
|
720
|
+
# <% end %>
|
721
|
+
#
|
722
|
+
# In this case, if you use:
|
723
|
+
#
|
724
|
+
# <%= render form %>
|
725
|
+
#
|
726
|
+
# The rendered template is <tt>people/_labelling_form</tt> and the local
|
727
|
+
# variable referencing the form builder is called
|
728
|
+
# <tt>labelling_form</tt>.
|
729
|
+
#
|
730
|
+
# The custom FormBuilder class is automatically merged with the options
|
731
|
+
# of a nested +fields+ call, unless it's explicitly set.
|
732
|
+
#
|
733
|
+
# In many cases you will want to wrap the above in another helper, so you
|
734
|
+
# could do something like the following:
|
735
|
+
#
|
736
|
+
# def labelled_form_with(**options, &block)
|
737
|
+
# form_with(**options.merge(builder: LabellingFormBuilder), &block)
|
738
|
+
# end
|
739
|
+
def form_with(model: nil, scope: nil, url: nil, format: nil, **options, &block)
|
740
|
+
options[:allow_method_names_outside_object] = true
|
741
|
+
options[:skip_default_ids] = !form_with_generates_ids
|
742
|
+
|
743
|
+
if model
|
744
|
+
url ||= polymorphic_path(model, format: format)
|
745
|
+
|
746
|
+
model = model.last if model.is_a?(Array)
|
747
|
+
scope ||= model_name_from_record_or_class(model).param_key
|
748
|
+
end
|
749
|
+
|
750
|
+
if block_given?
|
751
|
+
builder = instantiate_builder(scope, model, options)
|
752
|
+
output = capture(builder, &block)
|
753
|
+
options[:multipart] ||= builder.multipart?
|
754
|
+
|
755
|
+
html_options = html_options_for_form_with(url, model, options)
|
756
|
+
form_tag_with_body(html_options, output)
|
757
|
+
else
|
758
|
+
html_options = html_options_for_form_with(url, model, options)
|
759
|
+
form_tag_html(html_options)
|
760
|
+
end
|
761
|
+
end
|
762
|
+
|
471
763
|
# Creates a scope around a specific model object like form_for, but
|
472
764
|
# doesn't create the form tags themselves. This makes fields_for suitable
|
473
765
|
# for specifying additional model objects in the same form.
|
@@ -525,9 +817,9 @@ module ActionView
|
|
525
817
|
# _class_ of the model object, e.g. if <tt>@person.permission</tt>, is
|
526
818
|
# of class +Permission+, the field will still be named <tt>permission[admin]</tt>.
|
527
819
|
#
|
528
|
-
# Note: This also works for the methods in
|
820
|
+
# Note: This also works for the methods in FormOptionsHelper and
|
529
821
|
# DateHelper that are designed to work with an object as base, like
|
530
|
-
#
|
822
|
+
# FormOptionsHelper#collection_select and DateHelper#datetime_select.
|
531
823
|
#
|
532
824
|
# === Nested Attributes Examples
|
533
825
|
#
|
@@ -714,6 +1006,63 @@ module ActionView
|
|
714
1006
|
capture(builder, &block)
|
715
1007
|
end
|
716
1008
|
|
1009
|
+
# Scopes input fields with either an explicit scope or model.
|
1010
|
+
# Like +form_with+ does with <tt>:scope</tt> or <tt>:model</tt>,
|
1011
|
+
# except it doesn't output the form tags.
|
1012
|
+
#
|
1013
|
+
# # Using a scope prefixes the input field names:
|
1014
|
+
# <%= fields :comment do |fields| %>
|
1015
|
+
# <%= fields.text_field :body %>
|
1016
|
+
# <% end %>
|
1017
|
+
# # => <input type="text" name="comment[body]">
|
1018
|
+
#
|
1019
|
+
# # Using a model infers the scope and assigns field values:
|
1020
|
+
# <%= fields model: Comment.new(body: "full bodied") do |fields| %>
|
1021
|
+
# <%= fields.text_field :body %>
|
1022
|
+
# <% end %>
|
1023
|
+
# # => <input type="text" name="comment[body]" value="full bodied">
|
1024
|
+
#
|
1025
|
+
# # Using +fields+ with +form_with+:
|
1026
|
+
# <%= form_with model: @post do |form| %>
|
1027
|
+
# <%= form.text_field :title %>
|
1028
|
+
#
|
1029
|
+
# <%= form.fields :comment do |fields| %>
|
1030
|
+
# <%= fields.text_field :body %>
|
1031
|
+
# <% end %>
|
1032
|
+
# <% end %>
|
1033
|
+
#
|
1034
|
+
# Much like +form_with+ a FormBuilder instance associated with the scope
|
1035
|
+
# or model is yielded, so any generated field names are prefixed with
|
1036
|
+
# either the passed scope or the scope inferred from the <tt>:model</tt>.
|
1037
|
+
#
|
1038
|
+
# === Mixing with other form helpers
|
1039
|
+
#
|
1040
|
+
# While +form_with+ uses a FormBuilder object it's possible to mix and
|
1041
|
+
# match the stand-alone FormHelper methods and methods
|
1042
|
+
# from FormTagHelper:
|
1043
|
+
#
|
1044
|
+
# <%= fields model: @comment do |fields| %>
|
1045
|
+
# <%= fields.text_field :body %>
|
1046
|
+
#
|
1047
|
+
# <%= text_area :commenter, :biography %>
|
1048
|
+
# <%= check_box_tag "comment[all_caps]", "1", @comment.commenter.hulk_mode? %>
|
1049
|
+
# <% end %>
|
1050
|
+
#
|
1051
|
+
# Same goes for the methods in FormOptionsHelper and DateHelper designed
|
1052
|
+
# to work with an object as a base, like
|
1053
|
+
# FormOptionsHelper#collection_select and DateHelper#datetime_select.
|
1054
|
+
def fields(scope = nil, model: nil, **options, &block)
|
1055
|
+
options[:allow_method_names_outside_object] = true
|
1056
|
+
options[:skip_default_ids] = !form_with_generates_ids
|
1057
|
+
|
1058
|
+
if model
|
1059
|
+
scope ||= model_name_from_record_or_class(model).param_key
|
1060
|
+
end
|
1061
|
+
|
1062
|
+
builder = instantiate_builder(scope, model, options)
|
1063
|
+
capture(builder, &block)
|
1064
|
+
end
|
1065
|
+
|
717
1066
|
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
|
718
1067
|
# assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
|
719
1068
|
# is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
|
@@ -759,7 +1108,7 @@ module ActionView
|
|
759
1108
|
# # => <label for="post_privacy_public">Public Post</label>
|
760
1109
|
#
|
761
1110
|
# label(:post, :terms) do
|
762
|
-
# 'Accept <a href="/terms">Terms</a>.'
|
1111
|
+
# raw('Accept <a href="/terms">Terms</a>.')
|
763
1112
|
# end
|
764
1113
|
# # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
|
765
1114
|
def label(object_name, method, content_or_options = nil, options = nil, &block)
|
@@ -843,8 +1192,8 @@ module ActionView
|
|
843
1192
|
# file_field(:user, :avatar)
|
844
1193
|
# # => <input type="file" id="user_avatar" name="user[avatar]" />
|
845
1194
|
#
|
846
|
-
# file_field(:post, :image, :
|
847
|
-
# # => <input type="file" id="post_image" name="post[image]" multiple="
|
1195
|
+
# file_field(:post, :image, multiple: true)
|
1196
|
+
# # => <input type="file" id="post_image" name="post[image][]" multiple="multiple" />
|
848
1197
|
#
|
849
1198
|
# file_field(:post, :attached, accept: 'text/html')
|
850
1199
|
# # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
|
@@ -855,7 +1204,7 @@ module ActionView
|
|
855
1204
|
# file_field(:attachment, :file, class: 'file_input')
|
856
1205
|
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
|
857
1206
|
def file_field(object_name, method, options = {})
|
858
|
-
Tags::FileField.new(object_name, method, self, options).render
|
1207
|
+
Tags::FileField.new(object_name, method, self, convert_direct_upload_option_to_url(options.dup)).render
|
859
1208
|
end
|
860
1209
|
|
861
1210
|
# Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
|
@@ -959,6 +1308,7 @@ module ActionView
|
|
959
1308
|
# # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
|
960
1309
|
# # <input type="radio" id="post_category_java" name="post[category]" value="java" />
|
961
1310
|
#
|
1311
|
+
# # Let's say that @user.receive_newsletter returns "no":
|
962
1312
|
# radio_button("user", "receive_newsletter", "yes")
|
963
1313
|
# radio_button("user", "receive_newsletter", "no")
|
964
1314
|
# # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
|
@@ -1014,7 +1364,7 @@ module ActionView
|
|
1014
1364
|
# date_field("user", "born_on")
|
1015
1365
|
# # => <input id="user_born_on" name="user[born_on]" type="date" />
|
1016
1366
|
#
|
1017
|
-
# The default value is generated by trying to call "
|
1367
|
+
# The default value is generated by trying to call +strftime+ with "%Y-%m-%d"
|
1018
1368
|
# on the object's value, which makes it behave as expected for instances
|
1019
1369
|
# of DateTime and ActiveSupport::TimeWithZone. You can still override that
|
1020
1370
|
# by passing the "value" option explicitly, e.g.
|
@@ -1042,7 +1392,7 @@ module ActionView
|
|
1042
1392
|
# Returns a text_field of type "time".
|
1043
1393
|
#
|
1044
1394
|
# The default value is generated by trying to call +strftime+ with "%T.%L"
|
1045
|
-
# on the
|
1395
|
+
# on the object's value. It is still possible to override that
|
1046
1396
|
# by passing the "value" option.
|
1047
1397
|
#
|
1048
1398
|
# === Options
|
@@ -1068,38 +1418,9 @@ module ActionView
|
|
1068
1418
|
Tags::TimeField.new(object_name, method, self, options).render
|
1069
1419
|
end
|
1070
1420
|
|
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
1421
|
# Returns a text_field of type "datetime-local".
|
1101
1422
|
#
|
1102
|
-
#
|
1423
|
+
# datetime_field("user", "born_on")
|
1103
1424
|
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" />
|
1104
1425
|
#
|
1105
1426
|
# The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T"
|
@@ -1107,25 +1428,27 @@ module ActionView
|
|
1107
1428
|
# of DateTime and ActiveSupport::TimeWithZone.
|
1108
1429
|
#
|
1109
1430
|
# @user.born_on = Date.new(1984, 1, 12)
|
1110
|
-
#
|
1431
|
+
# datetime_field("user", "born_on")
|
1111
1432
|
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" value="1984-01-12T00:00:00" />
|
1112
1433
|
#
|
1113
1434
|
# You can create values for the "min" and "max" attributes by passing
|
1114
1435
|
# instances of Date or Time to the options hash.
|
1115
1436
|
#
|
1116
|
-
#
|
1437
|
+
# datetime_field("user", "born_on", min: Date.today)
|
1117
1438
|
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" />
|
1118
1439
|
#
|
1119
1440
|
# Alternatively, you can pass a String formatted as an ISO8601 datetime as
|
1120
1441
|
# the values for "min" and "max."
|
1121
1442
|
#
|
1122
|
-
#
|
1443
|
+
# datetime_field("user", "born_on", min: "2014-05-20T00:00:00")
|
1123
1444
|
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" />
|
1124
1445
|
#
|
1125
|
-
def
|
1446
|
+
def datetime_field(object_name, method, options = {})
|
1126
1447
|
Tags::DatetimeLocalField.new(object_name, method, self, options).render
|
1127
1448
|
end
|
1128
1449
|
|
1450
|
+
alias datetime_local_field datetime_field
|
1451
|
+
|
1129
1452
|
# Returns a text_field of type "month".
|
1130
1453
|
#
|
1131
1454
|
# month_field("user", "born_on")
|
@@ -1195,6 +1518,34 @@ module ActionView
|
|
1195
1518
|
end
|
1196
1519
|
|
1197
1520
|
private
|
1521
|
+
def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: !form_with_generates_remote_forms,
|
1522
|
+
skip_enforcing_utf8: false, **options)
|
1523
|
+
html_options = options.slice(:id, :class, :multipart, :method, :data).merge(html)
|
1524
|
+
html_options[:method] ||= :patch if model.respond_to?(:persisted?) && model.persisted?
|
1525
|
+
html_options[:enforce_utf8] = !skip_enforcing_utf8
|
1526
|
+
|
1527
|
+
html_options[:enctype] = "multipart/form-data" if html_options.delete(:multipart)
|
1528
|
+
|
1529
|
+
# The following URL is unescaped, this is just a hash of options, and it is the
|
1530
|
+
# responsibility of the caller to escape all the values.
|
1531
|
+
html_options[:action] = url_for(url_for_options || {})
|
1532
|
+
html_options[:"accept-charset"] = "UTF-8"
|
1533
|
+
html_options[:"data-remote"] = true unless local
|
1534
|
+
|
1535
|
+
html_options[:authenticity_token] = options.delete(:authenticity_token)
|
1536
|
+
|
1537
|
+
if !local && html_options[:authenticity_token].blank?
|
1538
|
+
html_options[:authenticity_token] = embed_authenticity_token_in_remote_forms
|
1539
|
+
end
|
1540
|
+
|
1541
|
+
if html_options[:authenticity_token] == true
|
1542
|
+
# Include the default authenticity_token, which is only generated when it's set to nil,
|
1543
|
+
# but we needed the true value to override the default of no authenticity_token on data-remote.
|
1544
|
+
html_options[:authenticity_token] = nil
|
1545
|
+
end
|
1546
|
+
|
1547
|
+
html_options.stringify_keys!
|
1548
|
+
end
|
1198
1549
|
|
1199
1550
|
def instantiate_builder(record_name, record_object, options)
|
1200
1551
|
case record_name
|
@@ -1203,7 +1554,7 @@ module ActionView
|
|
1203
1554
|
object_name = record_name
|
1204
1555
|
else
|
1205
1556
|
object = record_name
|
1206
|
-
object_name = model_name_from_record_or_class(object).param_key
|
1557
|
+
object_name = model_name_from_record_or_class(object).param_key if object
|
1207
1558
|
end
|
1208
1559
|
|
1209
1560
|
builder = options[:builder] || default_form_builder_class
|
@@ -1211,7 +1562,7 @@ module ActionView
|
|
1211
1562
|
end
|
1212
1563
|
|
1213
1564
|
def default_form_builder_class
|
1214
|
-
builder = ActionView::Base.default_form_builder
|
1565
|
+
builder = default_form_builder || ActionView::Base.default_form_builder
|
1215
1566
|
builder.respond_to?(:constantize) ? builder.constantize : builder
|
1216
1567
|
end
|
1217
1568
|
end
|
@@ -1229,7 +1580,7 @@ module ActionView
|
|
1229
1580
|
# In the above block, a +FormBuilder+ object is yielded as the
|
1230
1581
|
# +person_form+ variable. This allows you to generate the +text_field+
|
1231
1582
|
# and +check_box+ fields by specifying their eponymous methods, which
|
1232
|
-
# modify the underlying template and associates the
|
1583
|
+
# modify the underlying template and associates the <tt>@person</tt> model object
|
1233
1584
|
# with the form.
|
1234
1585
|
#
|
1235
1586
|
# The +FormBuilder+ object can be thought of as serving as a proxy for the
|
@@ -1268,14 +1619,15 @@ module ActionView
|
|
1268
1619
|
include ModelNaming
|
1269
1620
|
|
1270
1621
|
# The methods which wrap a form helper call.
|
1271
|
-
class_attribute :field_helpers
|
1272
|
-
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1278
|
-
|
1622
|
+
class_attribute :field_helpers, default: [
|
1623
|
+
:fields_for, :fields, :label, :text_field, :password_field,
|
1624
|
+
:hidden_field, :file_field, :text_area, :check_box,
|
1625
|
+
:radio_button, :color_field, :search_field,
|
1626
|
+
:telephone_field, :phone_field, :date_field,
|
1627
|
+
:time_field, :datetime_field, :datetime_local_field,
|
1628
|
+
:month_field, :week_field, :url_field, :email_field,
|
1629
|
+
:number_field, :range_field
|
1630
|
+
]
|
1279
1631
|
|
1280
1632
|
attr_accessor :object_name, :object, :options
|
1281
1633
|
|
@@ -1291,7 +1643,7 @@ module ActionView
|
|
1291
1643
|
end
|
1292
1644
|
|
1293
1645
|
def self._to_partial_path
|
1294
|
-
@_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/,
|
1646
|
+
@_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, "")
|
1295
1647
|
end
|
1296
1648
|
|
1297
1649
|
def to_partial_path
|
@@ -1305,19 +1657,24 @@ module ActionView
|
|
1305
1657
|
def initialize(object_name, object, template, options)
|
1306
1658
|
@nested_child_index = {}
|
1307
1659
|
@object_name, @object, @template, @options = object_name, object, template, options
|
1308
|
-
@default_options = @options ? @options.slice(:index, :namespace) : {}
|
1660
|
+
@default_options = @options ? @options.slice(:index, :namespace, :skip_default_ids, :allow_method_names_outside_object) : {}
|
1661
|
+
@default_html_options = @default_options.except(:skip_default_ids, :allow_method_names_outside_object)
|
1662
|
+
|
1663
|
+
convert_to_legacy_options(@options)
|
1664
|
+
|
1309
1665
|
if @object_name.to_s.match(/\[\]$/)
|
1310
|
-
if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}")
|
1666
|
+
if (object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}")) && object.respond_to?(:to_param)
|
1311
1667
|
@auto_index = object.to_param
|
1312
1668
|
else
|
1313
1669
|
raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
|
1314
1670
|
end
|
1315
1671
|
end
|
1672
|
+
|
1316
1673
|
@multipart = nil
|
1317
1674
|
@index = options[:index] || options[:child_index]
|
1318
1675
|
end
|
1319
1676
|
|
1320
|
-
(field_helpers - [:label, :check_box, :radio_button, :fields_for, :hidden_field, :file_field]).each do |selector|
|
1677
|
+
(field_helpers - [:label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field]).each do |selector|
|
1321
1678
|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
1322
1679
|
def #{selector}(method, options = {}) # def text_field(method, options = {})
|
1323
1680
|
@template.send( # @template.send(
|
@@ -1386,9 +1743,9 @@ module ActionView
|
|
1386
1743
|
# _class_ of the model object, e.g. if <tt>@person.permission</tt>, is
|
1387
1744
|
# of class +Permission+, the field will still be named <tt>permission[admin]</tt>.
|
1388
1745
|
#
|
1389
|
-
# Note: This also works for the methods in
|
1746
|
+
# Note: This also works for the methods in FormOptionsHelper and
|
1390
1747
|
# DateHelper that are designed to work with an object as base, like
|
1391
|
-
#
|
1748
|
+
# FormOptionsHelper#collection_select and DateHelper#datetime_select.
|
1392
1749
|
#
|
1393
1750
|
# === Nested Attributes Examples
|
1394
1751
|
#
|
@@ -1586,19 +1943,37 @@ module ActionView
|
|
1586
1943
|
record_name = model_name_from_record_or_class(record_object).param_key
|
1587
1944
|
end
|
1588
1945
|
|
1946
|
+
object_name = @object_name
|
1589
1947
|
index = if options.has_key?(:index)
|
1590
1948
|
options[:index]
|
1591
1949
|
elsif defined?(@auto_index)
|
1592
|
-
|
1950
|
+
object_name = object_name.to_s.sub(/\[\]$/, "")
|
1593
1951
|
@auto_index
|
1594
1952
|
end
|
1595
1953
|
|
1596
|
-
record_name =
|
1954
|
+
record_name = if index
|
1955
|
+
"#{object_name}[#{index}][#{record_name}]"
|
1956
|
+
elsif record_name.to_s.end_with?("[]")
|
1957
|
+
record_name = record_name.to_s.sub(/(.*)\[\]$/, "[\\1][#{record_object.id}]")
|
1958
|
+
"#{object_name}#{record_name}"
|
1959
|
+
else
|
1960
|
+
"#{object_name}[#{record_name}]"
|
1961
|
+
end
|
1597
1962
|
fields_options[:child_index] = index
|
1598
1963
|
|
1599
1964
|
@template.fields_for(record_name, record_object, fields_options, &block)
|
1600
1965
|
end
|
1601
1966
|
|
1967
|
+
# See the docs for the <tt>ActionView::FormHelper.fields</tt> helper method.
|
1968
|
+
def fields(scope = nil, model: nil, **options, &block)
|
1969
|
+
options[:allow_method_names_outside_object] = true
|
1970
|
+
options[:skip_default_ids] = !FormHelper.form_with_generates_ids
|
1971
|
+
|
1972
|
+
convert_to_legacy_options(options)
|
1973
|
+
|
1974
|
+
fields_for(scope || model, model, options, &block)
|
1975
|
+
end
|
1976
|
+
|
1602
1977
|
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
|
1603
1978
|
# assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
|
1604
1979
|
# is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
|
@@ -1607,7 +1982,7 @@ module ActionView
|
|
1607
1982
|
# target labels for radio_button tags (where the value is used in the ID of the input tag).
|
1608
1983
|
#
|
1609
1984
|
# ==== Examples
|
1610
|
-
# label(:
|
1985
|
+
# label(:title)
|
1611
1986
|
# # => <label for="post_title">Title</label>
|
1612
1987
|
#
|
1613
1988
|
# You can localize your labels based on model and attribute names.
|
@@ -1620,7 +1995,7 @@ module ActionView
|
|
1620
1995
|
#
|
1621
1996
|
# Which then will result in
|
1622
1997
|
#
|
1623
|
-
# label(:
|
1998
|
+
# label(:body)
|
1624
1999
|
# # => <label for="post_body">Write your entire text here</label>
|
1625
2000
|
#
|
1626
2001
|
# Localization can also be based purely on the translation of the attribute-name
|
@@ -1631,21 +2006,22 @@ module ActionView
|
|
1631
2006
|
# post:
|
1632
2007
|
# cost: "Total cost"
|
1633
2008
|
#
|
1634
|
-
# label(:
|
2009
|
+
# label(:cost)
|
1635
2010
|
# # => <label for="post_cost">Total cost</label>
|
1636
2011
|
#
|
1637
|
-
# label(:
|
2012
|
+
# label(:title, "A short title")
|
1638
2013
|
# # => <label for="post_title">A short title</label>
|
1639
2014
|
#
|
1640
|
-
# label(:
|
2015
|
+
# label(:title, "A short title", class: "title_label")
|
1641
2016
|
# # => <label for="post_title" class="title_label">A short title</label>
|
1642
2017
|
#
|
1643
|
-
# label(:
|
2018
|
+
# label(:privacy, "Public Post", value: "public")
|
1644
2019
|
# # => <label for="post_privacy_public">Public Post</label>
|
1645
2020
|
#
|
1646
|
-
# label(:
|
1647
|
-
# 'Accept <a href="/terms">Terms</a>.'
|
2021
|
+
# label(:terms) do
|
2022
|
+
# raw('Accept <a href="/terms">Terms</a>.')
|
1648
2023
|
# end
|
2024
|
+
# # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
|
1649
2025
|
def label(method, text = nil, options = {}, &block)
|
1650
2026
|
@template.label(@object_name, method, text, objectify_options(options), &block)
|
1651
2027
|
end
|
@@ -1694,16 +2070,17 @@ module ActionView
|
|
1694
2070
|
# hashes instead of arrays.
|
1695
2071
|
#
|
1696
2072
|
# # Let's say that @post.validated? is 1:
|
1697
|
-
# check_box("
|
2073
|
+
# check_box("validated")
|
1698
2074
|
# # => <input name="post[validated]" type="hidden" value="0" />
|
1699
2075
|
# # <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" />
|
1700
2076
|
#
|
1701
2077
|
# # Let's say that @puppy.gooddog is "no":
|
1702
|
-
# check_box("
|
2078
|
+
# check_box("gooddog", {}, "yes", "no")
|
1703
2079
|
# # => <input name="puppy[gooddog]" type="hidden" value="no" />
|
1704
2080
|
# # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
|
1705
2081
|
#
|
1706
|
-
#
|
2082
|
+
# # Let's say that @eula.accepted is "no":
|
2083
|
+
# check_box("accepted", { class: 'eula_check' }, "yes", "no")
|
1707
2084
|
# # => <input name="eula[accepted]" type="hidden" value="no" />
|
1708
2085
|
# # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
|
1709
2086
|
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
|
@@ -1718,13 +2095,14 @@ module ActionView
|
|
1718
2095
|
# +options+ hash. You may pass HTML options there as well.
|
1719
2096
|
#
|
1720
2097
|
# # Let's say that @post.category returns "rails":
|
1721
|
-
# radio_button("
|
1722
|
-
# radio_button("
|
2098
|
+
# radio_button("category", "rails")
|
2099
|
+
# radio_button("category", "java")
|
1723
2100
|
# # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
|
1724
2101
|
# # <input type="radio" id="post_category_java" name="post[category]" value="java" />
|
1725
2102
|
#
|
1726
|
-
#
|
1727
|
-
# radio_button("
|
2103
|
+
# # Let's say that @user.receive_newsletter returns "no":
|
2104
|
+
# radio_button("receive_newsletter", "yes")
|
2105
|
+
# radio_button("receive_newsletter", "no")
|
1728
2106
|
# # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
|
1729
2107
|
# # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
|
1730
2108
|
def radio_button(method, tag_value, options = {})
|
@@ -1737,14 +2115,17 @@ module ActionView
|
|
1737
2115
|
# shown.
|
1738
2116
|
#
|
1739
2117
|
# ==== Examples
|
1740
|
-
#
|
1741
|
-
#
|
2118
|
+
# # Let's say that @signup.pass_confirm returns true:
|
2119
|
+
# hidden_field(:pass_confirm)
|
2120
|
+
# # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="true" />
|
1742
2121
|
#
|
1743
|
-
#
|
1744
|
-
#
|
2122
|
+
# # Let's say that @post.tag_list returns "blog, ruby":
|
2123
|
+
# hidden_field(:tag_list)
|
2124
|
+
# # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="blog, ruby" />
|
1745
2125
|
#
|
1746
|
-
#
|
1747
|
-
#
|
2126
|
+
# # Let's say that @user.token returns "abcde":
|
2127
|
+
# hidden_field(:token)
|
2128
|
+
# # => <input type="hidden" id="user_token" name="user[token]" value="abcde" />
|
1748
2129
|
#
|
1749
2130
|
def hidden_field(method, options = {})
|
1750
2131
|
@emitted_hidden_id = true if method == :id
|
@@ -1765,19 +2146,24 @@ module ActionView
|
|
1765
2146
|
# * <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
2147
|
#
|
1767
2148
|
# ==== Examples
|
1768
|
-
#
|
2149
|
+
# # Let's say that @user has avatar:
|
2150
|
+
# file_field(:avatar)
|
1769
2151
|
# # => <input type="file" id="user_avatar" name="user[avatar]" />
|
1770
2152
|
#
|
1771
|
-
#
|
1772
|
-
#
|
2153
|
+
# # Let's say that @post has image:
|
2154
|
+
# file_field(:image, :multiple => true)
|
2155
|
+
# # => <input type="file" id="post_image" name="post[image][]" multiple="multiple" />
|
1773
2156
|
#
|
1774
|
-
#
|
2157
|
+
# # Let's say that @post has attached:
|
2158
|
+
# file_field(:attached, accept: 'text/html')
|
1775
2159
|
# # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
|
1776
2160
|
#
|
1777
|
-
#
|
2161
|
+
# # Let's say that @post has image:
|
2162
|
+
# file_field(:image, accept: 'image/png,image/gif,image/jpeg')
|
1778
2163
|
# # => <input type="file" id="post_image" name="post[image]" accept="image/png,image/gif,image/jpeg" />
|
1779
2164
|
#
|
1780
|
-
#
|
2165
|
+
# # Let's say that @attachment has file:
|
2166
|
+
# file_field(:file, class: 'file_input')
|
1781
2167
|
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
|
1782
2168
|
def file_field(method, options = {})
|
1783
2169
|
self.multipart = true
|
@@ -1791,11 +2177,11 @@ module ActionView
|
|
1791
2177
|
# <%= f.submit %>
|
1792
2178
|
# <% end %>
|
1793
2179
|
#
|
1794
|
-
# In the example above, if
|
1795
|
-
# submit button label
|
2180
|
+
# In the example above, if <tt>@post</tt> is a new record, it will use "Create Post" as
|
2181
|
+
# submit button label; otherwise, it uses "Update Post".
|
1796
2182
|
#
|
1797
|
-
# Those labels can be customized using I18n
|
1798
|
-
#
|
2183
|
+
# Those labels can be customized using I18n under the +helpers.submit+ key and using
|
2184
|
+
# <tt>%{model}</tt> for translation interpolation:
|
1799
2185
|
#
|
1800
2186
|
# en:
|
1801
2187
|
# helpers:
|
@@ -1803,7 +2189,7 @@ module ActionView
|
|
1803
2189
|
# create: "Create a %{model}"
|
1804
2190
|
# update: "Confirm changes to %{model}"
|
1805
2191
|
#
|
1806
|
-
# It also searches for a key specific
|
2192
|
+
# It also searches for a key specific to the given object:
|
1807
2193
|
#
|
1808
2194
|
# en:
|
1809
2195
|
# helpers:
|
@@ -1811,7 +2197,7 @@ module ActionView
|
|
1811
2197
|
# post:
|
1812
2198
|
# create: "Add %{model}"
|
1813
2199
|
#
|
1814
|
-
def submit(value=nil, options={})
|
2200
|
+
def submit(value = nil, options = {})
|
1815
2201
|
value, options = nil, value if value.is_a?(Hash)
|
1816
2202
|
value ||= submit_default_value
|
1817
2203
|
@template.submit_tag(value, options)
|
@@ -1824,11 +2210,11 @@ module ActionView
|
|
1824
2210
|
# <%= f.button %>
|
1825
2211
|
# <% end %>
|
1826
2212
|
#
|
1827
|
-
# In the example above, if
|
1828
|
-
# button label
|
2213
|
+
# In the example above, if <tt>@post</tt> is a new record, it will use "Create Post" as
|
2214
|
+
# button label; otherwise, it uses "Update Post".
|
1829
2215
|
#
|
1830
|
-
# Those labels can be customized using I18n
|
1831
|
-
# (the same as submit helper) and
|
2216
|
+
# Those labels can be customized using I18n under the +helpers.submit+ key
|
2217
|
+
# (the same as submit helper) and using <tt>%{model}</tt> for translation interpolation:
|
1832
2218
|
#
|
1833
2219
|
# en:
|
1834
2220
|
# helpers:
|
@@ -1836,7 +2222,7 @@ module ActionView
|
|
1836
2222
|
# create: "Create a %{model}"
|
1837
2223
|
# update: "Confirm changes to %{model}"
|
1838
2224
|
#
|
1839
|
-
# It also searches for a key specific
|
2225
|
+
# It also searches for a key specific to the given object:
|
1840
2226
|
#
|
1841
2227
|
# en:
|
1842
2228
|
# helpers:
|
@@ -1845,7 +2231,7 @@ module ActionView
|
|
1845
2231
|
# create: "Add %{model}"
|
1846
2232
|
#
|
1847
2233
|
# ==== Examples
|
1848
|
-
# button("Create
|
2234
|
+
# button("Create post")
|
1849
2235
|
# # => <button name='button' type='submit'>Create post</button>
|
1850
2236
|
#
|
1851
2237
|
# button do
|
@@ -1906,7 +2292,11 @@ module ActionView
|
|
1906
2292
|
explicit_child_index = options[:child_index]
|
1907
2293
|
output = ActiveSupport::SafeBuffer.new
|
1908
2294
|
association.each do |child|
|
1909
|
-
|
2295
|
+
if explicit_child_index
|
2296
|
+
options[:child_index] = explicit_child_index.call if explicit_child_index.respond_to?(:call)
|
2297
|
+
else
|
2298
|
+
options[:child_index] = nested_child_index(name)
|
2299
|
+
end
|
1910
2300
|
output << fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
|
1911
2301
|
end
|
1912
2302
|
output
|
@@ -1932,12 +2322,16 @@ module ActionView
|
|
1932
2322
|
@nested_child_index[name] ||= -1
|
1933
2323
|
@nested_child_index[name] += 1
|
1934
2324
|
end
|
2325
|
+
|
2326
|
+
def convert_to_legacy_options(options)
|
2327
|
+
if options.key?(:skip_id)
|
2328
|
+
options[:include_id] = !options.delete(:skip_id)
|
2329
|
+
end
|
2330
|
+
end
|
1935
2331
|
end
|
1936
2332
|
end
|
1937
2333
|
|
1938
2334
|
ActiveSupport.on_load(:action_view) do
|
1939
|
-
cattr_accessor
|
1940
|
-
::ActionView::Helpers::FormBuilder
|
1941
|
-
end
|
2335
|
+
cattr_accessor :default_form_builder, instance_writer: false, instance_reader: false, default: ::ActionView::Helpers::FormBuilder
|
1942
2336
|
end
|
1943
2337
|
end
|