actionview 5.1.4 → 6.1.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 (118) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +199 -168
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +7 -5
  5. data/lib/action_view.rb +10 -4
  6. data/lib/action_view/base.rb +87 -23
  7. data/lib/action_view/buffers.rb +17 -0
  8. data/lib/action_view/cache_expiry.rb +52 -0
  9. data/lib/action_view/context.rb +7 -11
  10. data/lib/action_view/dependency_tracker.rb +12 -4
  11. data/lib/action_view/digestor.rb +24 -23
  12. data/lib/action_view/flows.rb +2 -1
  13. data/lib/action_view/gem_version.rb +4 -2
  14. data/lib/action_view/helpers.rb +4 -2
  15. data/lib/action_view/helpers/active_model_helper.rb +9 -4
  16. data/lib/action_view/helpers/asset_tag_helper.rb +220 -57
  17. data/lib/action_view/helpers/asset_url_helper.rb +28 -23
  18. data/lib/action_view/helpers/atom_feed_helper.rb +5 -2
  19. data/lib/action_view/helpers/cache_helper.rb +39 -28
  20. data/lib/action_view/helpers/capture_helper.rb +13 -7
  21. data/lib/action_view/helpers/controller_helper.rb +3 -1
  22. data/lib/action_view/helpers/csp_helper.rb +26 -0
  23. data/lib/action_view/helpers/csrf_helper.rb +5 -3
  24. data/lib/action_view/helpers/date_helper.rb +78 -33
  25. data/lib/action_view/helpers/debug_helper.rb +4 -2
  26. data/lib/action_view/helpers/form_helper.rb +357 -106
  27. data/lib/action_view/helpers/form_options_helper.rb +45 -39
  28. data/lib/action_view/helpers/form_tag_helper.rb +42 -27
  29. data/lib/action_view/helpers/javascript_helper.rb +28 -12
  30. data/lib/action_view/helpers/number_helper.rb +16 -8
  31. data/lib/action_view/helpers/output_safety_helper.rb +3 -1
  32. data/lib/action_view/helpers/rendering_helper.rb +20 -9
  33. data/lib/action_view/helpers/sanitize_helper.rb +15 -19
  34. data/lib/action_view/helpers/tag_helper.rb +100 -24
  35. data/lib/action_view/helpers/tags.rb +3 -1
  36. data/lib/action_view/helpers/tags/base.rb +30 -21
  37. data/lib/action_view/helpers/tags/check_box.rb +3 -2
  38. data/lib/action_view/helpers/tags/checkable.rb +4 -2
  39. data/lib/action_view/helpers/tags/collection_check_boxes.rb +2 -1
  40. data/lib/action_view/helpers/tags/collection_helpers.rb +2 -1
  41. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +2 -1
  42. data/lib/action_view/helpers/tags/collection_select.rb +3 -1
  43. data/lib/action_view/helpers/tags/color_field.rb +4 -3
  44. data/lib/action_view/helpers/tags/date_field.rb +3 -2
  45. data/lib/action_view/helpers/tags/date_select.rb +5 -4
  46. data/lib/action_view/helpers/tags/datetime_field.rb +3 -2
  47. data/lib/action_view/helpers/tags/datetime_local_field.rb +3 -2
  48. data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
  49. data/lib/action_view/helpers/tags/email_field.rb +2 -0
  50. data/lib/action_view/helpers/tags/file_field.rb +2 -0
  51. data/lib/action_view/helpers/tags/grouped_collection_select.rb +3 -1
  52. data/lib/action_view/helpers/tags/hidden_field.rb +2 -0
  53. data/lib/action_view/helpers/tags/label.rb +6 -5
  54. data/lib/action_view/helpers/tags/month_field.rb +3 -2
  55. data/lib/action_view/helpers/tags/number_field.rb +2 -0
  56. data/lib/action_view/helpers/tags/password_field.rb +2 -0
  57. data/lib/action_view/helpers/tags/placeholderable.rb +2 -0
  58. data/lib/action_view/helpers/tags/radio_button.rb +3 -2
  59. data/lib/action_view/helpers/tags/range_field.rb +2 -0
  60. data/lib/action_view/helpers/tags/search_field.rb +2 -0
  61. data/lib/action_view/helpers/tags/select.rb +4 -3
  62. data/lib/action_view/helpers/tags/tel_field.rb +2 -0
  63. data/lib/action_view/helpers/tags/text_area.rb +3 -1
  64. data/lib/action_view/helpers/tags/text_field.rb +3 -2
  65. data/lib/action_view/helpers/tags/time_field.rb +3 -2
  66. data/lib/action_view/helpers/tags/time_select.rb +2 -0
  67. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
  68. data/lib/action_view/helpers/tags/translator.rb +3 -6
  69. data/lib/action_view/helpers/tags/url_field.rb +2 -0
  70. data/lib/action_view/helpers/tags/week_field.rb +3 -2
  71. data/lib/action_view/helpers/text_helper.rb +11 -10
  72. data/lib/action_view/helpers/translation_helper.rb +102 -52
  73. data/lib/action_view/helpers/url_helper.rb +150 -32
  74. data/lib/action_view/layouts.rb +15 -15
  75. data/lib/action_view/log_subscriber.rb +32 -15
  76. data/lib/action_view/lookup_context.rb +67 -39
  77. data/lib/action_view/model_naming.rb +2 -0
  78. data/lib/action_view/path_set.rb +5 -12
  79. data/lib/action_view/railtie.rb +46 -21
  80. data/lib/action_view/record_identifier.rb +4 -3
  81. data/lib/action_view/renderer/abstract_renderer.rb +144 -11
  82. data/lib/action_view/renderer/collection_renderer.rb +196 -0
  83. data/lib/action_view/renderer/object_renderer.rb +34 -0
  84. data/lib/action_view/renderer/partial_renderer.rb +33 -283
  85. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +64 -17
  86. data/lib/action_view/renderer/renderer.rb +61 -4
  87. data/lib/action_view/renderer/streaming_template_renderer.rb +14 -8
  88. data/lib/action_view/renderer/template_renderer.rb +36 -26
  89. data/lib/action_view/rendering.rb +57 -38
  90. data/lib/action_view/routing_url_for.rb +15 -12
  91. data/lib/action_view/tasks/cache_digests.rake +2 -0
  92. data/lib/action_view/template.rb +69 -76
  93. data/lib/action_view/template/error.rb +32 -18
  94. data/lib/action_view/template/handlers.rb +4 -2
  95. data/lib/action_view/template/handlers/builder.rb +5 -6
  96. data/lib/action_view/template/handlers/erb.rb +20 -19
  97. data/lib/action_view/template/handlers/erb/erubi.rb +17 -9
  98. data/lib/action_view/template/handlers/html.rb +3 -1
  99. data/lib/action_view/template/handlers/raw.rb +4 -2
  100. data/lib/action_view/template/html.rb +8 -7
  101. data/lib/action_view/template/inline.rb +22 -0
  102. data/lib/action_view/template/raw_file.rb +25 -0
  103. data/lib/action_view/template/renderable.rb +24 -0
  104. data/lib/action_view/template/resolver.rb +194 -152
  105. data/lib/action_view/template/sources.rb +13 -0
  106. data/lib/action_view/template/sources/file.rb +17 -0
  107. data/lib/action_view/template/text.rb +5 -4
  108. data/lib/action_view/template/types.rb +3 -1
  109. data/lib/action_view/test_case.rb +38 -30
  110. data/lib/action_view/testing/resolvers.rb +20 -27
  111. data/lib/action_view/unbound_template.rb +31 -0
  112. data/lib/action_view/version.rb +2 -0
  113. data/lib/action_view/view_paths.rb +61 -40
  114. data/lib/assets/compiled/rails-ujs.js +84 -23
  115. metadata +34 -23
  116. data/lib/action_view/helpers/record_tag_helper.rb +0 -21
  117. data/lib/action_view/template/handlers/erb/deprecated_erubis.rb +0 -9
  118. data/lib/action_view/template/handlers/erb/erubis.rb +0 -81
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  # = Action View CSRF Helper
3
- module Helpers
5
+ module Helpers #:nodoc:
4
6
  module CsrfHelper
5
7
  # Returns meta tags "csrf-param" and "csrf-token" with the name of the cross-site
6
8
  # request forgery protection parameter and token, respectively.
@@ -15,10 +17,10 @@ module ActionView
15
17
  # You don't need to use these tags for regular forms as they generate their own hidden fields.
16
18
  #
17
19
  # For AJAX requests other than GETs, extract the "csrf-token" from the meta-tag and send as the
18
- # "X-CSRF-Token" HTTP header. If you are using jQuery with jquery-rails this happens automatically.
20
+ # "X-CSRF-Token" HTTP header. If you are using rails-ujs this happens automatically.
19
21
  #
20
22
  def csrf_meta_tags
21
- if protect_against_forgery?
23
+ if defined?(protect_against_forgery?) && protect_against_forgery?
22
24
  [
23
25
  tag("meta", name: "csrf-param", content: request_forgery_protection_token),
24
26
  tag("meta", name: "csrf-token", content: form_authenticity_token)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "date"
2
4
  require "action_view/helpers/tag_helper"
3
5
  require "active_support/core_ext/array/extract_options"
@@ -7,7 +9,7 @@ require "active_support/core_ext/object/acts_like"
7
9
  require "active_support/core_ext/object/with_options"
8
10
 
9
11
  module ActionView
10
- module Helpers
12
+ module Helpers #:nodoc:
11
13
  # = Action View Date Helpers
12
14
  #
13
15
  # The Date Helper primarily creates select/option tags for different kinds of dates and times or date and time
@@ -114,7 +116,7 @@ module ActionView
114
116
  when 10..19 then locale.t :less_than_x_seconds, count: 20
115
117
  when 20..39 then locale.t :half_a_minute
116
118
  when 40..59 then locale.t :less_than_x_minutes, count: 1
117
- else locale.t :x_minutes, count: 1
119
+ else locale.t :x_minutes, count: 1
118
120
  end
119
121
 
120
122
  when 2...45 then locale.t :x_minutes, count: distance_in_minutes
@@ -129,7 +131,7 @@ module ActionView
129
131
  when 43200...86400 then locale.t :about_x_months, count: (distance_in_minutes.to_f / 43200.0).round
130
132
  # 60 days up to 365 days
131
133
  when 86400...525600 then locale.t :x_months, count: (distance_in_minutes.to_f / 43200.0).round
132
- else
134
+ else
133
135
  from_year = from_time.year
134
136
  from_year += 1 if from_time.month >= 3
135
137
  to_year = to_time.year
@@ -195,14 +197,15 @@ module ActionView
195
197
  # and +:name+ (string). A format string would be something like "%{name} (%<number>02d)" for example.
196
198
  # See <tt>Kernel.sprintf</tt> for documentation on format sequences.
197
199
  # * <tt>:date_separator</tt> - Specifies a string to separate the date fields. Default is "" (i.e. nothing).
198
- # * <tt>:time_separator</tt> - Specifies a string to separate the time fields. Default is "" (i.e. nothing).
199
- # * <tt>:datetime_separator</tt>- Specifies a string to separate the date and time fields. Default is "" (i.e. nothing).
200
+ # * <tt>:time_separator</tt> - Specifies a string to separate the time fields. Default is " : ".
201
+ # * <tt>:datetime_separator</tt>- Specifies a string to separate the date and time fields. Default is " &mdash; ".
200
202
  # * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Date.today.year - 5</tt> if
201
203
  # you are creating new record. While editing existing record, <tt>:start_year</tt> defaults to
202
204
  # the current selected year minus 5.
203
205
  # * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Date.today.year + 5</tt> if
204
206
  # you are creating new record. While editing existing record, <tt>:end_year</tt> defaults to
205
207
  # the current selected year plus 5.
208
+ # * <tt>:year_format</tt> - Set format of years for year select. Lambda should be passed.
206
209
  # * <tt>:discard_day</tt> - Set to true if you don't want to show a day select. This includes the day
207
210
  # as a hidden field instead of showing a select field. Also note that this implicitly sets the day to be the
208
211
  # first of the given month in order to not create invalid dates like 31 February.
@@ -273,6 +276,9 @@ module ActionView
273
276
  # # Generates a date select with custom prompts.
274
277
  # date_select("article", "written_on", prompt: { day: 'Select day', month: 'Select month', year: 'Select year' })
275
278
  #
279
+ # # Generates a date select with custom year format.
280
+ # date_select("article", "written_on", year_format: ->(year) { "Heisei #{year - 1988}" })
281
+ #
276
282
  # The selects are prepared for multi-parameter assignment to an Active Record object.
277
283
  #
278
284
  # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
@@ -300,15 +306,15 @@ module ActionView
300
306
  # time_select("article", "start_time", include_seconds: true)
301
307
  #
302
308
  # # You can set the <tt>:minute_step</tt> to 15 which will give you: 00, 15, 30, and 45.
303
- # time_select 'game', 'game_time', {minute_step: 15}
309
+ # time_select 'game', 'game_time', { minute_step: 15 }
304
310
  #
305
311
  # # Creates a time select tag with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
306
- # time_select("article", "written_on", prompt: {hour: 'Choose hour', minute: 'Choose minute', second: 'Choose seconds'})
307
- # time_select("article", "written_on", prompt: {hour: true}) # generic prompt for hours
312
+ # time_select("article", "written_on", prompt: { hour: 'Choose hour', minute: 'Choose minute', second: 'Choose seconds' })
313
+ # time_select("article", "written_on", prompt: { hour: true }) # generic prompt for hours
308
314
  # time_select("article", "written_on", prompt: true) # generic prompts for all
309
315
  #
310
316
  # # You can set :ampm option to true which will show the hours as: 12 PM, 01 AM .. 11 PM.
311
- # time_select 'game', 'game_time', {ampm: true}
317
+ # time_select 'game', 'game_time', { ampm: true }
312
318
  #
313
319
  # The selects are prepared for multi-parameter assignment to an Active Record object.
314
320
  #
@@ -344,8 +350,8 @@ module ActionView
344
350
  # datetime_select("article", "written_on", discard_type: true)
345
351
  #
346
352
  # # Generates a datetime select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
347
- # datetime_select("article", "written_on", prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
348
- # datetime_select("article", "written_on", prompt: {hour: true}) # generic prompt for hours
353
+ # datetime_select("article", "written_on", prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' })
354
+ # datetime_select("article", "written_on", prompt: { hour: true }) # generic prompt for hours
349
355
  # datetime_select("article", "written_on", prompt: true) # generic prompts for all
350
356
  #
351
357
  # The selects are prepared for multi-parameter assignment to an Active Record object.
@@ -395,8 +401,8 @@ module ActionView
395
401
  # select_datetime(my_date_time, prefix: 'payday')
396
402
  #
397
403
  # # Generates a datetime select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
398
- # select_datetime(my_date_time, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
399
- # select_datetime(my_date_time, prompt: {hour: true}) # generic prompt for hours
404
+ # select_datetime(my_date_time, prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' })
405
+ # select_datetime(my_date_time, prompt: { hour: true }) # generic prompt for hours
400
406
  # select_datetime(my_date_time, prompt: true) # generic prompts for all
401
407
  def select_datetime(datetime = Time.current, options = {}, html_options = {})
402
408
  DateTimeSelector.new(datetime, options, html_options).select_datetime
@@ -434,8 +440,8 @@ module ActionView
434
440
  # select_date(my_date, prefix: 'payday')
435
441
  #
436
442
  # # Generates a date select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
437
- # select_date(my_date, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
438
- # select_date(my_date, prompt: {hour: true}) # generic prompt for hours
443
+ # select_date(my_date, prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' })
444
+ # select_date(my_date, prompt: { hour: true }) # generic prompt for hours
439
445
  # select_date(my_date, prompt: true) # generic prompts for all
440
446
  def select_date(date = Date.current, options = {}, html_options = {})
441
447
  DateTimeSelector.new(date, options, html_options).select_date
@@ -474,8 +480,8 @@ module ActionView
474
480
  # select_time(my_time, start_hour: 2, end_hour: 14)
475
481
  #
476
482
  # # Generates a time select with a custom prompt. Use <tt>:prompt</tt> to true for generic prompts.
477
- # select_time(my_time, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
478
- # select_time(my_time, prompt: {hour: true}) # generic prompt for hours
483
+ # select_time(my_time, prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' })
484
+ # select_time(my_time, prompt: { hour: true }) # generic prompt for hours
479
485
  # select_time(my_time, prompt: true) # generic prompts for all
480
486
  def select_time(datetime = Time.current, options = {}, html_options = {})
481
487
  DateTimeSelector.new(datetime, options, html_options).select_time
@@ -666,8 +672,6 @@ module ActionView
666
672
  # <time datetime="2010-11-04T17:55:45+01:00">November 04, 2010 17:55</time>
667
673
  # time_tag Date.yesterday, 'Yesterday' # =>
668
674
  # <time datetime="2010-11-03">Yesterday</time>
669
- # time_tag Date.today, pubdate: true # =>
670
- # <time datetime="2010-11-04" pubdate="pubdate">November 04, 2010</time>
671
675
  # time_tag Date.today, datetime: Date.today.strftime('%G-W%V') # =>
672
676
  # <time datetime="2010-W44">November 04, 2010</time>
673
677
  #
@@ -679,13 +683,11 @@ module ActionView
679
683
  options = args.extract_options!
680
684
  format = options.delete(:format) || :long
681
685
  content = args.first || I18n.l(date_or_time, format: format)
682
- datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.iso8601
683
686
 
684
- content_tag("time".freeze, content, options.reverse_merge(datetime: datetime), &block)
687
+ content_tag("time", content, options.reverse_merge(datetime: date_or_time.iso8601), &block)
685
688
  end
686
689
 
687
690
  private
688
-
689
691
  def normalize_distance_of_time_argument_to_time(value)
690
692
  if value.is_a?(Numeric)
691
693
  Time.at(value)
@@ -700,7 +702,7 @@ module ActionView
700
702
  class DateTimeSelector #:nodoc:
701
703
  include ActionView::Helpers::TagHelper
702
704
 
703
- DEFAULT_PREFIX = "date".freeze
705
+ DEFAULT_PREFIX = "date"
704
706
  POSITION = {
705
707
  year: 1, month: 2, day: 3, hour: 4, minute: 5, second: 6
706
708
  }.freeze
@@ -821,14 +823,14 @@ module ActionView
821
823
  1.upto(12) do |month_number|
822
824
  options = { value: month_number }
823
825
  options[:selected] = "selected" if month == month_number
824
- month_options << content_tag("option".freeze, month_name(month_number), options) + "\n"
826
+ month_options << content_tag("option", month_name(month_number), options) + "\n"
825
827
  end
826
828
  build_select(:month, month_options.join)
827
829
  end
828
830
  end
829
831
 
830
832
  def select_year
831
- if !@datetime || @datetime == 0
833
+ if !year || @datetime == 0
832
834
  val = "1"
833
835
  middle_year = Date.today.year
834
836
  else
@@ -849,7 +851,7 @@ module ActionView
849
851
  raise ArgumentError, "There are too many years options to be built. Are you sure you haven't mistyped something? You can provide the :max_years_allowed parameter."
850
852
  end
851
853
 
852
- build_options_and_select(:year, val, options)
854
+ build_select(:year, build_year_options(val, options))
853
855
  end
854
856
  end
855
857
 
@@ -932,6 +934,21 @@ module ActionView
932
934
  end
933
935
  end
934
936
 
937
+ # Looks up year names by number.
938
+ #
939
+ # year_name(1998) # => 1998
940
+ #
941
+ # If the <tt>:year_format</tt> option is passed:
942
+ #
943
+ # year_name(1998) # => "Heisei 10"
944
+ def year_name(number)
945
+ if year_format_lambda = @options[:year_format]
946
+ year_format_lambda.call(number)
947
+ else
948
+ number
949
+ end
950
+ end
951
+
935
952
  def date_order
936
953
  @date_order ||= @options[:order] || translated_date_order
937
954
  end
@@ -988,7 +1005,35 @@ module ActionView
988
1005
  tag_options[:selected] = "selected" if selected == i
989
1006
  text = options[:use_two_digit_numbers] ? sprintf("%02d", i) : value
990
1007
  text = options[:ampm] ? AMPM_TRANSLATION[i] : text
991
- select_options << content_tag("option".freeze, text, tag_options)
1008
+ select_options << content_tag("option", text, tag_options)
1009
+ end
1010
+
1011
+ (select_options.join("\n") + "\n").html_safe
1012
+ end
1013
+
1014
+ # Build select option HTML for year.
1015
+ # If <tt>year_format</tt> option is not passed
1016
+ # build_year_options(1998, start: 1998, end: 2000)
1017
+ # => "<option value="1998" selected="selected">1998</option>
1018
+ # <option value="1999">1999</option>
1019
+ # <option value="2000">2000</option>"
1020
+ #
1021
+ # If <tt>year_format</tt> option is passed
1022
+ # build_year_options(1998, start: 1998, end: 2000, year_format: ->year { "Heisei #{ year - 1988 }" })
1023
+ # => "<option value="1998" selected="selected">Heisei 10</option>
1024
+ # <option value="1999">Heisei 11</option>
1025
+ # <option value="2000">Heisei 12</option>"
1026
+ def build_year_options(selected, options = {})
1027
+ start = options.delete(:start)
1028
+ stop = options.delete(:end)
1029
+ step = options.delete(:step)
1030
+
1031
+ select_options = []
1032
+ start.step(stop, step) do |value|
1033
+ tag_options = { value: value }
1034
+ tag_options[:selected] = "selected" if selected == value
1035
+ text = year_name(value)
1036
+ select_options << content_tag("option", text, tag_options)
992
1037
  end
993
1038
 
994
1039
  (select_options.join("\n") + "\n").html_safe
@@ -1007,12 +1052,12 @@ module ActionView
1007
1052
  select_options[:disabled] = "disabled" if @options[:disabled]
1008
1053
  select_options[:class] = css_class_attribute(type, select_options[:class], @options[:with_css_classes]) if @options[:with_css_classes]
1009
1054
 
1010
- select_html = "\n"
1011
- select_html << content_tag("option".freeze, "", value: "") + "\n" if @options[:include_blank]
1055
+ select_html = +"\n"
1056
+ select_html << content_tag("option", "", value: "", label: " ") + "\n" if @options[:include_blank]
1012
1057
  select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt]
1013
1058
  select_html << select_options_as_html
1014
1059
 
1015
- (content_tag("select".freeze, select_html.html_safe, select_options) + "\n").html_safe
1060
+ (content_tag("select", select_html.html_safe, select_options) + "\n").html_safe
1016
1061
  end
1017
1062
 
1018
1063
  # Builds the css class value for the select element
@@ -1045,7 +1090,7 @@ module ActionView
1045
1090
  I18n.translate(:"datetime.prompts.#{type}", locale: @options[:locale])
1046
1091
  end
1047
1092
 
1048
- prompt ? content_tag("option".freeze, prompt, value: "") : ""
1093
+ prompt ? content_tag("option", prompt, value: "") : ""
1049
1094
  end
1050
1095
 
1051
1096
  # Builds hidden input tag for date part and value.
@@ -1089,11 +1134,11 @@ module ActionView
1089
1134
  # Given an ordering of datetime components, create the selection HTML
1090
1135
  # and join them with their appropriate separators.
1091
1136
  def build_selects_from_types(order)
1092
- select = ""
1137
+ select = +""
1093
1138
  first_visible = order.find { |type| !@options[:"discard_#{type}"] }
1094
1139
  order.reverse_each do |type|
1095
1140
  separator = separator(type) unless type == first_visible # don't add before first visible field
1096
- select.insert(0, separator.to_s + send("select_#{type}").to_s)
1141
+ select.insert(0, separator.to_s + public_send("select_#{type}").to_s)
1097
1142
  end
1098
1143
  select.html_safe
1099
1144
  end
@@ -1,8 +1,10 @@
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
9
  include TagHelper
8
10
 
@@ -22,7 +24,7 @@ module ActionView
22
24
  # created_at:
23
25
  # </pre>
24
26
  def debug(object)
25
- Marshal::dump(object)
27
+ Marshal.dump(object)
26
28
  object = ERB::Util.html_escape(object.to_yaml)
27
29
  content_tag(:pre, object, class: "debug_dump")
28
30
  rescue # errors from Marshal or YAML
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "cgi"
2
4
  require "action_view/helpers/date_helper"
3
5
  require "action_view/helpers/tag_helper"
@@ -9,15 +11,16 @@ require "active_support/core_ext/module/attribute_accessors"
9
11
  require "active_support/core_ext/hash/slice"
10
12
  require "active_support/core_ext/string/output_safety"
11
13
  require "active_support/core_ext/string/inflections"
14
+ require "active_support/core_ext/symbol/starts_ends_with"
12
15
 
13
16
  module ActionView
14
17
  # = Action View Form Helpers
15
- module Helpers
18
+ module Helpers #:nodoc:
16
19
  # Form helpers are designed to make working with resources much easier
17
20
  # compared to using vanilla HTML.
18
21
  #
19
22
  # Typically, a form designed to create or update a resource reflects the
20
- # 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
21
24
  # sent to (the form element's +action+ attribute) should result in a request
22
25
  # being routed to the appropriate controller action (with the appropriate <tt>:id</tt>
23
26
  # parameter in the case of an existing resource), (ii) input fields should
@@ -164,7 +167,7 @@ module ActionView
164
167
  # So for example you may use a named route directly. When the model is
165
168
  # represented by a string or symbol, as in the example above, if the
166
169
  # <tt>:url</tt> option is not specified, by default the form will be
167
- # 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
168
171
  # resource-oriented usage of +form_for+ in which the URL does not need
169
172
  # to be specified explicitly).
170
173
  # * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of
@@ -183,8 +186,7 @@ module ActionView
183
186
  # get the authenticity token from the <tt>meta</tt> tag, so embedding is
184
187
  # unnecessary unless you support browsers without JavaScript.
185
188
  # * <tt>:remote</tt> - If set to true, will allow the Unobtrusive
186
- # JavaScript drivers to control the submit behavior. By default this
187
- # behavior is an ajax submit.
189
+ # JavaScript drivers to control the submit behavior.
188
190
  # * <tt>:enforce_utf8</tt> - If set to false, a hidden input with name
189
191
  # utf8 is not output.
190
192
  # * <tt>:html</tt> - Optional HTML attributes for the form tag.
@@ -201,9 +203,9 @@ module ActionView
201
203
  # <%= f.submit %>
202
204
  # <% end %>
203
205
  #
204
- # This also works for the methods in FormOptionHelper and DateHelper that
206
+ # This also works for the methods in FormOptionsHelper and DateHelper that
205
207
  # are designed to work with an object as base, like
206
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
208
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
207
209
  #
208
210
  # === #form_for with a model object
209
211
  #
@@ -320,10 +322,8 @@ module ActionView
320
322
  # remote: true
321
323
  #
322
324
  # in the options hash creates a form that will allow the unobtrusive JavaScript drivers to modify its
323
- # behavior. The expected default behavior is an XMLHttpRequest in the background instead of the regular
324
- # POST arrangement, but ultimately the behavior is the choice of the JavaScript driver implementor.
325
- # Even though it's using JavaScript to serialize the form elements, the form submission will work just like
326
- # 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>).
327
327
  #
328
328
  # Example:
329
329
  #
@@ -416,13 +416,13 @@ module ActionView
416
416
  #
417
417
  # To set an authenticity token you need to pass an <tt>:authenticity_token</tt> parameter
418
418
  #
419
- # <%= 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| %>
420
420
  # ...
421
421
  # <% end %>
422
422
  #
423
423
  # If you don't want to an authenticity token field be rendered at all just pass <tt>false</tt>:
424
424
  #
425
- # <%= form_for @invoice, url: external_url, authenticity_token: false do |f|
425
+ # <%= form_for @invoice, url: external_url, authenticity_token: false do |f| %>
426
426
  # ...
427
427
  # <% end %>
428
428
  def form_for(record, options = {}, &block)
@@ -474,7 +474,9 @@ module ActionView
474
474
  end
475
475
  private :apply_form_for_options!
476
476
 
477
- mattr_accessor(:form_with_generates_remote_forms) { true }
477
+ mattr_accessor :form_with_generates_remote_forms, default: true
478
+
479
+ mattr_accessor :form_with_generates_ids, default: false
478
480
 
479
481
  # Creates a form tag based on mixing URLs, scopes, or models.
480
482
  #
@@ -531,11 +533,6 @@ module ActionView
531
533
  # accessible as <tt>params[:title]</tt> and <tt>params[:post][:title]</tt>
532
534
  # respectively.
533
535
  #
534
- # By default +form_with+ attaches the <tt>data-remote</tt> attribute
535
- # submitting the form via an XMLHTTPRequest in the background if an
536
- # Unobtrusive JavaScript driver, like rails-ujs, is used. See the
537
- # <tt>:local</tt> option for more.
538
- #
539
536
  # For ease of comparison the examples above left out the submit button,
540
537
  # as well as the auto generated hidden fields that enable UTF-8 support
541
538
  # and adds an authenticity token needed for cross site request forgery
@@ -586,6 +583,9 @@ module ActionView
586
583
  # Skipped if a <tt>:url</tt> is passed.
587
584
  # * <tt>:scope</tt> - The scope to prefix input field names with and
588
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
589
  # * <tt>:model</tt> - A model object to infer the <tt>:url</tt> and
590
590
  # <tt>:scope</tt> by, plus fill out input field values.
591
591
  # So if a +title+ attribute is set to "Ahoy!" then a +title+ input
@@ -604,10 +604,12 @@ module ActionView
604
604
  # This is helpful when fragment-caching the form. Remote forms
605
605
  # get the authenticity token from the <tt>meta</tt> tag, so embedding is
606
606
  # unnecessary unless you support browsers without JavaScript.
607
- # * <tt>:local</tt> - By default form submits are remote and unobstrusive XHRs.
608
- # Disable remote submits with <tt>local: true</tt>.
609
- # * <tt>:skip_enforcing_utf8</tt> - By default a hidden field named +utf8+
610
- # is output to enforce UTF-8 submits. Set to true to skip the field.
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.
611
613
  # * <tt>:builder</tt> - Override the object used to build the form.
612
614
  # * <tt>:id</tt> - Optional HTML id attribute.
613
615
  # * <tt>:class</tt> - Optional HTML class attribute.
@@ -638,16 +640,6 @@ module ActionView
638
640
  #
639
641
  # Where <tt>@document = Document.find(params[:id])</tt>.
640
642
  #
641
- # When using labels +form_with+ requires setting the id on the field being
642
- # labelled:
643
- #
644
- # <%= form_with(model: @post) do |form| %>
645
- # <%= form.label :title %>
646
- # <%= form.text_field :title, id: :post_title %>
647
- # <% end %>
648
- #
649
- # See +label+ for more on how the +for+ attribute is derived.
650
- #
651
643
  # === Mixing with other form helpers
652
644
  #
653
645
  # While +form_with+ uses a FormBuilder object it's possible to mix and
@@ -664,9 +656,9 @@ module ActionView
664
656
  # <%= form.submit %>
665
657
  # <% end %>
666
658
  #
667
- # Same goes for the methods in FormOptionHelper and DateHelper designed
659
+ # Same goes for the methods in FormOptionsHelper and DateHelper designed
668
660
  # to work with an object as a base, like
669
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
661
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
670
662
  #
671
663
  # === Setting the method
672
664
  #
@@ -742,9 +734,9 @@ module ActionView
742
734
  # def labelled_form_with(**options, &block)
743
735
  # form_with(**options.merge(builder: LabellingFormBuilder), &block)
744
736
  # end
745
- def form_with(model: nil, scope: nil, url: nil, format: nil, **options)
737
+ def form_with(model: nil, scope: nil, url: nil, format: nil, **options, &block)
746
738
  options[:allow_method_names_outside_object] = true
747
- options[:skip_default_ids] = true
739
+ options[:skip_default_ids] = !form_with_generates_ids
748
740
 
749
741
  if model
750
742
  url ||= polymorphic_path(model, format: format)
@@ -755,13 +747,13 @@ module ActionView
755
747
 
756
748
  if block_given?
757
749
  builder = instantiate_builder(scope, model, options)
758
- output = capture(builder, &Proc.new)
750
+ output = capture(builder, &block)
759
751
  options[:multipart] ||= builder.multipart?
760
752
 
761
- html_options = html_options_for_form_with(url, model, options)
753
+ html_options = html_options_for_form_with(url, model, **options)
762
754
  form_tag_with_body(html_options, output)
763
755
  else
764
- html_options = html_options_for_form_with(url, model, options)
756
+ html_options = html_options_for_form_with(url, model, **options)
765
757
  form_tag_html(html_options)
766
758
  end
767
759
  end
@@ -823,9 +815,9 @@ module ActionView
823
815
  # _class_ of the model object, e.g. if <tt>@person.permission</tt>, is
824
816
  # of class +Permission+, the field will still be named <tt>permission[admin]</tt>.
825
817
  #
826
- # Note: This also works for the methods in FormOptionHelper and
818
+ # Note: This also works for the methods in FormOptionsHelper and
827
819
  # DateHelper that are designed to work with an object as base, like
828
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
820
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
829
821
  #
830
822
  # === Nested Attributes Examples
831
823
  #
@@ -891,7 +883,7 @@ module ActionView
891
883
  #
892
884
  # Now, when you use a form element with the <tt>_destroy</tt> parameter,
893
885
  # with a value that evaluates to +true+, you will destroy the associated
894
- # model (eg. 1, '1', true, or 'true'):
886
+ # model (e.g. 1, '1', true, or 'true'):
895
887
  #
896
888
  # <%= form_for @person do |person_form| %>
897
889
  # ...
@@ -980,7 +972,7 @@ module ActionView
980
972
  # This will allow you to specify which models to destroy in the
981
973
  # attributes hash by adding a form element for the <tt>_destroy</tt>
982
974
  # parameter with a value that evaluates to +true+
983
- # (eg. 1, '1', true, or 'true'):
975
+ # (e.g. 1, '1', true, or 'true'):
984
976
  #
985
977
  # <%= form_for @person do |person_form| %>
986
978
  # ...
@@ -1020,14 +1012,13 @@ module ActionView
1020
1012
  # <%= fields :comment do |fields| %>
1021
1013
  # <%= fields.text_field :body %>
1022
1014
  # <% end %>
1023
- # # => <input type="text" name="comment[body]>
1015
+ # # => <input type="text" name="comment[body]">
1024
1016
  #
1025
1017
  # # Using a model infers the scope and assigns field values:
1026
- # <%= fields model: Comment.new(body: "full bodied") do |fields| %<
1018
+ # <%= fields model: Comment.new(body: "full bodied") do |fields| %>
1027
1019
  # <%= fields.text_field :body %>
1028
1020
  # <% end %>
1029
- # # =>
1030
- # <input type="text" name="comment[body] value="full bodied">
1021
+ # # => <input type="text" name="comment[body]" value="full bodied">
1031
1022
  #
1032
1023
  # # Using +fields+ with +form_with+:
1033
1024
  # <%= form_with model: @post do |form| %>
@@ -1042,16 +1033,6 @@ module ActionView
1042
1033
  # or model is yielded, so any generated field names are prefixed with
1043
1034
  # either the passed scope or the scope inferred from the <tt>:model</tt>.
1044
1035
  #
1045
- # When using labels +fields+ requires setting the id on the field being
1046
- # labelled:
1047
- #
1048
- # <%= fields :comment do |fields| %>
1049
- # <%= fields.label :body %>
1050
- # <%= fields.text_field :body, id: :comment_body %>
1051
- # <% end %>
1052
- #
1053
- # See +label+ for more on how the +for+ attribute is derived.
1054
- #
1055
1036
  # === Mixing with other form helpers
1056
1037
  #
1057
1038
  # While +form_with+ uses a FormBuilder object it's possible to mix and
@@ -1065,12 +1046,12 @@ module ActionView
1065
1046
  # <%= check_box_tag "comment[all_caps]", "1", @comment.commenter.hulk_mode? %>
1066
1047
  # <% end %>
1067
1048
  #
1068
- # Same goes for the methods in FormOptionHelper and DateHelper designed
1049
+ # Same goes for the methods in FormOptionsHelper and DateHelper designed
1069
1050
  # to work with an object as a base, like
1070
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
1051
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
1071
1052
  def fields(scope = nil, model: nil, **options, &block)
1072
1053
  options[:allow_method_names_outside_object] = true
1073
- options[:skip_default_ids] = true
1054
+ options[:skip_default_ids] = !form_with_generates_ids
1074
1055
 
1075
1056
  if model
1076
1057
  scope ||= model_name_from_record_or_class(model).param_key
@@ -1124,6 +1105,16 @@ module ActionView
1124
1105
  # label(:post, :privacy, "Public Post", value: "public")
1125
1106
  # # => <label for="post_privacy_public">Public Post</label>
1126
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
+ #
1127
1118
  # label(:post, :terms) do
1128
1119
  # raw('Accept <a href="/terms">Terms</a>.')
1129
1120
  # end
@@ -1144,6 +1135,9 @@ module ActionView
1144
1135
  # text_field(:post, :title, class: "create_input")
1145
1136
  # # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
1146
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
+ #
1147
1141
  # text_field(:session, :user, onchange: "if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }")
1148
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!'); }"/>
1149
1143
  #
@@ -1221,7 +1215,7 @@ module ActionView
1221
1215
  # file_field(:attachment, :file, class: 'file_input')
1222
1216
  # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
1223
1217
  def file_field(object_name, method, options = {})
1224
- 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
1225
1219
  end
1226
1220
 
1227
1221
  # Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
@@ -1536,10 +1530,10 @@ module ActionView
1536
1530
 
1537
1531
  private
1538
1532
  def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: !form_with_generates_remote_forms,
1539
- skip_enforcing_utf8: false, **options)
1533
+ skip_enforcing_utf8: nil, **options)
1540
1534
  html_options = options.slice(:id, :class, :multipart, :method, :data).merge(html)
1541
1535
  html_options[:method] ||= :patch if model.respond_to?(:persisted?) && model.persisted?
1542
- html_options[:enforce_utf8] = !skip_enforcing_utf8
1536
+ html_options[:enforce_utf8] = !skip_enforcing_utf8 unless skip_enforcing_utf8.nil?
1543
1537
 
1544
1538
  html_options[:enctype] = "multipart/form-data" if html_options.delete(:multipart)
1545
1539
 
@@ -1597,7 +1591,7 @@ module ActionView
1597
1591
  # In the above block, a +FormBuilder+ object is yielded as the
1598
1592
  # +person_form+ variable. This allows you to generate the +text_field+
1599
1593
  # and +check_box+ fields by specifying their eponymous methods, which
1600
- # modify the underlying template and associates the +@person+ model object
1594
+ # modify the underlying template and associates the <tt>@person</tt> model object
1601
1595
  # with the form.
1602
1596
  #
1603
1597
  # The +FormBuilder+ object can be thought of as serving as a proxy for the
@@ -1636,14 +1630,15 @@ module ActionView
1636
1630
  include ModelNaming
1637
1631
 
1638
1632
  # The methods which wrap a form helper call.
1639
- class_attribute :field_helpers
1640
- self.field_helpers = [:fields_for, :fields, :label, :text_field, :password_field,
1641
- :hidden_field, :file_field, :text_area, :check_box,
1642
- :radio_button, :color_field, :search_field,
1643
- :telephone_field, :phone_field, :date_field,
1644
- :time_field, :datetime_field, :datetime_local_field,
1645
- :month_field, :week_field, :url_field, :email_field,
1646
- :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
+ ]
1647
1642
 
1648
1643
  attr_accessor :object_name, :object, :options
1649
1644
 
@@ -1674,11 +1669,12 @@ module ActionView
1674
1669
  @nested_child_index = {}
1675
1670
  @object_name, @object, @template, @options = object_name, object, template, options
1676
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)
1677
1673
 
1678
1674
  convert_to_legacy_options(@options)
1679
1675
 
1680
- if @object_name.to_s.match(/\[\]$/)
1681
- if (object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}")) && object.respond_to?(:to_param)
1676
+ if @object_name&.end_with?("[]")
1677
+ if (object ||= @template.instance_variable_get("@#{@object_name[0..-3]}")) && object.respond_to?(:to_param)
1682
1678
  @auto_index = object.to_param
1683
1679
  else
1684
1680
  raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
@@ -1689,11 +1685,232 @@ module ActionView
1689
1685
  @index = options[:index] || options[:child_index]
1690
1686
  end
1691
1687
 
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
+
1692
1909
  (field_helpers - [:label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field]).each do |selector|
1693
1910
  class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
1694
1911
  def #{selector}(method, options = {}) # def text_field(method, options = {})
1695
- @template.send( # @template.send(
1696
- #{selector.inspect}, # "text_field",
1912
+ @template.public_send( # @template.public_send(
1913
+ #{selector.inspect}, # :text_field,
1697
1914
  @object_name, # @object_name,
1698
1915
  method, # method,
1699
1916
  objectify_options(options)) # objectify_options(options))
@@ -1758,9 +1975,9 @@ module ActionView
1758
1975
  # _class_ of the model object, e.g. if <tt>@person.permission</tt>, is
1759
1976
  # of class +Permission+, the field will still be named <tt>permission[admin]</tt>.
1760
1977
  #
1761
- # Note: This also works for the methods in FormOptionHelper and
1978
+ # Note: This also works for the methods in FormOptionsHelper and
1762
1979
  # DateHelper that are designed to work with an object as base, like
1763
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
1980
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
1764
1981
  #
1765
1982
  # === Nested Attributes Examples
1766
1983
  #
@@ -1826,7 +2043,7 @@ module ActionView
1826
2043
  #
1827
2044
  # Now, when you use a form element with the <tt>_destroy</tt> parameter,
1828
2045
  # with a value that evaluates to +true+, you will destroy the associated
1829
- # model (eg. 1, '1', true, or 'true'):
2046
+ # model (e.g. 1, '1', true, or 'true'):
1830
2047
  #
1831
2048
  # <%= form_for @person do |person_form| %>
1832
2049
  # ...
@@ -1915,7 +2132,7 @@ module ActionView
1915
2132
  # This will allow you to specify which models to destroy in the
1916
2133
  # attributes hash by adding a form element for the <tt>_destroy</tt>
1917
2134
  # parameter with a value that evaluates to +true+
1918
- # (eg. 1, '1', true, or 'true'):
2135
+ # (e.g. 1, '1', true, or 'true'):
1919
2136
  #
1920
2137
  # <%= form_for @person do |person_form| %>
1921
2138
  # ...
@@ -1962,15 +2179,14 @@ module ActionView
1962
2179
  index = if options.has_key?(:index)
1963
2180
  options[:index]
1964
2181
  elsif defined?(@auto_index)
1965
- object_name = object_name.to_s.sub(/\[\]$/, "")
2182
+ object_name = object_name.to_s.delete_suffix("[]")
1966
2183
  @auto_index
1967
2184
  end
1968
2185
 
1969
2186
  record_name = if index
1970
2187
  "#{object_name}[#{index}][#{record_name}]"
1971
- elsif record_name.to_s.end_with?("[]")
1972
- record_name = record_name.to_s.sub(/(.*)\[\]$/, "[\\1][#{record_object.id}]")
1973
- "#{object_name}#{record_name}"
2188
+ elsif record_name.end_with?("[]")
2189
+ "#{object_name}[#{record_name[0..-3]}][#{record_object.id}]"
1974
2190
  else
1975
2191
  "#{object_name}[#{record_name}]"
1976
2192
  end
@@ -1982,11 +2198,11 @@ module ActionView
1982
2198
  # See the docs for the <tt>ActionView::FormHelper.fields</tt> helper method.
1983
2199
  def fields(scope = nil, model: nil, **options, &block)
1984
2200
  options[:allow_method_names_outside_object] = true
1985
- options[:skip_default_ids] = true
2201
+ options[:skip_default_ids] = !FormHelper.form_with_generates_ids
1986
2202
 
1987
2203
  convert_to_legacy_options(options)
1988
2204
 
1989
- fields_for(scope || model, model, **options, &block)
2205
+ fields_for(scope || model, model, options, &block)
1990
2206
  end
1991
2207
 
1992
2208
  # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
@@ -2033,6 +2249,24 @@ module ActionView
2033
2249
  # label(:privacy, "Public Post", value: "public")
2034
2250
  # # => <label for="post_privacy_public">Public Post</label>
2035
2251
  #
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
+ # ])
2267
+ # end
2268
+ # # => <label for="post_cost"><span class="cost_label error_label">Total cost</span></label>
2269
+ #
2036
2270
  # label(:terms) do
2037
2271
  # raw('Accept <a href="/terms">Terms</a>.')
2038
2272
  # end
@@ -2192,11 +2426,11 @@ module ActionView
2192
2426
  # <%= f.submit %>
2193
2427
  # <% end %>
2194
2428
  #
2195
- # In the example above, if @post is a new record, it will use "Create Post" as
2196
- # 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".
2197
2431
  #
2198
- # Those labels can be customized using I18n, under the helpers.submit key and accept
2199
- # 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:
2200
2434
  #
2201
2435
  # en:
2202
2436
  # helpers:
@@ -2204,7 +2438,7 @@ module ActionView
2204
2438
  # create: "Create a %{model}"
2205
2439
  # update: "Confirm changes to %{model}"
2206
2440
  #
2207
- # It also searches for a key specific for the given object:
2441
+ # It also searches for a key specific to the given object:
2208
2442
  #
2209
2443
  # en:
2210
2444
  # helpers:
@@ -2225,11 +2459,11 @@ module ActionView
2225
2459
  # <%= f.button %>
2226
2460
  # <% end %>
2227
2461
  #
2228
- # In the example above, if @post is a new record, it will use "Create Post" as
2229
- # 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".
2230
2464
  #
2231
- # Those labels can be customized using I18n, under the helpers.submit key
2232
- # (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:
2233
2467
  #
2234
2468
  # en:
2235
2469
  # helpers:
@@ -2237,7 +2471,7 @@ module ActionView
2237
2471
  # create: "Create a %{model}"
2238
2472
  # update: "Confirm changes to %{model}"
2239
2473
  #
2240
- # It also searches for a key specific for the given object:
2474
+ # It also searches for a key specific to the given object:
2241
2475
  #
2242
2476
  # en:
2243
2477
  # helpers:
@@ -2256,19 +2490,33 @@ module ActionView
2256
2490
  # # <strong>Ask me!</strong>
2257
2491
  # # </button>
2258
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
+ #
2259
2500
  def button(value = nil, options = {}, &block)
2260
2501
  value, options = nil, value if value.is_a?(Hash)
2261
2502
  value ||= submit_default_value
2262
- @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)
2263
2509
  end
2264
2510
 
2265
- def emitted_hidden_id?
2511
+ def emitted_hidden_id? # :nodoc:
2266
2512
  @emitted_hidden_id ||= nil
2267
2513
  end
2268
2514
 
2269
2515
  private
2270
2516
  def objectify_options(options)
2271
- @default_options.merge(options.merge(object: @object))
2517
+ result = @default_options.merge(options)
2518
+ result[:object] = @object
2519
+ result
2272
2520
  end
2273
2521
 
2274
2522
  def submit_default_value
@@ -2282,7 +2530,12 @@ module ActionView
2282
2530
  end
2283
2531
 
2284
2532
  defaults = []
2285
- 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
2286
2539
  defaults << :"helpers.submit.#{key}"
2287
2540
  defaults << "#{key.to_s.humanize} #{model}"
2288
2541
 
@@ -2298,9 +2551,9 @@ module ActionView
2298
2551
  association = convert_to_model(association)
2299
2552
 
2300
2553
  if association.respond_to?(:persisted?)
2301
- association = [association] if @object.send(association_name).respond_to?(:to_ary)
2554
+ association = [association] if @object.public_send(association_name).respond_to?(:to_ary)
2302
2555
  elsif !association.respond_to?(:to_ary)
2303
- association = @object.send(association_name)
2556
+ association = @object.public_send(association_name)
2304
2557
  end
2305
2558
 
2306
2559
  if association.respond_to?(:to_ary)
@@ -2347,8 +2600,6 @@ module ActionView
2347
2600
  end
2348
2601
 
2349
2602
  ActiveSupport.on_load(:action_view) do
2350
- cattr_accessor(:default_form_builder, instance_writer: false, instance_reader: false) do
2351
- ::ActionView::Helpers::FormBuilder
2352
- end
2603
+ cattr_accessor :default_form_builder, instance_writer: false, instance_reader: false, default: ::ActionView::Helpers::FormBuilder
2353
2604
  end
2354
2605
  end