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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +232 -186
- data/MIT-LICENSE +1 -2
- data/README.rdoc +9 -8
- data/lib/action_view/base.rb +115 -39
- data/lib/action_view/buffers.rb +18 -1
- data/lib/action_view/cache_expiry.rb +52 -0
- data/lib/action_view/context.rb +8 -12
- data/lib/action_view/dependency_tracker.rb +61 -21
- data/lib/action_view/digestor.rb +89 -85
- data/lib/action_view/flows.rb +11 -12
- data/lib/action_view/gem_version.rb +6 -4
- data/lib/action_view/helpers/active_model_helper.rb +16 -11
- data/lib/action_view/helpers/asset_tag_helper.rb +282 -83
- data/lib/action_view/helpers/asset_url_helper.rb +175 -69
- data/lib/action_view/helpers/atom_feed_helper.rb +20 -17
- data/lib/action_view/helpers/cache_helper.rb +107 -43
- data/lib/action_view/helpers/capture_helper.rb +20 -13
- data/lib/action_view/helpers/controller_helper.rb +15 -4
- data/lib/action_view/helpers/csp_helper.rb +26 -0
- data/lib/action_view/helpers/csrf_helper.rb +8 -6
- data/lib/action_view/helpers/date_helper.rb +232 -130
- data/lib/action_view/helpers/debug_helper.rb +7 -6
- data/lib/action_view/helpers/form_helper.rb +808 -146
- data/lib/action_view/helpers/form_options_helper.rb +124 -78
- data/lib/action_view/helpers/form_tag_helper.rb +120 -74
- data/lib/action_view/helpers/javascript_helper.rb +33 -17
- data/lib/action_view/helpers/number_helper.rb +87 -62
- data/lib/action_view/helpers/output_safety_helper.rb +36 -4
- data/lib/action_view/helpers/rendering_helper.rb +21 -10
- data/lib/action_view/helpers/sanitize_helper.rb +30 -31
- data/lib/action_view/helpers/tag_helper.rb +269 -68
- data/lib/action_view/helpers/tags/base.rb +141 -97
- data/lib/action_view/helpers/tags/check_box.rb +20 -19
- data/lib/action_view/helpers/tags/checkable.rb +4 -2
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +12 -34
- data/lib/action_view/helpers/tags/collection_helpers.rb +69 -36
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +6 -12
- data/lib/action_view/helpers/tags/collection_select.rb +4 -2
- data/lib/action_view/helpers/tags/color_field.rb +4 -3
- data/lib/action_view/helpers/tags/date_field.rb +3 -2
- data/lib/action_view/helpers/tags/date_select.rb +38 -37
- data/lib/action_view/helpers/tags/datetime_field.rb +4 -3
- data/lib/action_view/helpers/tags/datetime_local_field.rb +3 -2
- 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 +6 -0
- data/lib/action_view/helpers/tags/label.rb +7 -2
- data/lib/action_view/helpers/tags/month_field.rb +3 -2
- 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 -6
- 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 -10
- 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 -8
- data/lib/action_view/helpers/tags/time_field.rb +3 -2
- 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 +15 -16
- data/lib/action_view/helpers/tags/url_field.rb +2 -0
- data/lib/action_view/helpers/tags/week_field.rb +3 -2
- data/lib/action_view/helpers/tags.rb +3 -1
- data/lib/action_view/helpers/text_helper.rb +56 -38
- data/lib/action_view/helpers/translation_helper.rb +150 -68
- data/lib/action_view/helpers/url_helper.rb +284 -117
- data/lib/action_view/helpers.rb +5 -3
- data/lib/action_view/layouts.rb +68 -63
- data/lib/action_view/log_subscriber.rb +77 -10
- data/lib/action_view/lookup_context.rb +134 -91
- data/lib/action_view/model_naming.rb +3 -1
- data/lib/action_view/path_set.rb +26 -24
- data/lib/action_view/railtie.rb +62 -13
- data/lib/action_view/record_identifier.rb +53 -26
- data/lib/action_view/renderer/abstract_renderer.rb +151 -14
- data/lib/action_view/renderer/collection_renderer.rb +196 -0
- data/lib/action_view/renderer/object_renderer.rb +34 -0
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +102 -0
- data/lib/action_view/renderer/partial_renderer.rb +55 -303
- data/lib/action_view/renderer/renderer.rb +66 -9
- data/lib/action_view/renderer/streaming_template_renderer.rb +58 -54
- data/lib/action_view/renderer/template_renderer.rb +82 -73
- data/lib/action_view/rendering.rb +71 -45
- data/lib/action_view/routing_url_for.rb +34 -23
- data/lib/action_view/tasks/cache_digests.rake +25 -0
- data/lib/action_view/template/error.rb +44 -29
- data/lib/action_view/template/handlers/builder.rb +12 -13
- data/lib/action_view/template/handlers/erb/erubi.rb +89 -0
- data/lib/action_view/template/handlers/erb.rb +23 -89
- data/lib/action_view/template/handlers/html.rb +11 -0
- data/lib/action_view/template/handlers/raw.rb +4 -4
- data/lib/action_view/template/handlers.rb +12 -8
- data/lib/action_view/template/html.rb +10 -11
- data/lib/action_view/template/inline.rb +22 -0
- data/lib/action_view/template/raw_file.rb +25 -0
- data/lib/action_view/template/renderable.rb +24 -0
- data/lib/action_view/template/resolver.rb +263 -197
- data/lib/action_view/template/sources/file.rb +17 -0
- data/lib/action_view/template/sources.rb +13 -0
- data/lib/action_view/template/text.rb +8 -10
- data/lib/action_view/template/types.rb +18 -18
- data/lib/action_view/template.rb +108 -92
- data/lib/action_view/test_case.rb +66 -53
- data/lib/action_view/testing/resolvers.rb +24 -33
- data/lib/action_view/unbound_template.rb +31 -0
- data/lib/action_view/version.rb +3 -1
- data/lib/action_view/view_paths.rb +73 -58
- data/lib/action_view.rb +14 -8
- data/lib/assets/compiled/rails-ujs.js +746 -0
- metadata +42 -29
- data/lib/action_view/helpers/record_tag_helper.rb +0 -108
- data/lib/action_view/tasks/dependencies.rake +0 -23
@@ -1,22 +1,26 @@
|
|
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"
|
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
|
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.
|
73
|
+
# set in the form. That hash is ready to be passed to <tt>Person.new</tt>:
|
70
74
|
#
|
71
|
-
#
|
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
|
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.
|
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
|
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
|
#
|
@@ -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
|
318
|
-
#
|
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
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
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
|
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
|
-
#
|
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 (
|
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
|
-
# (
|
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>.'
|
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, :
|
847
|
-
# # => <input type="file" id="post_image" name="post[image]" multiple="
|
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 "
|
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
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
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
|
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
|
-
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1278
|
-
|
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
|
-
|
1310
|
-
|
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
|
-
|
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.
|
1324
|
-
#{selector.inspect}, #
|
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
|
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
|
-
#
|
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 (
|
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
|
-
# (
|
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
|
-
|
2182
|
+
object_name = object_name.to_s.delete_suffix("[]")
|
1593
2183
|
@auto_index
|
1594
2184
|
end
|
1595
2185
|
|
1596
|
-
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(:
|
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(:
|
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(:
|
2240
|
+
# label(:cost)
|
1635
2241
|
# # => <label for="post_cost">Total cost</label>
|
1636
2242
|
#
|
1637
|
-
# label(:
|
2243
|
+
# label(:title, "A short title")
|
1638
2244
|
# # => <label for="post_title">A short title</label>
|
1639
2245
|
#
|
1640
|
-
# 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(:
|
2249
|
+
# label(:privacy, "Public Post", value: "public")
|
1644
2250
|
# # => <label for="post_privacy_public">Public Post</label>
|
1645
2251
|
#
|
1646
|
-
# label(:
|
1647
|
-
#
|
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("
|
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("
|
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
|
-
#
|
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("
|
1722
|
-
# radio_button("
|
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
|
-
#
|
1727
|
-
# radio_button("
|
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
|
-
#
|
1741
|
-
#
|
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
|
-
#
|
1744
|
-
#
|
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
|
-
#
|
1747
|
-
#
|
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
|
-
#
|
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
|
-
#
|
1772
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
1795
|
-
# submit button label
|
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
|
1798
|
-
#
|
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
|
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
|
1828
|
-
# button label
|
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
|
1831
|
-
# (the same as submit helper) and
|
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
|
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
|
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
|
-
|
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
|
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
|
-
|
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.
|
2554
|
+
association = [association] if @object.public_send(association_name).respond_to?(:to_ary)
|
1901
2555
|
elsif !association.respond_to?(:to_ary)
|
1902
|
-
association = @object.
|
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
|
-
|
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
|
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
|