actionview 4.1.13 → 6.1.3.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 +5 -5
- data/CHANGELOG.md +181 -359
- data/MIT-LICENSE +1 -1
- data/README.rdoc +12 -6
- data/lib/action_view/base.rb +115 -43
- data/lib/action_view/buffers.rb +22 -4
- 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 -84
- data/lib/action_view/flows.rb +12 -13
- 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 +311 -105
- data/lib/action_view/helpers/asset_url_helper.rb +197 -80
- data/lib/action_view/helpers/atom_feed_helper.rb +20 -17
- data/lib/action_view/helpers/cache_helper.rb +109 -45
- data/lib/action_view/helpers/capture_helper.rb +20 -22
- 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 +245 -140
- data/lib/action_view/helpers/debug_helper.rb +14 -17
- data/lib/action_view/helpers/form_helper.rb +875 -148
- data/lib/action_view/helpers/form_options_helper.rb +128 -82
- data/lib/action_view/helpers/form_tag_helper.rb +253 -91
- data/lib/action_view/helpers/javascript_helper.rb +37 -15
- data/lib/action_view/helpers/number_helper.rb +100 -77
- data/lib/action_view/helpers/output_safety_helper.rb +42 -10
- data/lib/action_view/helpers/rendering_helper.rb +26 -15
- data/lib/action_view/helpers/sanitize_helper.rb +79 -164
- data/lib/action_view/helpers/tag_helper.rb +277 -64
- data/lib/action_view/helpers/tags/base.rb +143 -92
- 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 -30
- 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 +14 -5
- 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 +2 -0
- data/lib/action_view/helpers/tags/label.rb +41 -22
- 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 +24 -0
- 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 +3 -0
- 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 +7 -1
- data/lib/action_view/helpers/tags/text_field.rb +11 -7
- 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 +39 -0
- 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 +4 -1
- data/lib/action_view/helpers/text_helper.rb +80 -45
- data/lib/action_view/helpers/translation_helper.rb +148 -67
- data/lib/action_view/helpers/url_helper.rb +289 -147
- data/lib/action_view/helpers.rb +5 -3
- data/lib/action_view/layouts.rb +68 -63
- data/lib/action_view/log_subscriber.rb +80 -13
- data/lib/action_view/lookup_context.rb +137 -92
- data/lib/action_view/model_naming.rb +4 -2
- data/lib/action_view/path_set.rb +30 -16
- 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 +152 -13
- 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 +61 -261
- data/lib/action_view/renderer/renderer.rb +67 -6
- data/lib/action_view/renderer/streaming_template_renderer.rb +58 -54
- data/lib/action_view/renderer/template_renderer.rb +83 -75
- data/lib/action_view/rendering.rb +73 -46
- data/lib/action_view/routing_url_for.rb +54 -17
- 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 +22 -9
- 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 +267 -181
- 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 +109 -99
- data/lib/action_view/test_case.rb +73 -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 +74 -44
- data/lib/action_view.rb +14 -9
- data/lib/assets/compiled/rails-ujs.js +746 -0
- metadata +71 -26
- data/lib/action_view/helpers/record_tag_helper.rb +0 -108
- data/lib/action_view/tasks/dependencies.rake +0 -23
- data/lib/action_view/vendor/html-scanner/html/document.rb +0 -68
- data/lib/action_view/vendor/html-scanner/html/node.rb +0 -532
- data/lib/action_view/vendor/html-scanner/html/sanitizer.rb +0 -188
- data/lib/action_view/vendor/html-scanner/html/selector.rb +0 -830
- data/lib/action_view/vendor/html-scanner/html/tokenizer.rb +0 -107
- data/lib/action_view/vendor/html-scanner/html/version.rb +0 -11
- data/lib/action_view/vendor/html-scanner.rb +0 -20
@@ -1,38 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActionView
|
2
4
|
# = Action View Debug Helper
|
3
5
|
#
|
4
6
|
# Provides a set of methods for making it easier to debug Rails objects.
|
5
|
-
module Helpers
|
7
|
+
module Helpers #:nodoc:
|
6
8
|
module DebugHelper
|
7
|
-
|
8
9
|
include TagHelper
|
9
10
|
|
10
11
|
# Returns a YAML representation of +object+ wrapped with <pre> and </pre>.
|
11
12
|
# If the object cannot be converted to YAML using +to_yaml+, +inspect+ will be called instead.
|
12
13
|
# Useful for inspecting an object at the time of rendering.
|
13
14
|
#
|
14
|
-
# @user = User.new({ username: 'testing', password: 'xyz', age: 42})
|
15
|
+
# @user = User.new({ username: 'testing', password: 'xyz', age: 42})
|
15
16
|
# debug(@user)
|
16
17
|
# # =>
|
17
18
|
# <pre class='debug_dump'>--- !ruby/object:User
|
18
19
|
# attributes:
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
# created_at:
|
25
|
-
# attributes_cache: {}
|
26
|
-
#
|
27
|
-
# new_record: true
|
20
|
+
# updated_at:
|
21
|
+
# username: testing
|
22
|
+
# age: 42
|
23
|
+
# password: xyz
|
24
|
+
# created_at:
|
28
25
|
# </pre>
|
29
26
|
def debug(object)
|
30
|
-
Marshal
|
31
|
-
object = ERB::Util.html_escape(object.to_yaml)
|
32
|
-
content_tag(:pre, object, :
|
33
|
-
rescue
|
27
|
+
Marshal.dump(object)
|
28
|
+
object = ERB::Util.html_escape(object.to_yaml)
|
29
|
+
content_tag(:pre, object, class: "debug_dump")
|
30
|
+
rescue # errors from Marshal or YAML
|
34
31
|
# Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
|
35
|
-
content_tag(:code, object.inspect, :
|
32
|
+
content_tag(:code, object.inspect, class: "debug_dump")
|
36
33
|
end
|
37
34
|
end
|
38
35
|
end
|
@@ -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
|
@@ -51,9 +55,7 @@ module ActionView
|
|
51
55
|
# The HTML generated for this would be (modulus formatting):
|
52
56
|
#
|
53
57
|
# <form action="/people" class="new_person" id="new_person" method="post">
|
54
|
-
# <
|
55
|
-
# <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
|
56
|
-
# </div>
|
58
|
+
# <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
|
57
59
|
# <label for="person_first_name">First name</label>:
|
58
60
|
# <input id="person_first_name" name="person[first_name]" type="text" /><br />
|
59
61
|
#
|
@@ -68,9 +70,10 @@ module ActionView
|
|
68
70
|
#
|
69
71
|
# In particular, thanks to the conventions followed in the generated field names, the
|
70
72
|
# controller gets a nested hash <tt>params[:person]</tt> with the person attributes
|
71
|
-
# 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>:
|
72
74
|
#
|
73
|
-
#
|
75
|
+
# @person = Person.new(params[:person])
|
76
|
+
# if @person.save
|
74
77
|
# # success
|
75
78
|
# else
|
76
79
|
# # error handling
|
@@ -81,10 +84,8 @@ module ActionView
|
|
81
84
|
# the code above as is would yield instead:
|
82
85
|
#
|
83
86
|
# <form action="/people/256" class="edit_person" id="edit_person_256" method="post">
|
84
|
-
# <
|
85
|
-
#
|
86
|
-
# <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
|
87
|
-
# </div>
|
87
|
+
# <input name="_method" type="hidden" value="patch" />
|
88
|
+
# <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
|
88
89
|
# <label for="person_first_name">First name</label>:
|
89
90
|
# <input id="person_first_name" name="person[first_name]" type="text" value="John" /><br />
|
90
91
|
#
|
@@ -114,6 +115,9 @@ module ActionView
|
|
114
115
|
include FormTagHelper
|
115
116
|
include UrlHelper
|
116
117
|
include ModelNaming
|
118
|
+
include RecordIdentifier
|
119
|
+
|
120
|
+
attr_internal :default_form_builder
|
117
121
|
|
118
122
|
# Creates a form that allows the user to create or update the attributes
|
119
123
|
# of a specific model object.
|
@@ -142,7 +146,8 @@ module ActionView
|
|
142
146
|
# will get expanded to
|
143
147
|
#
|
144
148
|
# <%= text_field :person, :first_name %>
|
145
|
-
#
|
149
|
+
#
|
150
|
+
# which results in an HTML <tt><input></tt> tag whose +name+ attribute is
|
146
151
|
# <tt>person[first_name]</tt>. This means that when the form is submitted,
|
147
152
|
# the value entered by the user will be available in the controller as
|
148
153
|
# <tt>params[:person][:first_name]</tt>.
|
@@ -162,12 +167,28 @@ module ActionView
|
|
162
167
|
# So for example you may use a named route directly. When the model is
|
163
168
|
# represented by a string or symbol, as in the example above, if the
|
164
169
|
# <tt>:url</tt> option is not specified, by default the form will be
|
165
|
-
# sent back to the current
|
170
|
+
# sent back to the current URL (We will describe below an alternative
|
166
171
|
# resource-oriented usage of +form_for+ in which the URL does not need
|
167
172
|
# to be specified explicitly).
|
168
173
|
# * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of
|
169
174
|
# id attributes on form elements. The namespace attribute will be prefixed
|
170
175
|
# with underscore on the generated HTML id.
|
176
|
+
# * <tt>:method</tt> - The method to use when submitting the form, usually
|
177
|
+
# either "get" or "post". If "patch", "put", "delete", or another verb
|
178
|
+
# is used, a hidden input with name <tt>_method</tt> is added to
|
179
|
+
# simulate the verb over post.
|
180
|
+
# * <tt>:authenticity_token</tt> - Authenticity token to use in the form.
|
181
|
+
# Use only if you need to pass custom authenticity token string, or to
|
182
|
+
# not add authenticity_token field at all (by passing <tt>false</tt>).
|
183
|
+
# Remote forms may omit the embedded authenticity token by setting
|
184
|
+
# <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>.
|
185
|
+
# This is helpful when you're fragment-caching the form. Remote forms
|
186
|
+
# get the authenticity token from the <tt>meta</tt> tag, so embedding is
|
187
|
+
# unnecessary unless you support browsers without JavaScript.
|
188
|
+
# * <tt>:remote</tt> - If set to true, will allow the Unobtrusive
|
189
|
+
# JavaScript drivers to control the submit behavior.
|
190
|
+
# * <tt>:enforce_utf8</tt> - If set to false, a hidden input with name
|
191
|
+
# utf8 is not output.
|
171
192
|
# * <tt>:html</tt> - Optional HTML attributes for the form tag.
|
172
193
|
#
|
173
194
|
# Also note that +form_for+ doesn't create an exclusive scope. It's still
|
@@ -182,9 +203,9 @@ module ActionView
|
|
182
203
|
# <%= f.submit %>
|
183
204
|
# <% end %>
|
184
205
|
#
|
185
|
-
# This also works for the methods in
|
206
|
+
# This also works for the methods in FormOptionsHelper and DateHelper that
|
186
207
|
# are designed to work with an object as base, like
|
187
|
-
#
|
208
|
+
# FormOptionsHelper#collection_select and DateHelper#datetime_select.
|
188
209
|
#
|
189
210
|
# === #form_for with a model object
|
190
211
|
#
|
@@ -301,10 +322,8 @@ module ActionView
|
|
301
322
|
# remote: true
|
302
323
|
#
|
303
324
|
# in the options hash creates a form that will allow the unobtrusive JavaScript drivers to modify its
|
304
|
-
# behavior. The
|
305
|
-
#
|
306
|
-
# Even though it's using JavaScript to serialize the form elements, the form submission will work just like
|
307
|
-
# 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>).
|
308
327
|
#
|
309
328
|
# Example:
|
310
329
|
#
|
@@ -315,9 +334,7 @@ module ActionView
|
|
315
334
|
# The HTML generated for this would be:
|
316
335
|
#
|
317
336
|
# <form action='http://www.example.com' method='post' data-remote='true'>
|
318
|
-
# <
|
319
|
-
# <input name='_method' type='hidden' value='patch' />
|
320
|
-
# </div>
|
337
|
+
# <input name='_method' type='hidden' value='patch' />
|
321
338
|
# ...
|
322
339
|
# </form>
|
323
340
|
#
|
@@ -333,9 +350,7 @@ module ActionView
|
|
333
350
|
# The HTML generated for this would be:
|
334
351
|
#
|
335
352
|
# <form action='http://www.example.com' method='post' data-behavior='autosave' name='go'>
|
336
|
-
# <
|
337
|
-
# <input name='_method' type='hidden' value='patch' />
|
338
|
-
# </div>
|
353
|
+
# <input name='_method' type='hidden' value='patch' />
|
339
354
|
# ...
|
340
355
|
# </form>
|
341
356
|
#
|
@@ -401,13 +416,13 @@ module ActionView
|
|
401
416
|
#
|
402
417
|
# To set an authenticity token you need to pass an <tt>:authenticity_token</tt> parameter
|
403
418
|
#
|
404
|
-
# <%= 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| %>
|
405
420
|
# ...
|
406
421
|
# <% end %>
|
407
422
|
#
|
408
423
|
# If you don't want to an authenticity token field be rendered at all just pass <tt>false</tt>:
|
409
424
|
#
|
410
|
-
# <%= form_for @invoice, url: external_url, authenticity_token: false do |f|
|
425
|
+
# <%= form_for @invoice, url: external_url, authenticity_token: false do |f| %>
|
411
426
|
# ...
|
412
427
|
# <% end %>
|
413
428
|
def form_for(record, options = {}, &block)
|
@@ -428,13 +443,15 @@ module ActionView
|
|
428
443
|
html_options[:data] = options.delete(:data) if options.has_key?(:data)
|
429
444
|
html_options[:remote] = options.delete(:remote) if options.has_key?(:remote)
|
430
445
|
html_options[:method] = options.delete(:method) if options.has_key?(:method)
|
446
|
+
html_options[:enforce_utf8] = options.delete(:enforce_utf8) if options.has_key?(:enforce_utf8)
|
431
447
|
html_options[:authenticity_token] = options.delete(:authenticity_token)
|
432
448
|
|
433
449
|
builder = instantiate_builder(object_name, object, options)
|
434
450
|
output = capture(builder, &block)
|
435
451
|
html_options[:multipart] ||= builder.multipart?
|
436
452
|
|
437
|
-
|
453
|
+
html_options = html_options_for_form(options[:url] || {}, html_options)
|
454
|
+
form_tag_with_body(html_options, output)
|
438
455
|
end
|
439
456
|
|
440
457
|
def apply_form_for_options!(record, object, options) #:nodoc:
|
@@ -449,15 +466,303 @@ module ActionView
|
|
449
466
|
method: method
|
450
467
|
)
|
451
468
|
|
452
|
-
options[:url] ||=
|
469
|
+
options[:url] ||= if options.key?(:format)
|
470
|
+
polymorphic_path(record, format: options.delete(:format))
|
471
|
+
else
|
472
|
+
polymorphic_path(record, {})
|
473
|
+
end
|
453
474
|
end
|
454
475
|
private :apply_form_for_options!
|
455
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
|
+
|
456
761
|
# Creates a scope around a specific model object like form_for, but
|
457
762
|
# doesn't create the form tags themselves. This makes fields_for suitable
|
458
763
|
# for specifying additional model objects in the same form.
|
459
764
|
#
|
460
|
-
# Although the usage and purpose of +
|
765
|
+
# Although the usage and purpose of +fields_for+ is similar to +form_for+'s,
|
461
766
|
# its method signature is slightly different. Like +form_for+, it yields
|
462
767
|
# a FormBuilder object associated with a particular model object to a block,
|
463
768
|
# and within the block allows methods to be called on the builder to
|
@@ -477,7 +782,7 @@ module ActionView
|
|
477
782
|
# Admin? : <%= permission_fields.check_box :admin %>
|
478
783
|
# <% end %>
|
479
784
|
#
|
480
|
-
# <%=
|
785
|
+
# <%= person_form.submit %>
|
481
786
|
# <% end %>
|
482
787
|
#
|
483
788
|
# In this case, the checkbox field will be represented by an HTML +input+
|
@@ -510,9 +815,9 @@ module ActionView
|
|
510
815
|
# _class_ of the model object, e.g. if <tt>@person.permission</tt>, is
|
511
816
|
# of class +Permission+, the field will still be named <tt>permission[admin]</tt>.
|
512
817
|
#
|
513
|
-
# Note: This also works for the methods in
|
818
|
+
# Note: This also works for the methods in FormOptionsHelper and
|
514
819
|
# DateHelper that are designed to work with an object as base, like
|
515
|
-
#
|
820
|
+
# FormOptionsHelper#collection_select and DateHelper#datetime_select.
|
516
821
|
#
|
517
822
|
# === Nested Attributes Examples
|
518
823
|
#
|
@@ -578,7 +883,7 @@ module ActionView
|
|
578
883
|
#
|
579
884
|
# Now, when you use a form element with the <tt>_destroy</tt> parameter,
|
580
885
|
# with a value that evaluates to +true+, you will destroy the associated
|
581
|
-
# model (
|
886
|
+
# model (e.g. 1, '1', true, or 'true'):
|
582
887
|
#
|
583
888
|
# <%= form_for @person do |person_form| %>
|
584
889
|
# ...
|
@@ -667,7 +972,7 @@ module ActionView
|
|
667
972
|
# This will allow you to specify which models to destroy in the
|
668
973
|
# attributes hash by adding a form element for the <tt>_destroy</tt>
|
669
974
|
# parameter with a value that evaluates to +true+
|
670
|
-
# (
|
975
|
+
# (e.g. 1, '1', true, or 'true'):
|
671
976
|
#
|
672
977
|
# <%= form_for @person do |person_form| %>
|
673
978
|
# ...
|
@@ -699,6 +1004,63 @@ module ActionView
|
|
699
1004
|
capture(builder, &block)
|
700
1005
|
end
|
701
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
|
+
|
702
1064
|
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
|
703
1065
|
# assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
|
704
1066
|
# is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
|
@@ -743,9 +1105,20 @@ module ActionView
|
|
743
1105
|
# label(:post, :privacy, "Public Post", value: "public")
|
744
1106
|
# # => <label for="post_privacy_public">Public Post</label>
|
745
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
|
+
#
|
746
1118
|
# label(:post, :terms) do
|
747
|
-
# 'Accept <a href="/terms">Terms</a>.'
|
1119
|
+
# raw('Accept <a href="/terms">Terms</a>.')
|
748
1120
|
# end
|
1121
|
+
# # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
|
749
1122
|
def label(object_name, method, content_or_options = nil, options = nil, &block)
|
750
1123
|
Tags::Label.new(object_name, method, self, content_or_options, options).render(&block)
|
751
1124
|
end
|
@@ -762,6 +1135,9 @@ module ActionView
|
|
762
1135
|
# text_field(:post, :title, class: "create_input")
|
763
1136
|
# # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
|
764
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
|
+
#
|
765
1141
|
# text_field(:session, :user, onchange: "if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }")
|
766
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!'); }"/>
|
767
1143
|
#
|
@@ -827,8 +1203,8 @@ module ActionView
|
|
827
1203
|
# file_field(:user, :avatar)
|
828
1204
|
# # => <input type="file" id="user_avatar" name="user[avatar]" />
|
829
1205
|
#
|
830
|
-
# file_field(:post, :image, :
|
831
|
-
# # => <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" />
|
832
1208
|
#
|
833
1209
|
# file_field(:post, :attached, accept: 'text/html')
|
834
1210
|
# # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
|
@@ -839,7 +1215,7 @@ module ActionView
|
|
839
1215
|
# file_field(:attachment, :file, class: 'file_input')
|
840
1216
|
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
|
841
1217
|
def file_field(object_name, method, options = {})
|
842
|
-
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
|
843
1219
|
end
|
844
1220
|
|
845
1221
|
# Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
|
@@ -943,6 +1319,7 @@ module ActionView
|
|
943
1319
|
# # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
|
944
1320
|
# # <input type="radio" id="post_category_java" name="post[category]" value="java" />
|
945
1321
|
#
|
1322
|
+
# # Let's say that @user.receive_newsletter returns "no":
|
946
1323
|
# radio_button("user", "receive_newsletter", "yes")
|
947
1324
|
# radio_button("user", "receive_newsletter", "no")
|
948
1325
|
# # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
|
@@ -998,7 +1375,7 @@ module ActionView
|
|
998
1375
|
# date_field("user", "born_on")
|
999
1376
|
# # => <input id="user_born_on" name="user[born_on]" type="date" />
|
1000
1377
|
#
|
1001
|
-
# The default value is generated by trying to call "
|
1378
|
+
# The default value is generated by trying to call +strftime+ with "%Y-%m-%d"
|
1002
1379
|
# on the object's value, which makes it behave as expected for instances
|
1003
1380
|
# of DateTime and ActiveSupport::TimeWithZone. You can still override that
|
1004
1381
|
# by passing the "value" option explicitly, e.g.
|
@@ -1007,6 +1384,18 @@ module ActionView
|
|
1007
1384
|
# date_field("user", "born_on", value: "1984-05-12")
|
1008
1385
|
# # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-05-12" />
|
1009
1386
|
#
|
1387
|
+
# You can create values for the "min" and "max" attributes by passing
|
1388
|
+
# instances of Date or Time to the options hash.
|
1389
|
+
#
|
1390
|
+
# date_field("user", "born_on", min: Date.today)
|
1391
|
+
# # => <input id="user_born_on" name="user[born_on]" type="date" min="2014-05-20" />
|
1392
|
+
#
|
1393
|
+
# Alternatively, you can pass a String formatted as an ISO8601 date as the
|
1394
|
+
# values for "min" and "max."
|
1395
|
+
#
|
1396
|
+
# date_field("user", "born_on", min: "2014-05-20")
|
1397
|
+
# # => <input id="user_born_on" name="user[born_on]" type="date" min="2014-05-20" />
|
1398
|
+
#
|
1010
1399
|
def date_field(object_name, method, options = {})
|
1011
1400
|
Tags::DateField.new(object_name, method, self, options).render
|
1012
1401
|
end
|
@@ -1014,7 +1403,7 @@ module ActionView
|
|
1014
1403
|
# Returns a text_field of type "time".
|
1015
1404
|
#
|
1016
1405
|
# The default value is generated by trying to call +strftime+ with "%T.%L"
|
1017
|
-
# on the
|
1406
|
+
# on the object's value. It is still possible to override that
|
1018
1407
|
# by passing the "value" option.
|
1019
1408
|
#
|
1020
1409
|
# === Options
|
@@ -1024,30 +1413,25 @@ module ActionView
|
|
1024
1413
|
# time_field("task", "started_at")
|
1025
1414
|
# # => <input id="task_started_at" name="task[started_at]" type="time" />
|
1026
1415
|
#
|
1027
|
-
|
1028
|
-
|
1029
|
-
end
|
1030
|
-
|
1031
|
-
# Returns a text_field of type "datetime".
|
1416
|
+
# You can create values for the "min" and "max" attributes by passing
|
1417
|
+
# instances of Date or Time to the options hash.
|
1032
1418
|
#
|
1033
|
-
#
|
1034
|
-
# # => <input id="
|
1419
|
+
# time_field("task", "started_at", min: Time.now)
|
1420
|
+
# # => <input id="task_started_at" name="task[started_at]" type="time" min="01:00:00.000" />
|
1035
1421
|
#
|
1036
|
-
#
|
1037
|
-
#
|
1038
|
-
# of DateTime and ActiveSupport::TimeWithZone.
|
1422
|
+
# Alternatively, you can pass a String formatted as an ISO8601 time as the
|
1423
|
+
# values for "min" and "max."
|
1039
1424
|
#
|
1040
|
-
#
|
1041
|
-
#
|
1042
|
-
# # => <input id="user_born_on" name="user[born_on]" type="datetime" value="1984-01-12T00:00:00.000+0000" />
|
1425
|
+
# time_field("task", "started_at", min: "01:00:00")
|
1426
|
+
# # => <input id="task_started_at" name="task[started_at]" type="time" min="01:00:00.000" />
|
1043
1427
|
#
|
1044
|
-
def
|
1045
|
-
Tags::
|
1428
|
+
def time_field(object_name, method, options = {})
|
1429
|
+
Tags::TimeField.new(object_name, method, self, options).render
|
1046
1430
|
end
|
1047
1431
|
|
1048
1432
|
# Returns a text_field of type "datetime-local".
|
1049
1433
|
#
|
1050
|
-
#
|
1434
|
+
# datetime_field("user", "born_on")
|
1051
1435
|
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" />
|
1052
1436
|
#
|
1053
1437
|
# The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T"
|
@@ -1055,13 +1439,27 @@ module ActionView
|
|
1055
1439
|
# of DateTime and ActiveSupport::TimeWithZone.
|
1056
1440
|
#
|
1057
1441
|
# @user.born_on = Date.new(1984, 1, 12)
|
1058
|
-
#
|
1442
|
+
# datetime_field("user", "born_on")
|
1059
1443
|
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" value="1984-01-12T00:00:00" />
|
1060
1444
|
#
|
1061
|
-
|
1445
|
+
# You can create values for the "min" and "max" attributes by passing
|
1446
|
+
# instances of Date or Time to the options hash.
|
1447
|
+
#
|
1448
|
+
# datetime_field("user", "born_on", min: Date.today)
|
1449
|
+
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" />
|
1450
|
+
#
|
1451
|
+
# Alternatively, you can pass a String formatted as an ISO8601 datetime as
|
1452
|
+
# the values for "min" and "max."
|
1453
|
+
#
|
1454
|
+
# datetime_field("user", "born_on", min: "2014-05-20T00:00:00")
|
1455
|
+
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" />
|
1456
|
+
#
|
1457
|
+
def datetime_field(object_name, method, options = {})
|
1062
1458
|
Tags::DatetimeLocalField.new(object_name, method, self, options).render
|
1063
1459
|
end
|
1064
1460
|
|
1461
|
+
alias datetime_local_field datetime_field
|
1462
|
+
|
1065
1463
|
# Returns a text_field of type "month".
|
1066
1464
|
#
|
1067
1465
|
# month_field("user", "born_on")
|
@@ -1131,6 +1529,34 @@ module ActionView
|
|
1131
1529
|
end
|
1132
1530
|
|
1133
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
|
1134
1560
|
|
1135
1561
|
def instantiate_builder(record_name, record_object, options)
|
1136
1562
|
case record_name
|
@@ -1139,7 +1565,7 @@ module ActionView
|
|
1139
1565
|
object_name = record_name
|
1140
1566
|
else
|
1141
1567
|
object = record_name
|
1142
|
-
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
|
1143
1569
|
end
|
1144
1570
|
|
1145
1571
|
builder = options[:builder] || default_form_builder_class
|
@@ -1147,7 +1573,7 @@ module ActionView
|
|
1147
1573
|
end
|
1148
1574
|
|
1149
1575
|
def default_form_builder_class
|
1150
|
-
builder = ActionView::Base.default_form_builder
|
1576
|
+
builder = default_form_builder || ActionView::Base.default_form_builder
|
1151
1577
|
builder.respond_to?(:constantize) ? builder.constantize : builder
|
1152
1578
|
end
|
1153
1579
|
end
|
@@ -1162,10 +1588,10 @@ module ActionView
|
|
1162
1588
|
# Admin: <%= person_form.check_box :admin %>
|
1163
1589
|
# <% end %>
|
1164
1590
|
#
|
1165
|
-
# In the above block,
|
1591
|
+
# In the above block, a +FormBuilder+ object is yielded as the
|
1166
1592
|
# +person_form+ variable. This allows you to generate the +text_field+
|
1167
1593
|
# and +check_box+ fields by specifying their eponymous methods, which
|
1168
|
-
# modify the underlying template and associates the
|
1594
|
+
# modify the underlying template and associates the <tt>@person</tt> model object
|
1169
1595
|
# with the form.
|
1170
1596
|
#
|
1171
1597
|
# The +FormBuilder+ object can be thought of as serving as a proxy for the
|
@@ -1183,10 +1609,11 @@ module ActionView
|
|
1183
1609
|
# )
|
1184
1610
|
# )
|
1185
1611
|
# end
|
1612
|
+
# end
|
1186
1613
|
#
|
1187
1614
|
# The above code creates a new method +div_radio_button+ which wraps a div
|
1188
|
-
# around the
|
1189
|
-
# must
|
1615
|
+
# around the new radio button. Note that when options are passed in, you
|
1616
|
+
# must call +objectify_options+ in order for the model object to get
|
1190
1617
|
# correctly passed to the method. If +objectify_options+ is not called,
|
1191
1618
|
# then the newly created helper will not be linked back to the model.
|
1192
1619
|
#
|
@@ -1203,14 +1630,15 @@ module ActionView
|
|
1203
1630
|
include ModelNaming
|
1204
1631
|
|
1205
1632
|
# The methods which wrap a form helper call.
|
1206
|
-
class_attribute :field_helpers
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
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
|
+
]
|
1214
1642
|
|
1215
1643
|
attr_accessor :object_name, :object, :options
|
1216
1644
|
|
@@ -1226,7 +1654,7 @@ module ActionView
|
|
1226
1654
|
end
|
1227
1655
|
|
1228
1656
|
def self._to_partial_path
|
1229
|
-
@_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/,
|
1657
|
+
@_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, "")
|
1230
1658
|
end
|
1231
1659
|
|
1232
1660
|
def to_partial_path
|
@@ -1240,23 +1668,249 @@ module ActionView
|
|
1240
1668
|
def initialize(object_name, object, template, options)
|
1241
1669
|
@nested_child_index = {}
|
1242
1670
|
@object_name, @object, @template, @options = object_name, object, template, options
|
1243
|
-
@default_options = @options ? @options.slice(:index, :namespace) : {}
|
1244
|
-
|
1245
|
-
|
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)
|
1246
1678
|
@auto_index = object.to_param
|
1247
1679
|
else
|
1248
1680
|
raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
|
1249
1681
|
end
|
1250
1682
|
end
|
1683
|
+
|
1251
1684
|
@multipart = nil
|
1252
1685
|
@index = options[:index] || options[:child_index]
|
1253
1686
|
end
|
1254
1687
|
|
1255
|
-
|
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|
|
1256
1910
|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
1257
1911
|
def #{selector}(method, options = {}) # def text_field(method, options = {})
|
1258
|
-
@template.
|
1259
|
-
#{selector.inspect}, #
|
1912
|
+
@template.public_send( # @template.public_send(
|
1913
|
+
#{selector.inspect}, # :text_field,
|
1260
1914
|
@object_name, # @object_name,
|
1261
1915
|
method, # method,
|
1262
1916
|
objectify_options(options)) # objectify_options(options))
|
@@ -1268,7 +1922,7 @@ module ActionView
|
|
1268
1922
|
# doesn't create the form tags themselves. This makes fields_for suitable
|
1269
1923
|
# for specifying additional model objects in the same form.
|
1270
1924
|
#
|
1271
|
-
# Although the usage and purpose of +
|
1925
|
+
# Although the usage and purpose of +fields_for+ is similar to +form_for+'s,
|
1272
1926
|
# its method signature is slightly different. Like +form_for+, it yields
|
1273
1927
|
# a FormBuilder object associated with a particular model object to a block,
|
1274
1928
|
# and within the block allows methods to be called on the builder to
|
@@ -1321,9 +1975,9 @@ module ActionView
|
|
1321
1975
|
# _class_ of the model object, e.g. if <tt>@person.permission</tt>, is
|
1322
1976
|
# of class +Permission+, the field will still be named <tt>permission[admin]</tt>.
|
1323
1977
|
#
|
1324
|
-
# Note: This also works for the methods in
|
1978
|
+
# Note: This also works for the methods in FormOptionsHelper and
|
1325
1979
|
# DateHelper that are designed to work with an object as base, like
|
1326
|
-
#
|
1980
|
+
# FormOptionsHelper#collection_select and DateHelper#datetime_select.
|
1327
1981
|
#
|
1328
1982
|
# === Nested Attributes Examples
|
1329
1983
|
#
|
@@ -1389,7 +2043,7 @@ module ActionView
|
|
1389
2043
|
#
|
1390
2044
|
# Now, when you use a form element with the <tt>_destroy</tt> parameter,
|
1391
2045
|
# with a value that evaluates to +true+, you will destroy the associated
|
1392
|
-
# model (
|
2046
|
+
# model (e.g. 1, '1', true, or 'true'):
|
1393
2047
|
#
|
1394
2048
|
# <%= form_for @person do |person_form| %>
|
1395
2049
|
# ...
|
@@ -1478,7 +2132,7 @@ module ActionView
|
|
1478
2132
|
# This will allow you to specify which models to destroy in the
|
1479
2133
|
# attributes hash by adding a form element for the <tt>_destroy</tt>
|
1480
2134
|
# parameter with a value that evaluates to +true+
|
1481
|
-
# (
|
2135
|
+
# (e.g. 1, '1', true, or 'true'):
|
1482
2136
|
#
|
1483
2137
|
# <%= form_for @person do |person_form| %>
|
1484
2138
|
# ...
|
@@ -1521,19 +2175,36 @@ module ActionView
|
|
1521
2175
|
record_name = model_name_from_record_or_class(record_object).param_key
|
1522
2176
|
end
|
1523
2177
|
|
2178
|
+
object_name = @object_name
|
1524
2179
|
index = if options.has_key?(:index)
|
1525
2180
|
options[:index]
|
1526
2181
|
elsif defined?(@auto_index)
|
1527
|
-
|
2182
|
+
object_name = object_name.to_s.delete_suffix("[]")
|
1528
2183
|
@auto_index
|
1529
2184
|
end
|
1530
2185
|
|
1531
|
-
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
|
1532
2193
|
fields_options[:child_index] = index
|
1533
2194
|
|
1534
2195
|
@template.fields_for(record_name, record_object, fields_options, &block)
|
1535
2196
|
end
|
1536
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
|
+
|
1537
2208
|
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
|
1538
2209
|
# assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
|
1539
2210
|
# is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
|
@@ -1542,7 +2213,7 @@ module ActionView
|
|
1542
2213
|
# target labels for radio_button tags (where the value is used in the ID of the input tag).
|
1543
2214
|
#
|
1544
2215
|
# ==== Examples
|
1545
|
-
# label(:
|
2216
|
+
# label(:title)
|
1546
2217
|
# # => <label for="post_title">Title</label>
|
1547
2218
|
#
|
1548
2219
|
# You can localize your labels based on model and attribute names.
|
@@ -1555,7 +2226,7 @@ module ActionView
|
|
1555
2226
|
#
|
1556
2227
|
# Which then will result in
|
1557
2228
|
#
|
1558
|
-
# label(:
|
2229
|
+
# label(:body)
|
1559
2230
|
# # => <label for="post_body">Write your entire text here</label>
|
1560
2231
|
#
|
1561
2232
|
# Localization can also be based purely on the translation of the attribute-name
|
@@ -1566,21 +2237,40 @@ module ActionView
|
|
1566
2237
|
# post:
|
1567
2238
|
# cost: "Total cost"
|
1568
2239
|
#
|
1569
|
-
# label(:
|
2240
|
+
# label(:cost)
|
1570
2241
|
# # => <label for="post_cost">Total cost</label>
|
1571
2242
|
#
|
1572
|
-
# label(:
|
2243
|
+
# label(:title, "A short title")
|
1573
2244
|
# # => <label for="post_title">A short title</label>
|
1574
2245
|
#
|
1575
|
-
# label(:
|
2246
|
+
# label(:title, "A short title", class: "title_label")
|
1576
2247
|
# # => <label for="post_title" class="title_label">A short title</label>
|
1577
2248
|
#
|
1578
|
-
# label(:
|
2249
|
+
# label(:privacy, "Public Post", value: "public")
|
1579
2250
|
# # => <label for="post_privacy_public">Public Post</label>
|
1580
2251
|
#
|
1581
|
-
# label(:
|
1582
|
-
#
|
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
|
+
# ])
|
1583
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>
|
1584
2274
|
def label(method, text = nil, options = {}, &block)
|
1585
2275
|
@template.label(@object_name, method, text, objectify_options(options), &block)
|
1586
2276
|
end
|
@@ -1629,16 +2319,17 @@ module ActionView
|
|
1629
2319
|
# hashes instead of arrays.
|
1630
2320
|
#
|
1631
2321
|
# # Let's say that @post.validated? is 1:
|
1632
|
-
# check_box("
|
2322
|
+
# check_box("validated")
|
1633
2323
|
# # => <input name="post[validated]" type="hidden" value="0" />
|
1634
2324
|
# # <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" />
|
1635
2325
|
#
|
1636
2326
|
# # Let's say that @puppy.gooddog is "no":
|
1637
|
-
# check_box("
|
2327
|
+
# check_box("gooddog", {}, "yes", "no")
|
1638
2328
|
# # => <input name="puppy[gooddog]" type="hidden" value="no" />
|
1639
2329
|
# # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
|
1640
2330
|
#
|
1641
|
-
#
|
2331
|
+
# # Let's say that @eula.accepted is "no":
|
2332
|
+
# check_box("accepted", { class: 'eula_check' }, "yes", "no")
|
1642
2333
|
# # => <input name="eula[accepted]" type="hidden" value="no" />
|
1643
2334
|
# # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
|
1644
2335
|
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
|
@@ -1653,13 +2344,14 @@ module ActionView
|
|
1653
2344
|
# +options+ hash. You may pass HTML options there as well.
|
1654
2345
|
#
|
1655
2346
|
# # Let's say that @post.category returns "rails":
|
1656
|
-
# radio_button("
|
1657
|
-
# radio_button("
|
2347
|
+
# radio_button("category", "rails")
|
2348
|
+
# radio_button("category", "java")
|
1658
2349
|
# # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
|
1659
2350
|
# # <input type="radio" id="post_category_java" name="post[category]" value="java" />
|
1660
2351
|
#
|
1661
|
-
#
|
1662
|
-
# radio_button("
|
2352
|
+
# # Let's say that @user.receive_newsletter returns "no":
|
2353
|
+
# radio_button("receive_newsletter", "yes")
|
2354
|
+
# radio_button("receive_newsletter", "no")
|
1663
2355
|
# # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
|
1664
2356
|
# # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
|
1665
2357
|
def radio_button(method, tag_value, options = {})
|
@@ -1672,14 +2364,17 @@ module ActionView
|
|
1672
2364
|
# shown.
|
1673
2365
|
#
|
1674
2366
|
# ==== Examples
|
1675
|
-
#
|
1676
|
-
#
|
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" />
|
1677
2370
|
#
|
1678
|
-
#
|
1679
|
-
#
|
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" />
|
1680
2374
|
#
|
1681
|
-
#
|
1682
|
-
#
|
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" />
|
1683
2378
|
#
|
1684
2379
|
def hidden_field(method, options = {})
|
1685
2380
|
@emitted_hidden_id = true if method == :id
|
@@ -1700,19 +2395,24 @@ module ActionView
|
|
1700
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.
|
1701
2396
|
#
|
1702
2397
|
# ==== Examples
|
1703
|
-
#
|
2398
|
+
# # Let's say that @user has avatar:
|
2399
|
+
# file_field(:avatar)
|
1704
2400
|
# # => <input type="file" id="user_avatar" name="user[avatar]" />
|
1705
2401
|
#
|
1706
|
-
#
|
1707
|
-
#
|
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" />
|
1708
2405
|
#
|
1709
|
-
#
|
2406
|
+
# # Let's say that @post has attached:
|
2407
|
+
# file_field(:attached, accept: 'text/html')
|
1710
2408
|
# # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
|
1711
2409
|
#
|
1712
|
-
#
|
2410
|
+
# # Let's say that @post has image:
|
2411
|
+
# file_field(:image, accept: 'image/png,image/gif,image/jpeg')
|
1713
2412
|
# # => <input type="file" id="post_image" name="post[image]" accept="image/png,image/gif,image/jpeg" />
|
1714
2413
|
#
|
1715
|
-
#
|
2414
|
+
# # Let's say that @attachment has file:
|
2415
|
+
# file_field(:file, class: 'file_input')
|
1716
2416
|
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
|
1717
2417
|
def file_field(method, options = {})
|
1718
2418
|
self.multipart = true
|
@@ -1726,11 +2426,11 @@ module ActionView
|
|
1726
2426
|
# <%= f.submit %>
|
1727
2427
|
# <% end %>
|
1728
2428
|
#
|
1729
|
-
# In the example above, if
|
1730
|
-
# 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".
|
1731
2431
|
#
|
1732
|
-
# Those labels can be customized using I18n
|
1733
|
-
#
|
2432
|
+
# Those labels can be customized using I18n under the +helpers.submit+ key and using
|
2433
|
+
# <tt>%{model}</tt> for translation interpolation:
|
1734
2434
|
#
|
1735
2435
|
# en:
|
1736
2436
|
# helpers:
|
@@ -1738,7 +2438,7 @@ module ActionView
|
|
1738
2438
|
# create: "Create a %{model}"
|
1739
2439
|
# update: "Confirm changes to %{model}"
|
1740
2440
|
#
|
1741
|
-
# It also searches for a key specific
|
2441
|
+
# It also searches for a key specific to the given object:
|
1742
2442
|
#
|
1743
2443
|
# en:
|
1744
2444
|
# helpers:
|
@@ -1746,7 +2446,7 @@ module ActionView
|
|
1746
2446
|
# post:
|
1747
2447
|
# create: "Add %{model}"
|
1748
2448
|
#
|
1749
|
-
def submit(value=nil, options={})
|
2449
|
+
def submit(value = nil, options = {})
|
1750
2450
|
value, options = nil, value if value.is_a?(Hash)
|
1751
2451
|
value ||= submit_default_value
|
1752
2452
|
@template.submit_tag(value, options)
|
@@ -1759,11 +2459,11 @@ module ActionView
|
|
1759
2459
|
# <%= f.button %>
|
1760
2460
|
# <% end %>
|
1761
2461
|
#
|
1762
|
-
# In the example above, if
|
1763
|
-
# 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".
|
1764
2464
|
#
|
1765
|
-
# Those labels can be customized using I18n
|
1766
|
-
# (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:
|
1767
2467
|
#
|
1768
2468
|
# en:
|
1769
2469
|
# helpers:
|
@@ -1771,7 +2471,7 @@ module ActionView
|
|
1771
2471
|
# create: "Create a %{model}"
|
1772
2472
|
# update: "Confirm changes to %{model}"
|
1773
2473
|
#
|
1774
|
-
# It also searches for a key specific
|
2474
|
+
# It also searches for a key specific to the given object:
|
1775
2475
|
#
|
1776
2476
|
# en:
|
1777
2477
|
# helpers:
|
@@ -1780,7 +2480,7 @@ module ActionView
|
|
1780
2480
|
# create: "Add %{model}"
|
1781
2481
|
#
|
1782
2482
|
# ==== Examples
|
1783
|
-
# button("Create
|
2483
|
+
# button("Create post")
|
1784
2484
|
# # => <button name='button' type='submit'>Create post</button>
|
1785
2485
|
#
|
1786
2486
|
# button do
|
@@ -1790,33 +2490,52 @@ module ActionView
|
|
1790
2490
|
# # <strong>Ask me!</strong>
|
1791
2491
|
# # </button>
|
1792
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
|
+
#
|
1793
2500
|
def button(value = nil, options = {}, &block)
|
1794
2501
|
value, options = nil, value if value.is_a?(Hash)
|
1795
2502
|
value ||= submit_default_value
|
1796
|
-
|
2503
|
+
|
2504
|
+
if block_given?
|
2505
|
+
value = @template.capture { yield(value) }
|
2506
|
+
end
|
2507
|
+
|
2508
|
+
@template.button_tag(value, options)
|
1797
2509
|
end
|
1798
2510
|
|
1799
|
-
def emitted_hidden_id?
|
2511
|
+
def emitted_hidden_id? # :nodoc:
|
1800
2512
|
@emitted_hidden_id ||= nil
|
1801
2513
|
end
|
1802
2514
|
|
1803
2515
|
private
|
1804
2516
|
def objectify_options(options)
|
1805
|
-
@default_options.merge(options
|
2517
|
+
result = @default_options.merge(options)
|
2518
|
+
result[:object] = @object
|
2519
|
+
result
|
1806
2520
|
end
|
1807
2521
|
|
1808
2522
|
def submit_default_value
|
1809
2523
|
object = convert_to_model(@object)
|
1810
2524
|
key = object ? (object.persisted? ? :update : :create) : :submit
|
1811
2525
|
|
1812
|
-
model = if object.
|
1813
|
-
object.
|
2526
|
+
model = if object.respond_to?(:model_name)
|
2527
|
+
object.model_name.human
|
1814
2528
|
else
|
1815
2529
|
@object_name.to_s.humanize
|
1816
2530
|
end
|
1817
2531
|
|
1818
2532
|
defaults = []
|
1819
|
-
|
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
|
1820
2539
|
defaults << :"helpers.submit.#{key}"
|
1821
2540
|
defaults << "#{key.to_s.humanize} #{model}"
|
1822
2541
|
|
@@ -1832,16 +2551,20 @@ module ActionView
|
|
1832
2551
|
association = convert_to_model(association)
|
1833
2552
|
|
1834
2553
|
if association.respond_to?(:persisted?)
|
1835
|
-
association = [association] if @object.
|
2554
|
+
association = [association] if @object.public_send(association_name).respond_to?(:to_ary)
|
1836
2555
|
elsif !association.respond_to?(:to_ary)
|
1837
|
-
association = @object.
|
2556
|
+
association = @object.public_send(association_name)
|
1838
2557
|
end
|
1839
2558
|
|
1840
2559
|
if association.respond_to?(:to_ary)
|
1841
2560
|
explicit_child_index = options[:child_index]
|
1842
2561
|
output = ActiveSupport::SafeBuffer.new
|
1843
2562
|
association.each do |child|
|
1844
|
-
|
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
|
1845
2568
|
output << fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
|
1846
2569
|
end
|
1847
2570
|
output
|
@@ -1867,12 +2590,16 @@ module ActionView
|
|
1867
2590
|
@nested_child_index[name] ||= -1
|
1868
2591
|
@nested_child_index[name] += 1
|
1869
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
|
1870
2599
|
end
|
1871
2600
|
end
|
1872
2601
|
|
1873
2602
|
ActiveSupport.on_load(:action_view) do
|
1874
|
-
cattr_accessor
|
1875
|
-
::ActionView::Helpers::FormBuilder
|
1876
|
-
end
|
2603
|
+
cattr_accessor :default_form_builder, instance_writer: false, instance_reader: false, default: ::ActionView::Helpers::FormBuilder
|
1877
2604
|
end
|
1878
2605
|
end
|