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.

Files changed (124) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +181 -359
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +12 -6
  5. data/lib/action_view/base.rb +115 -43
  6. data/lib/action_view/buffers.rb +22 -4
  7. data/lib/action_view/cache_expiry.rb +52 -0
  8. data/lib/action_view/context.rb +8 -12
  9. data/lib/action_view/dependency_tracker.rb +61 -21
  10. data/lib/action_view/digestor.rb +89 -84
  11. data/lib/action_view/flows.rb +12 -13
  12. data/lib/action_view/gem_version.rb +6 -4
  13. data/lib/action_view/helpers/active_model_helper.rb +16 -11
  14. data/lib/action_view/helpers/asset_tag_helper.rb +311 -105
  15. data/lib/action_view/helpers/asset_url_helper.rb +197 -80
  16. data/lib/action_view/helpers/atom_feed_helper.rb +20 -17
  17. data/lib/action_view/helpers/cache_helper.rb +109 -45
  18. data/lib/action_view/helpers/capture_helper.rb +20 -22
  19. data/lib/action_view/helpers/controller_helper.rb +15 -4
  20. data/lib/action_view/helpers/csp_helper.rb +26 -0
  21. data/lib/action_view/helpers/csrf_helper.rb +8 -6
  22. data/lib/action_view/helpers/date_helper.rb +245 -140
  23. data/lib/action_view/helpers/debug_helper.rb +14 -17
  24. data/lib/action_view/helpers/form_helper.rb +875 -148
  25. data/lib/action_view/helpers/form_options_helper.rb +128 -82
  26. data/lib/action_view/helpers/form_tag_helper.rb +253 -91
  27. data/lib/action_view/helpers/javascript_helper.rb +37 -15
  28. data/lib/action_view/helpers/number_helper.rb +100 -77
  29. data/lib/action_view/helpers/output_safety_helper.rb +42 -10
  30. data/lib/action_view/helpers/rendering_helper.rb +26 -15
  31. data/lib/action_view/helpers/sanitize_helper.rb +79 -164
  32. data/lib/action_view/helpers/tag_helper.rb +277 -64
  33. data/lib/action_view/helpers/tags/base.rb +143 -92
  34. data/lib/action_view/helpers/tags/check_box.rb +20 -19
  35. data/lib/action_view/helpers/tags/checkable.rb +4 -2
  36. data/lib/action_view/helpers/tags/collection_check_boxes.rb +12 -30
  37. data/lib/action_view/helpers/tags/collection_helpers.rb +69 -36
  38. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +6 -12
  39. data/lib/action_view/helpers/tags/collection_select.rb +4 -2
  40. data/lib/action_view/helpers/tags/color_field.rb +4 -3
  41. data/lib/action_view/helpers/tags/date_field.rb +3 -2
  42. data/lib/action_view/helpers/tags/date_select.rb +38 -37
  43. data/lib/action_view/helpers/tags/datetime_field.rb +14 -5
  44. data/lib/action_view/helpers/tags/datetime_local_field.rb +3 -2
  45. data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
  46. data/lib/action_view/helpers/tags/email_field.rb +2 -0
  47. data/lib/action_view/helpers/tags/file_field.rb +2 -0
  48. data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
  49. data/lib/action_view/helpers/tags/hidden_field.rb +2 -0
  50. data/lib/action_view/helpers/tags/label.rb +41 -22
  51. data/lib/action_view/helpers/tags/month_field.rb +3 -2
  52. data/lib/action_view/helpers/tags/number_field.rb +2 -0
  53. data/lib/action_view/helpers/tags/password_field.rb +3 -1
  54. data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
  55. data/lib/action_view/helpers/tags/radio_button.rb +7 -6
  56. data/lib/action_view/helpers/tags/range_field.rb +2 -0
  57. data/lib/action_view/helpers/tags/search_field.rb +3 -0
  58. data/lib/action_view/helpers/tags/select.rb +11 -10
  59. data/lib/action_view/helpers/tags/tel_field.rb +2 -0
  60. data/lib/action_view/helpers/tags/text_area.rb +7 -1
  61. data/lib/action_view/helpers/tags/text_field.rb +11 -7
  62. data/lib/action_view/helpers/tags/time_field.rb +3 -2
  63. data/lib/action_view/helpers/tags/time_select.rb +2 -0
  64. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
  65. data/lib/action_view/helpers/tags/translator.rb +39 -0
  66. data/lib/action_view/helpers/tags/url_field.rb +2 -0
  67. data/lib/action_view/helpers/tags/week_field.rb +3 -2
  68. data/lib/action_view/helpers/tags.rb +4 -1
  69. data/lib/action_view/helpers/text_helper.rb +80 -45
  70. data/lib/action_view/helpers/translation_helper.rb +148 -67
  71. data/lib/action_view/helpers/url_helper.rb +289 -147
  72. data/lib/action_view/helpers.rb +5 -3
  73. data/lib/action_view/layouts.rb +68 -63
  74. data/lib/action_view/log_subscriber.rb +80 -13
  75. data/lib/action_view/lookup_context.rb +137 -92
  76. data/lib/action_view/model_naming.rb +4 -2
  77. data/lib/action_view/path_set.rb +30 -16
  78. data/lib/action_view/railtie.rb +62 -13
  79. data/lib/action_view/record_identifier.rb +53 -26
  80. data/lib/action_view/renderer/abstract_renderer.rb +152 -13
  81. data/lib/action_view/renderer/collection_renderer.rb +196 -0
  82. data/lib/action_view/renderer/object_renderer.rb +34 -0
  83. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +102 -0
  84. data/lib/action_view/renderer/partial_renderer.rb +61 -261
  85. data/lib/action_view/renderer/renderer.rb +67 -6
  86. data/lib/action_view/renderer/streaming_template_renderer.rb +58 -54
  87. data/lib/action_view/renderer/template_renderer.rb +83 -75
  88. data/lib/action_view/rendering.rb +73 -46
  89. data/lib/action_view/routing_url_for.rb +54 -17
  90. data/lib/action_view/tasks/cache_digests.rake +25 -0
  91. data/lib/action_view/template/error.rb +44 -29
  92. data/lib/action_view/template/handlers/builder.rb +12 -13
  93. data/lib/action_view/template/handlers/erb/erubi.rb +89 -0
  94. data/lib/action_view/template/handlers/erb.rb +23 -89
  95. data/lib/action_view/template/handlers/html.rb +11 -0
  96. data/lib/action_view/template/handlers/raw.rb +4 -4
  97. data/lib/action_view/template/handlers.rb +22 -9
  98. data/lib/action_view/template/html.rb +10 -11
  99. data/lib/action_view/template/inline.rb +22 -0
  100. data/lib/action_view/template/raw_file.rb +25 -0
  101. data/lib/action_view/template/renderable.rb +24 -0
  102. data/lib/action_view/template/resolver.rb +267 -181
  103. data/lib/action_view/template/sources/file.rb +17 -0
  104. data/lib/action_view/template/sources.rb +13 -0
  105. data/lib/action_view/template/text.rb +8 -10
  106. data/lib/action_view/template/types.rb +18 -18
  107. data/lib/action_view/template.rb +109 -99
  108. data/lib/action_view/test_case.rb +73 -53
  109. data/lib/action_view/testing/resolvers.rb +24 -33
  110. data/lib/action_view/unbound_template.rb +31 -0
  111. data/lib/action_view/version.rb +3 -1
  112. data/lib/action_view/view_paths.rb +74 -44
  113. data/lib/action_view.rb +14 -9
  114. data/lib/assets/compiled/rails-ujs.js +746 -0
  115. metadata +71 -26
  116. data/lib/action_view/helpers/record_tag_helper.rb +0 -108
  117. data/lib/action_view/tasks/dependencies.rake +0 -23
  118. data/lib/action_view/vendor/html-scanner/html/document.rb +0 -68
  119. data/lib/action_view/vendor/html-scanner/html/node.rb +0 -532
  120. data/lib/action_view/vendor/html-scanner/html/sanitizer.rb +0 -188
  121. data/lib/action_view/vendor/html-scanner/html/selector.rb +0 -830
  122. data/lib/action_view/vendor/html-scanner/html/tokenizer.rb +0 -107
  123. data/lib/action_view/vendor/html-scanner/html/version.rb +0 -11
  124. 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
- # &nbsp; updated_at:
20
- # &nbsp; username: testing
21
- #
22
- # &nbsp; age: 42
23
- # &nbsp; password: xyz
24
- # &nbsp; 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::dump(object)
31
- object = ERB::Util.html_escape(object.to_yaml).gsub(" ", "&nbsp; ").html_safe
32
- content_tag(:pre, object, :class => "debug_dump")
33
- rescue Exception # errors from Marshal or YAML
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, :class => "debug_dump")
32
+ content_tag(:code, object.inspect, class: "debug_dump")
36
33
  end
37
34
  end
38
35
  end
@@ -1,22 +1,26 @@
1
- require 'cgi'
2
- require 'action_view/helpers/date_helper'
3
- require 'action_view/helpers/tag_helper'
4
- require 'action_view/helpers/form_tag_helper'
5
- require 'action_view/helpers/active_model_helper'
6
- require 'action_view/model_naming'
7
- require 'active_support/core_ext/module/attribute_accessors'
8
- require 'active_support/core_ext/hash/slice'
9
- require 'active_support/core_ext/string/output_safety'
10
- require 'active_support/core_ext/string/inflections'
1
+ # frozen_string_literal: true
2
+
3
+ require "cgi"
4
+ require "action_view/helpers/date_helper"
5
+ require "action_view/helpers/tag_helper"
6
+ require "action_view/helpers/form_tag_helper"
7
+ require "action_view/helpers/active_model_helper"
8
+ require "action_view/model_naming"
9
+ require "action_view/record_identifier"
10
+ require "active_support/core_ext/module/attribute_accessors"
11
+ require "active_support/core_ext/hash/slice"
12
+ require "active_support/core_ext/string/output_safety"
13
+ require "active_support/core_ext/string/inflections"
14
+ require "active_support/core_ext/symbol/starts_ends_with"
11
15
 
12
16
  module ActionView
13
17
  # = Action View Form Helpers
14
- module Helpers
18
+ module Helpers #:nodoc:
15
19
  # Form helpers are designed to make working with resources much easier
16
20
  # compared to using vanilla HTML.
17
21
  #
18
22
  # Typically, a form designed to create or update a resource reflects the
19
- # identity of the resource in several ways: (i) the url that the form is
23
+ # identity of the resource in several ways: (i) the URL that the form is
20
24
  # sent to (the form element's +action+ attribute) should result in a request
21
25
  # being routed to the appropriate controller action (with the appropriate <tt>:id</tt>
22
26
  # parameter in the case of an existing resource), (ii) input fields should
@@ -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
- # <div style="display:none">
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.create</tt>:
73
+ # set in the form. That hash is ready to be passed to <tt>Person.new</tt>:
72
74
  #
73
- # if @person = Person.create(params[:person])
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
- # <div style="display:none">
85
- # <input name="_method" type="hidden" value="patch" />
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
- # which results in an html <tt><input></tt> tag whose +name+ attribute is
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 url (We will describe below an alternative
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 FormOptionHelper and DateHelper that
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
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
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 expected default behavior is an XMLHttpRequest in the background instead of the regular
305
- # POST arrangement, but ultimately the behavior is the choice of the JavaScript driver implementor.
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
- # <div style='display:none'>
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
- # <div style='display:none'>
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
- form_tag(options[:url] || {}, html_options) { output }
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] ||= polymorphic_path(record, format: options.delete(:format))
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 +field_for+ is similar to +form_for+'s,
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
- # <%= f.submit %>
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 FormOptionHelper and
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
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
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 (eg. 1, '1', true, or 'true'):
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
- # (eg. 1, '1', true, or 'true'):
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>.'.html_safe
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, :multiple => true)
831
- # # => <input type="file" id="post_image" name="post[image]" multiple="true" />
1206
+ # file_field(:post, :image, multiple: true)
1207
+ # # => <input type="file" id="post_image" name="post[image][]" multiple="multiple" />
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 "to_date"
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 objects's value. It is still possible to override that
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
- def time_field(object_name, method, options = {})
1028
- Tags::TimeField.new(object_name, method, self, options).render
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
- # datetime_field("user", "born_on")
1034
- # # => <input id="user_born_on" name="user[born_on]" type="datetime" />
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
- # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T.%L%z"
1037
- # on the object's value, which makes it behave as expected for instances
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
- # @user.born_on = Date.new(1984, 1, 12)
1041
- # datetime_field("user", "born_on")
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 datetime_field(object_name, method, options = {})
1045
- Tags::DatetimeField.new(object_name, method, self, options).render
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
- # datetime_local_field("user", "born_on")
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
- # datetime_local_field("user", "born_on")
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
- def datetime_local_field(object_name, method, options = {})
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, the a +FormBuilder+ object is yielded as the
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 +@person+ model object
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 a new radio button. Note that when options are passed in, you
1189
- # must called +objectify_options+ in order for the model object to get
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
- self.field_helpers = [:fields_for, :label, :text_field, :password_field,
1208
- :hidden_field, :file_field, :text_area, :check_box,
1209
- :radio_button, :color_field, :search_field,
1210
- :telephone_field, :phone_field, :date_field,
1211
- :time_field, :datetime_field, :datetime_local_field,
1212
- :month_field, :week_field, :url_field, :email_field,
1213
- :number_field, :range_field]
1633
+ class_attribute :field_helpers, default: [
1634
+ :fields_for, :fields, :label, :text_field, :password_field,
1635
+ :hidden_field, :file_field, :text_area, :check_box,
1636
+ :radio_button, :color_field, :search_field,
1637
+ :telephone_field, :phone_field, :date_field,
1638
+ :time_field, :datetime_field, :datetime_local_field,
1639
+ :month_field, :week_field, :url_field, :email_field,
1640
+ :number_field, :range_field
1641
+ ]
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
- if @object_name.to_s.match(/\[\]$/)
1245
- if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
1671
+ @default_options = @options ? @options.slice(:index, :namespace, :skip_default_ids, :allow_method_names_outside_object) : {}
1672
+ @default_html_options = @default_options.except(:skip_default_ids, :allow_method_names_outside_object)
1673
+
1674
+ convert_to_legacy_options(@options)
1675
+
1676
+ if @object_name&.end_with?("[]")
1677
+ if (object ||= @template.instance_variable_get("@#{@object_name[0..-3]}")) && object.respond_to?(:to_param)
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
- (field_helpers - [:label, :check_box, :radio_button, :fields_for, :hidden_field, :file_field]).each do |selector|
1688
+ ##
1689
+ # :method: text_field
1690
+ #
1691
+ # :call-seq: text_field(method, options = {})
1692
+ #
1693
+ # Wraps ActionView::Helpers::FormHelper#text_field for form builders:
1694
+ #
1695
+ # <%= form_with model: @user do |f| %>
1696
+ # <%= f.text_field :name %>
1697
+ # <% end %>
1698
+ #
1699
+ # Please refer to the documentation of the base helper for details.
1700
+
1701
+ ##
1702
+ # :method: password_field
1703
+ #
1704
+ # :call-seq: password_field(method, options = {})
1705
+ #
1706
+ # Wraps ActionView::Helpers::FormHelper#password_field for form builders:
1707
+ #
1708
+ # <%= form_with model: @user do |f| %>
1709
+ # <%= f.password_field :password %>
1710
+ # <% end %>
1711
+ #
1712
+ # Please refer to the documentation of the base helper for details.
1713
+
1714
+ ##
1715
+ # :method: text_area
1716
+ #
1717
+ # :call-seq: text_area(method, options = {})
1718
+ #
1719
+ # Wraps ActionView::Helpers::FormHelper#text_area for form builders:
1720
+ #
1721
+ # <%= form_with model: @user do |f| %>
1722
+ # <%= f.text_area :detail %>
1723
+ # <% end %>
1724
+ #
1725
+ # Please refer to the documentation of the base helper for details.
1726
+
1727
+ ##
1728
+ # :method: color_field
1729
+ #
1730
+ # :call-seq: color_field(method, options = {})
1731
+ #
1732
+ # Wraps ActionView::Helpers::FormHelper#color_field for form builders:
1733
+ #
1734
+ # <%= form_with model: @user do |f| %>
1735
+ # <%= f.color_field :favorite_color %>
1736
+ # <% end %>
1737
+ #
1738
+ # Please refer to the documentation of the base helper for details.
1739
+
1740
+ ##
1741
+ # :method: search_field
1742
+ #
1743
+ # :call-seq: search_field(method, options = {})
1744
+ #
1745
+ # Wraps ActionView::Helpers::FormHelper#search_field for form builders:
1746
+ #
1747
+ # <%= form_with model: @user do |f| %>
1748
+ # <%= f.search_field :name %>
1749
+ # <% end %>
1750
+ #
1751
+ # Please refer to the documentation of the base helper for details.
1752
+
1753
+ ##
1754
+ # :method: telephone_field
1755
+ #
1756
+ # :call-seq: telephone_field(method, options = {})
1757
+ #
1758
+ # Wraps ActionView::Helpers::FormHelper#telephone_field for form builders:
1759
+ #
1760
+ # <%= form_with model: @user do |f| %>
1761
+ # <%= f.telephone_field :phone %>
1762
+ # <% end %>
1763
+ #
1764
+ # Please refer to the documentation of the base helper for details.
1765
+
1766
+ ##
1767
+ # :method: phone_field
1768
+ #
1769
+ # :call-seq: phone_field(method, options = {})
1770
+ #
1771
+ # Wraps ActionView::Helpers::FormHelper#phone_field for form builders:
1772
+ #
1773
+ # <%= form_with model: @user do |f| %>
1774
+ # <%= f.phone_field :phone %>
1775
+ # <% end %>
1776
+ #
1777
+ # Please refer to the documentation of the base helper for details.
1778
+
1779
+ ##
1780
+ # :method: date_field
1781
+ #
1782
+ # :call-seq: date_field(method, options = {})
1783
+ #
1784
+ # Wraps ActionView::Helpers::FormHelper#date_field for form builders:
1785
+ #
1786
+ # <%= form_with model: @user do |f| %>
1787
+ # <%= f.date_field :born_on %>
1788
+ # <% end %>
1789
+ #
1790
+ # Please refer to the documentation of the base helper for details.
1791
+
1792
+ ##
1793
+ # :method: time_field
1794
+ #
1795
+ # :call-seq: time_field(method, options = {})
1796
+ #
1797
+ # Wraps ActionView::Helpers::FormHelper#time_field for form builders:
1798
+ #
1799
+ # <%= form_with model: @user do |f| %>
1800
+ # <%= f.time_field :born_at %>
1801
+ # <% end %>
1802
+ #
1803
+ # Please refer to the documentation of the base helper for details.
1804
+
1805
+ ##
1806
+ # :method: datetime_field
1807
+ #
1808
+ # :call-seq: datetime_field(method, options = {})
1809
+ #
1810
+ # Wraps ActionView::Helpers::FormHelper#datetime_field for form builders:
1811
+ #
1812
+ # <%= form_with model: @user do |f| %>
1813
+ # <%= f.datetime_field :graduation_day %>
1814
+ # <% end %>
1815
+ #
1816
+ # Please refer to the documentation of the base helper for details.
1817
+
1818
+ ##
1819
+ # :method: datetime_local_field
1820
+ #
1821
+ # :call-seq: datetime_local_field(method, options = {})
1822
+ #
1823
+ # Wraps ActionView::Helpers::FormHelper#datetime_local_field for form builders:
1824
+ #
1825
+ # <%= form_with model: @user do |f| %>
1826
+ # <%= f.datetime_local_field :graduation_day %>
1827
+ # <% end %>
1828
+ #
1829
+ # Please refer to the documentation of the base helper for details.
1830
+
1831
+ ##
1832
+ # :method: month_field
1833
+ #
1834
+ # :call-seq: month_field(method, options = {})
1835
+ #
1836
+ # Wraps ActionView::Helpers::FormHelper#month_field for form builders:
1837
+ #
1838
+ # <%= form_with model: @user do |f| %>
1839
+ # <%= f.month_field :birthday_month %>
1840
+ # <% end %>
1841
+ #
1842
+ # Please refer to the documentation of the base helper for details.
1843
+
1844
+ ##
1845
+ # :method: week_field
1846
+ #
1847
+ # :call-seq: week_field(method, options = {})
1848
+ #
1849
+ # Wraps ActionView::Helpers::FormHelper#week_field for form builders:
1850
+ #
1851
+ # <%= form_with model: @user do |f| %>
1852
+ # <%= f.week_field :birthday_week %>
1853
+ # <% end %>
1854
+ #
1855
+ # Please refer to the documentation of the base helper for details.
1856
+
1857
+ ##
1858
+ # :method: url_field
1859
+ #
1860
+ # :call-seq: url_field(method, options = {})
1861
+ #
1862
+ # Wraps ActionView::Helpers::FormHelper#url_field for form builders:
1863
+ #
1864
+ # <%= form_with model: @user do |f| %>
1865
+ # <%= f.url_field :homepage %>
1866
+ # <% end %>
1867
+ #
1868
+ # Please refer to the documentation of the base helper for details.
1869
+
1870
+ ##
1871
+ # :method: email_field
1872
+ #
1873
+ # :call-seq: email_field(method, options = {})
1874
+ #
1875
+ # Wraps ActionView::Helpers::FormHelper#email_field for form builders:
1876
+ #
1877
+ # <%= form_with model: @user do |f| %>
1878
+ # <%= f.email_field :address %>
1879
+ # <% end %>
1880
+ #
1881
+ # Please refer to the documentation of the base helper for details.
1882
+
1883
+ ##
1884
+ # :method: number_field
1885
+ #
1886
+ # :call-seq: number_field(method, options = {})
1887
+ #
1888
+ # Wraps ActionView::Helpers::FormHelper#number_field for form builders:
1889
+ #
1890
+ # <%= form_with model: @user do |f| %>
1891
+ # <%= f.number_field :age %>
1892
+ # <% end %>
1893
+ #
1894
+ # Please refer to the documentation of the base helper for details.
1895
+
1896
+ ##
1897
+ # :method: range_field
1898
+ #
1899
+ # :call-seq: range_field(method, options = {})
1900
+ #
1901
+ # Wraps ActionView::Helpers::FormHelper#range_field for form builders:
1902
+ #
1903
+ # <%= form_with model: @user do |f| %>
1904
+ # <%= f.range_field :age %>
1905
+ # <% end %>
1906
+ #
1907
+ # Please refer to the documentation of the base helper for details.
1908
+
1909
+ (field_helpers - [:label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field]).each do |selector|
1256
1910
  class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
1257
1911
  def #{selector}(method, options = {}) # def text_field(method, options = {})
1258
- @template.send( # @template.send(
1259
- #{selector.inspect}, # "text_field",
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 +field_for+ is similar to +form_for+'s,
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 FormOptionHelper and
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
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
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 (eg. 1, '1', true, or 'true'):
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
- # (eg. 1, '1', true, or 'true'):
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
- self.object_name = @object_name.to_s.sub(/\[\]$/,"")
2182
+ object_name = object_name.to_s.delete_suffix("[]")
1528
2183
  @auto_index
1529
2184
  end
1530
2185
 
1531
- record_name = index ? "#{object_name}[#{index}][#{record_name}]" : "#{object_name}[#{record_name}]"
2186
+ record_name = if index
2187
+ "#{object_name}[#{index}][#{record_name}]"
2188
+ elsif record_name.end_with?("[]")
2189
+ "#{object_name}[#{record_name[0..-3]}][#{record_object.id}]"
2190
+ else
2191
+ "#{object_name}[#{record_name}]"
2192
+ end
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(:post, :title)
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(:post, :body)
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(:post, :cost)
2240
+ # label(:cost)
1570
2241
  # # => <label for="post_cost">Total cost</label>
1571
2242
  #
1572
- # label(:post, :title, "A short title")
2243
+ # label(:title, "A short title")
1573
2244
  # # => <label for="post_title">A short title</label>
1574
2245
  #
1575
- # label(:post, :title, "A short title", class: "title_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(:post, :privacy, "Public Post", value: "public")
2249
+ # label(:privacy, "Public Post", value: "public")
1579
2250
  # # => <label for="post_privacy_public">Public Post</label>
1580
2251
  #
1581
- # label(:post, :terms) do
1582
- # 'Accept <a href="/terms">Terms</a>.'.html_safe
2252
+ # label(:cost) do |translation|
2253
+ # content_tag(:span, translation, class: "cost_label")
2254
+ # end
2255
+ # # => <label for="post_cost"><span class="cost_label">Total cost</span></label>
2256
+ #
2257
+ # label(:cost) do |builder|
2258
+ # content_tag(:span, builder.translation, class: "cost_label")
2259
+ # end
2260
+ # # => <label for="post_cost"><span class="cost_label">Total cost</span></label>
2261
+ #
2262
+ # label(:cost) do |builder|
2263
+ # content_tag(:span, builder.translation, class: [
2264
+ # "cost_label",
2265
+ # ("error_label" if builder.object.errors.include?(:cost))
2266
+ # ])
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("post", "validated")
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("puppy", "gooddog", {}, "yes", "no")
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
- # check_box("eula", "accepted", { class: 'eula_check' }, "yes", "no")
2331
+ # # Let's say that @eula.accepted is "no":
2332
+ # check_box("accepted", { class: 'eula_check' }, "yes", "no")
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("post", "category", "rails")
1657
- # radio_button("post", "category", "java")
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
- # radio_button("user", "receive_newsletter", "yes")
1662
- # radio_button("user", "receive_newsletter", "no")
2352
+ # # Let's say that @user.receive_newsletter returns "no":
2353
+ # radio_button("receive_newsletter", "yes")
2354
+ # radio_button("receive_newsletter", "no")
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
- # hidden_field(:signup, :pass_confirm)
1676
- # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
2367
+ # # Let's say that @signup.pass_confirm returns true:
2368
+ # hidden_field(:pass_confirm)
2369
+ # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="true" />
1677
2370
  #
1678
- # hidden_field(:post, :tag_list)
1679
- # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
2371
+ # # Let's say that @post.tag_list returns "blog, ruby":
2372
+ # hidden_field(:tag_list)
2373
+ # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="blog, ruby" />
1680
2374
  #
1681
- # hidden_field(:user, :token)
1682
- # # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
2375
+ # # Let's say that @user.token returns "abcde":
2376
+ # hidden_field(:token)
2377
+ # # => <input type="hidden" id="user_token" name="user[token]" value="abcde" />
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
- # file_field(:user, :avatar)
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
- # file_field(:post, :image, :multiple => true)
1707
- # # => <input type="file" id="post_image" name="post[image]" multiple="true" />
2402
+ # # Let's say that @post has image:
2403
+ # file_field(:image, :multiple => true)
2404
+ # # => <input type="file" id="post_image" name="post[image][]" multiple="multiple" />
1708
2405
  #
1709
- # file_field(:post, :attached, accept: 'text/html')
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
- # file_field(:post, :image, accept: 'image/png,image/gif,image/jpeg')
2410
+ # # Let's say that @post has image:
2411
+ # file_field(:image, accept: 'image/png,image/gif,image/jpeg')
1713
2412
  # # => <input type="file" id="post_image" name="post[image]" accept="image/png,image/gif,image/jpeg" />
1714
2413
  #
1715
- # file_field(:attachment, :file, class: 'file_input')
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 @post is a new record, it will use "Create Post" as
1730
- # submit button label, otherwise, it uses "Update Post".
2429
+ # In the example above, if <tt>@post</tt> is a new record, it will use "Create Post" as
2430
+ # submit button label; otherwise, it uses "Update Post".
1731
2431
  #
1732
- # Those labels can be customized using I18n, under the helpers.submit key and accept
1733
- # the %{model} as translation interpolation:
2432
+ # Those labels can be customized using I18n under the +helpers.submit+ key and using
2433
+ # <tt>%{model}</tt> for translation interpolation:
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 for the given object:
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 @post is a new record, it will use "Create Post" as
1763
- # button label, otherwise, it uses "Update Post".
2462
+ # In the example above, if <tt>@post</tt> is a new record, it will use "Create Post" as
2463
+ # button label; otherwise, it uses "Update Post".
1764
2464
  #
1765
- # Those labels can be customized using I18n, under the helpers.submit key
1766
- # (the same as submit helper) and accept the %{model} as translation interpolation:
2465
+ # Those labels can be customized using I18n under the +helpers.submit+ key
2466
+ # (the same as submit helper) and using <tt>%{model}</tt> for translation interpolation:
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 for the given object:
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 a post")
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
- @template.button_tag(value, options, &block)
2503
+
2504
+ if block_given?
2505
+ value = @template.capture { yield(value) }
2506
+ end
2507
+
2508
+ @template.button_tag(value, options)
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.merge(object: @object))
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.class.respond_to?(:model_name)
1813
- object.class.model_name.human
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
- defaults << :"helpers.submit.#{object_name}.#{key}"
2533
+ # Object is a model and it is not overwritten by as and scope option.
2534
+ if object.respond_to?(:model_name) && object_name.to_s == model.downcase
2535
+ defaults << :"helpers.submit.#{object.model_name.i18n_key}.#{key}"
2536
+ else
2537
+ defaults << :"helpers.submit.#{object_name}.#{key}"
2538
+ end
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.send(association_name).respond_to?(:to_ary)
2554
+ association = [association] if @object.public_send(association_name).respond_to?(:to_ary)
1836
2555
  elsif !association.respond_to?(:to_ary)
1837
- association = @object.send(association_name)
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
- options[:child_index] = nested_child_index(name) unless explicit_child_index
2563
+ if explicit_child_index
2564
+ options[:child_index] = explicit_child_index.call if explicit_child_index.respond_to?(:call)
2565
+ else
2566
+ options[:child_index] = nested_child_index(name)
2567
+ end
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(:default_form_builder, instance_writer: false, instance_reader: false) do
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