actionview 5.2.4.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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +221 -93
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -3
  5. data/lib/action_view.rb +7 -2
  6. data/lib/action_view/base.rb +81 -15
  7. data/lib/action_view/buffers.rb +15 -0
  8. data/lib/action_view/cache_expiry.rb +52 -0
  9. data/lib/action_view/context.rb +5 -9
  10. data/lib/action_view/dependency_tracker.rb +10 -4
  11. data/lib/action_view/digestor.rb +15 -22
  12. data/lib/action_view/flows.rb +0 -1
  13. data/lib/action_view/gem_version.rb +4 -4
  14. data/lib/action_view/helpers.rb +0 -2
  15. data/lib/action_view/helpers/active_model_helper.rb +0 -1
  16. data/lib/action_view/helpers/asset_tag_helper.rb +63 -46
  17. data/lib/action_view/helpers/asset_url_helper.rb +9 -6
  18. data/lib/action_view/helpers/atom_feed_helper.rb +2 -1
  19. data/lib/action_view/helpers/cache_helper.rb +23 -22
  20. data/lib/action_view/helpers/capture_helper.rb +4 -0
  21. data/lib/action_view/helpers/csp_helper.rb +4 -2
  22. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  23. data/lib/action_view/helpers/date_helper.rb +73 -30
  24. data/lib/action_view/helpers/form_helper.rb +305 -37
  25. data/lib/action_view/helpers/form_options_helper.rb +23 -23
  26. data/lib/action_view/helpers/form_tag_helper.rb +19 -16
  27. data/lib/action_view/helpers/javascript_helper.rb +12 -11
  28. data/lib/action_view/helpers/number_helper.rb +14 -8
  29. data/lib/action_view/helpers/output_safety_helper.rb +1 -1
  30. data/lib/action_view/helpers/rendering_helper.rb +17 -7
  31. data/lib/action_view/helpers/sanitize_helper.rb +12 -18
  32. data/lib/action_view/helpers/tag_helper.rb +98 -22
  33. data/lib/action_view/helpers/tags/base.rb +18 -11
  34. data/lib/action_view/helpers/tags/check_box.rb +0 -1
  35. data/lib/action_view/helpers/tags/collection_check_boxes.rb +0 -1
  36. data/lib/action_view/helpers/tags/collection_helpers.rb +0 -1
  37. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +0 -1
  38. data/lib/action_view/helpers/tags/color_field.rb +1 -2
  39. data/lib/action_view/helpers/tags/date_field.rb +1 -2
  40. data/lib/action_view/helpers/tags/date_select.rb +2 -3
  41. data/lib/action_view/helpers/tags/datetime_field.rb +0 -1
  42. data/lib/action_view/helpers/tags/datetime_local_field.rb +1 -2
  43. data/lib/action_view/helpers/tags/label.rb +4 -1
  44. data/lib/action_view/helpers/tags/month_field.rb +1 -2
  45. data/lib/action_view/helpers/tags/radio_button.rb +0 -1
  46. data/lib/action_view/helpers/tags/select.rb +1 -2
  47. data/lib/action_view/helpers/tags/text_field.rb +0 -1
  48. data/lib/action_view/helpers/tags/time_field.rb +1 -2
  49. data/lib/action_view/helpers/tags/translator.rb +1 -6
  50. data/lib/action_view/helpers/tags/week_field.rb +1 -2
  51. data/lib/action_view/helpers/text_helper.rb +3 -4
  52. data/lib/action_view/helpers/translation_helper.rb +93 -55
  53. data/lib/action_view/helpers/url_helper.rb +121 -27
  54. data/lib/action_view/layouts.rb +8 -10
  55. data/lib/action_view/log_subscriber.rb +30 -15
  56. data/lib/action_view/lookup_context.rb +63 -35
  57. data/lib/action_view/path_set.rb +3 -12
  58. data/lib/action_view/railtie.rb +42 -26
  59. data/lib/action_view/record_identifier.rb +2 -3
  60. data/lib/action_view/renderer/abstract_renderer.rb +142 -11
  61. data/lib/action_view/renderer/collection_renderer.rb +196 -0
  62. data/lib/action_view/renderer/object_renderer.rb +34 -0
  63. data/lib/action_view/renderer/partial_renderer.rb +21 -273
  64. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +61 -16
  65. data/lib/action_view/renderer/renderer.rb +59 -4
  66. data/lib/action_view/renderer/streaming_template_renderer.rb +10 -8
  67. data/lib/action_view/renderer/template_renderer.rb +35 -27
  68. data/lib/action_view/rendering.rb +54 -33
  69. data/lib/action_view/routing_url_for.rb +13 -12
  70. data/lib/action_view/template.rb +66 -75
  71. data/lib/action_view/template/error.rb +30 -15
  72. data/lib/action_view/template/handlers.rb +1 -1
  73. data/lib/action_view/template/handlers/builder.rb +2 -2
  74. data/lib/action_view/template/handlers/erb.rb +16 -11
  75. data/lib/action_view/template/handlers/erb/erubi.rb +15 -9
  76. data/lib/action_view/template/handlers/html.rb +1 -1
  77. data/lib/action_view/template/handlers/raw.rb +2 -2
  78. data/lib/action_view/template/html.rb +5 -6
  79. data/lib/action_view/template/inline.rb +22 -0
  80. data/lib/action_view/template/raw_file.rb +25 -0
  81. data/lib/action_view/template/renderable.rb +24 -0
  82. data/lib/action_view/template/resolver.rb +191 -150
  83. data/lib/action_view/template/sources.rb +13 -0
  84. data/lib/action_view/template/sources/file.rb +17 -0
  85. data/lib/action_view/template/text.rb +2 -3
  86. data/lib/action_view/test_case.rb +21 -29
  87. data/lib/action_view/testing/resolvers.rb +18 -27
  88. data/lib/action_view/unbound_template.rb +31 -0
  89. data/lib/action_view/view_paths.rb +59 -38
  90. data/lib/assets/compiled/rails-ujs.js +29 -3
  91. metadata +32 -21
  92. data/lib/action_view/helpers/record_tag_helper.rb +0 -23
@@ -52,7 +52,7 @@ module ActionView
52
52
  # solution being slower. You should be sure to measure your actual
53
53
  # performance across targeted browsers both before and after this change.
54
54
  #
55
- # To implement the corresponding hosts you can either setup four actual
55
+ # To implement the corresponding hosts you can either set up four actual
56
56
  # hosts or use wildcard DNS to CNAME the wildcard to a single asset host.
57
57
  # You can read more about setting up your DNS CNAME records from your ISP.
58
58
  #
@@ -80,7 +80,7 @@ module ActionView
80
80
  # absolute path of the asset, for example "/assets/rails.png".
81
81
  #
82
82
  # ActionController::Base.asset_host = Proc.new { |source|
83
- # if source.ends_with?('.css')
83
+ # if source.end_with?('.css')
84
84
  # "http://stylesheets.example.com"
85
85
  # else
86
86
  # "http://assets.example.com"
@@ -98,8 +98,9 @@ module ActionView
98
98
  # have SSL certificates for each of the asset hosts this technique allows you
99
99
  # to avoid warnings in the client about mixed media.
100
100
  # Note that the +request+ parameter might not be supplied, e.g. when the assets
101
- # are precompiled via a Rake task. Make sure to use a +Proc+ instead of a lambda,
102
- # since a +Proc+ allows missing parameters and sets them to +nil+.
101
+ # are precompiled with the command `bin/rails assets:precompile`. Make sure to use a
102
+ # +Proc+ instead of a lambda, since a +Proc+ allows missing parameters and sets them
103
+ # to +nil+.
103
104
  #
104
105
  # config.action_controller.asset_host = Proc.new { |source, request|
105
106
  # if request && request.ssl?
@@ -132,6 +133,8 @@ module ActionView
132
133
  # which is implemented by sprockets-rails.
133
134
  #
134
135
  # asset_path("application.js") # => "/assets/application-60aa4fdc5cea14baf5400fba1abf4f2a46a5166bad4772b1effe341570f07de9.js"
136
+ # asset_path('application.js', host: 'example.com') # => "//example.com/assets/application.js"
137
+ # asset_path("application.js", host: 'example.com', protocol: 'https') # => "https://example.com/assets/application.js"
135
138
  #
136
139
  # === Without the asset pipeline (<tt>skip_pipeline: true</tt>)
137
140
  #
@@ -187,7 +190,7 @@ module ActionView
187
190
  return "" if source.blank?
188
191
  return source if URI_REGEXP.match?(source)
189
192
 
190
- tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, "".freeze)
193
+ tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, "")
191
194
 
192
195
  if extname = compute_asset_extname(source, options)
193
196
  source = "#{source}#{extname}"
@@ -203,7 +206,7 @@ module ActionView
203
206
 
204
207
  relative_url_root = defined?(config.relative_url_root) && config.relative_url_root
205
208
  if relative_url_root
206
- source = File.join(relative_url_root, source) unless source.starts_with?("#{relative_url_root}/")
209
+ source = File.join(relative_url_root, source) unless source.start_with?("#{relative_url_root}/")
207
210
  end
208
211
 
209
212
  if host = compute_asset_host(source, options)
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "set"
4
+ require "active_support/core_ext/symbol/starts_ends_with"
4
5
 
5
6
  module ActionView
6
7
  # = Action View Atom Feed Helpers
@@ -115,7 +116,7 @@ module ActionView
115
116
  end
116
117
 
117
118
  feed_opts = { "xml:lang" => options[:language] || "en-US", "xmlns" => "http://www.w3.org/2005/Atom" }
118
- feed_opts.merge!(options).reject! { |k, v| !k.to_s.match(/^xml/) }
119
+ feed_opts.merge!(options).select! { |k, _| k.start_with?("xml") }
119
120
 
120
121
  xml.feed(feed_opts) do
121
122
  xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}")
@@ -24,8 +24,8 @@ module ActionView
24
24
  # This approach will assume that when a new topic is added, you'll touch
25
25
  # the project. The cache key generated from this call will be something like:
26
26
  #
27
- # views/template/action.html.erb:7a1156131a6928cb0026877f8b749ac9/projects/123
28
- # ^template path ^template tree digest ^class ^id
27
+ # views/template/action:7a1156131a6928cb0026877f8b749ac9/projects/123
28
+ # ^template path ^template tree digest ^class ^id
29
29
  #
30
30
  # This cache key is stable, but it's combined with a cache version derived from the project
31
31
  # record. When the project updated_at is touched, the #cache_version changes, even
@@ -165,8 +165,8 @@ module ActionView
165
165
  # expire the cache.
166
166
  def cache(name = {}, options = {}, &block)
167
167
  if controller.respond_to?(:perform_caching) && controller.perform_caching
168
- name_options = options.slice(:skip_digest, :virtual_path)
169
- safe_concat(fragment_for(cache_fragment_name(name, name_options), options, &block))
168
+ name_options = options.slice(:skip_digest)
169
+ safe_concat(fragment_for(cache_fragment_name(name, **name_options), options, &block))
170
170
  else
171
171
  yield
172
172
  end
@@ -201,34 +201,35 @@ module ActionView
201
201
  end
202
202
 
203
203
  # This helper returns the name of a cache key for a given fragment cache
204
- # call. By supplying +skip_digest:+ true to cache, the digestion of cache
204
+ # call. By supplying <tt>skip_digest: true</tt> to cache, the digestion of cache
205
205
  # fragments can be manually bypassed. This is useful when cache fragments
206
206
  # cannot be manually expired unless you know the exact key which is the
207
207
  # case when using memcached.
208
- #
209
- # The digest will be generated using +virtual_path:+ if it is provided.
210
- #
211
- def cache_fragment_name(name = {}, skip_digest: nil, virtual_path: nil)
208
+ def cache_fragment_name(name = {}, skip_digest: nil, digest_path: nil)
212
209
  if skip_digest
213
210
  name
214
211
  else
215
- fragment_name_with_digest(name, virtual_path)
212
+ fragment_name_with_digest(name, digest_path)
216
213
  end
217
214
  end
218
215
 
219
- private
216
+ def digest_path_from_template(template) # :nodoc:
217
+ digest = Digestor.digest(name: template.virtual_path, format: template.format, finder: lookup_context, dependencies: view_cache_dependencies)
220
218
 
221
- def fragment_name_with_digest(name, virtual_path)
222
- virtual_path ||= @virtual_path
219
+ if digest.present?
220
+ "#{template.virtual_path}:#{digest}"
221
+ else
222
+ template.virtual_path
223
+ end
224
+ end
223
225
 
224
- if virtual_path
225
- name = controller.url_for(name).split("://").last if name.is_a?(Hash)
226
+ private
227
+ def fragment_name_with_digest(name, digest_path)
228
+ name = controller.url_for(name).split("://").last if name.is_a?(Hash)
226
229
 
227
- if digest = Digestor.digest(name: virtual_path, finder: lookup_context, dependencies: view_cache_dependencies).presence
228
- [ "#{virtual_path}:#{digest}", name ]
229
- else
230
- [ virtual_path, name ]
231
- end
230
+ if @current_template&.virtual_path || digest_path
231
+ digest_path ||= digest_path_from_template(@current_template)
232
+ [ digest_path, name ]
232
233
  else
233
234
  name
234
235
  end
@@ -236,10 +237,10 @@ module ActionView
236
237
 
237
238
  def fragment_for(name = {}, options = nil, &block)
238
239
  if content = read_fragment_for(name, options)
239
- @view_renderer.cache_hits[@virtual_path] = :hit if defined?(@view_renderer)
240
+ @view_renderer.cache_hits[@current_template&.virtual_path] = :hit if defined?(@view_renderer)
240
241
  content
241
242
  else
242
- @view_renderer.cache_hits[@virtual_path] = :miss if defined?(@view_renderer)
243
+ @view_renderer.cache_hits[@current_template&.virtual_path] = :miss if defined?(@view_renderer)
243
244
  write_fragment_for(name, options, &block)
244
245
  end
245
246
  end
@@ -36,6 +36,10 @@ module ActionView
36
36
  # </body>
37
37
  # </html>
38
38
  #
39
+ # The return of capture is the string generated by the block. For Example:
40
+ #
41
+ # @greeting # => "Welcome to my shiny new web page! The date and time is 2018-09-06 11:09:16 -0500"
42
+ #
39
43
  def capture(*args)
40
44
  value = nil
41
45
  buffer = with_output_buffer { value = yield(*args) }
@@ -14,9 +14,11 @@ module ActionView
14
14
  # This is used by the Rails UJS helper to create dynamically
15
15
  # loaded inline <script> elements.
16
16
  #
17
- def csp_meta_tag
17
+ def csp_meta_tag(**options)
18
18
  if content_security_policy?
19
- tag("meta", name: "csp-nonce", content: content_security_policy_nonce)
19
+ options[:name] = "csp-nonce"
20
+ options[:content] = content_security_policy_nonce
21
+ tag("meta", options)
20
22
  end
21
23
  end
22
24
  end
@@ -20,7 +20,7 @@ module ActionView
20
20
  # "X-CSRF-Token" HTTP header. If you are using rails-ujs this happens automatically.
21
21
  #
22
22
  def csrf_meta_tags
23
- if protect_against_forgery?
23
+ if defined?(protect_against_forgery?) && protect_against_forgery?
24
24
  [
25
25
  tag("meta", name: "csrf-param", content: request_forgery_protection_token),
26
26
  tag("meta", name: "csrf-token", content: form_authenticity_token)
@@ -197,14 +197,15 @@ module ActionView
197
197
  # and +:name+ (string). A format string would be something like "%{name} (%<number>02d)" for example.
198
198
  # See <tt>Kernel.sprintf</tt> for documentation on format sequences.
199
199
  # * <tt>:date_separator</tt> - Specifies a string to separate the date fields. Default is "" (i.e. nothing).
200
- # * <tt>:time_separator</tt> - Specifies a string to separate the time fields. Default is "" (i.e. nothing).
201
- # * <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; ".
202
202
  # * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Date.today.year - 5</tt> if
203
203
  # you are creating new record. While editing existing record, <tt>:start_year</tt> defaults to
204
204
  # the current selected year minus 5.
205
205
  # * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Date.today.year + 5</tt> if
206
206
  # you are creating new record. While editing existing record, <tt>:end_year</tt> defaults to
207
207
  # the current selected year plus 5.
208
+ # * <tt>:year_format</tt> - Set format of years for year select. Lambda should be passed.
208
209
  # * <tt>:discard_day</tt> - Set to true if you don't want to show a day select. This includes the day
209
210
  # as a hidden field instead of showing a select field. Also note that this implicitly sets the day to be the
210
211
  # first of the given month in order to not create invalid dates like 31 February.
@@ -275,6 +276,9 @@ module ActionView
275
276
  # # Generates a date select with custom prompts.
276
277
  # date_select("article", "written_on", prompt: { day: 'Select day', month: 'Select month', year: 'Select year' })
277
278
  #
279
+ # # Generates a date select with custom year format.
280
+ # date_select("article", "written_on", year_format: ->(year) { "Heisei #{year - 1988}" })
281
+ #
278
282
  # The selects are prepared for multi-parameter assignment to an Active Record object.
279
283
  #
280
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
@@ -302,15 +306,15 @@ module ActionView
302
306
  # time_select("article", "start_time", include_seconds: true)
303
307
  #
304
308
  # # You can set the <tt>:minute_step</tt> to 15 which will give you: 00, 15, 30, and 45.
305
- # time_select 'game', 'game_time', {minute_step: 15}
309
+ # time_select 'game', 'game_time', { minute_step: 15 }
306
310
  #
307
311
  # # Creates a time select tag with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
308
- # time_select("article", "written_on", prompt: {hour: 'Choose hour', minute: 'Choose minute', second: 'Choose seconds'})
309
- # 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
310
314
  # time_select("article", "written_on", prompt: true) # generic prompts for all
311
315
  #
312
316
  # # You can set :ampm option to true which will show the hours as: 12 PM, 01 AM .. 11 PM.
313
- # time_select 'game', 'game_time', {ampm: true}
317
+ # time_select 'game', 'game_time', { ampm: true }
314
318
  #
315
319
  # The selects are prepared for multi-parameter assignment to an Active Record object.
316
320
  #
@@ -346,8 +350,8 @@ module ActionView
346
350
  # datetime_select("article", "written_on", discard_type: true)
347
351
  #
348
352
  # # Generates a datetime select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
349
- # datetime_select("article", "written_on", prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
350
- # 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
351
355
  # datetime_select("article", "written_on", prompt: true) # generic prompts for all
352
356
  #
353
357
  # The selects are prepared for multi-parameter assignment to an Active Record object.
@@ -397,8 +401,8 @@ module ActionView
397
401
  # select_datetime(my_date_time, prefix: 'payday')
398
402
  #
399
403
  # # Generates a datetime select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
400
- # select_datetime(my_date_time, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
401
- # 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
402
406
  # select_datetime(my_date_time, prompt: true) # generic prompts for all
403
407
  def select_datetime(datetime = Time.current, options = {}, html_options = {})
404
408
  DateTimeSelector.new(datetime, options, html_options).select_datetime
@@ -436,8 +440,8 @@ module ActionView
436
440
  # select_date(my_date, prefix: 'payday')
437
441
  #
438
442
  # # Generates a date select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
439
- # select_date(my_date, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
440
- # 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
441
445
  # select_date(my_date, prompt: true) # generic prompts for all
442
446
  def select_date(date = Date.current, options = {}, html_options = {})
443
447
  DateTimeSelector.new(date, options, html_options).select_date
@@ -476,8 +480,8 @@ module ActionView
476
480
  # select_time(my_time, start_hour: 2, end_hour: 14)
477
481
  #
478
482
  # # Generates a time select with a custom prompt. Use <tt>:prompt</tt> to true for generic prompts.
479
- # select_time(my_time, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
480
- # 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
481
485
  # select_time(my_time, prompt: true) # generic prompts for all
482
486
  def select_time(datetime = Time.current, options = {}, html_options = {})
483
487
  DateTimeSelector.new(datetime, options, html_options).select_time
@@ -668,8 +672,6 @@ module ActionView
668
672
  # <time datetime="2010-11-04T17:55:45+01:00">November 04, 2010 17:55</time>
669
673
  # time_tag Date.yesterday, 'Yesterday' # =>
670
674
  # <time datetime="2010-11-03">Yesterday</time>
671
- # time_tag Date.today, pubdate: true # =>
672
- # <time datetime="2010-11-04" pubdate="pubdate">November 04, 2010</time>
673
675
  # time_tag Date.today, datetime: Date.today.strftime('%G-W%V') # =>
674
676
  # <time datetime="2010-W44">November 04, 2010</time>
675
677
  #
@@ -681,13 +683,11 @@ module ActionView
681
683
  options = args.extract_options!
682
684
  format = options.delete(:format) || :long
683
685
  content = args.first || I18n.l(date_or_time, format: format)
684
- datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.iso8601
685
686
 
686
- 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)
687
688
  end
688
689
 
689
690
  private
690
-
691
691
  def normalize_distance_of_time_argument_to_time(value)
692
692
  if value.is_a?(Numeric)
693
693
  Time.at(value)
@@ -702,7 +702,7 @@ module ActionView
702
702
  class DateTimeSelector #:nodoc:
703
703
  include ActionView::Helpers::TagHelper
704
704
 
705
- DEFAULT_PREFIX = "date".freeze
705
+ DEFAULT_PREFIX = "date"
706
706
  POSITION = {
707
707
  year: 1, month: 2, day: 3, hour: 4, minute: 5, second: 6
708
708
  }.freeze
@@ -823,14 +823,14 @@ module ActionView
823
823
  1.upto(12) do |month_number|
824
824
  options = { value: month_number }
825
825
  options[:selected] = "selected" if month == month_number
826
- month_options << content_tag("option".freeze, month_name(month_number), options) + "\n"
826
+ month_options << content_tag("option", month_name(month_number), options) + "\n"
827
827
  end
828
828
  build_select(:month, month_options.join)
829
829
  end
830
830
  end
831
831
 
832
832
  def select_year
833
- if !@datetime || @datetime == 0
833
+ if !year || @datetime == 0
834
834
  val = "1"
835
835
  middle_year = Date.today.year
836
836
  else
@@ -851,7 +851,7 @@ module ActionView
851
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."
852
852
  end
853
853
 
854
- build_options_and_select(:year, val, options)
854
+ build_select(:year, build_year_options(val, options))
855
855
  end
856
856
  end
857
857
 
@@ -934,6 +934,21 @@ module ActionView
934
934
  end
935
935
  end
936
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
+
937
952
  def date_order
938
953
  @date_order ||= @options[:order] || translated_date_order
939
954
  end
@@ -990,7 +1005,35 @@ module ActionView
990
1005
  tag_options[:selected] = "selected" if selected == i
991
1006
  text = options[:use_two_digit_numbers] ? sprintf("%02d", i) : value
992
1007
  text = options[:ampm] ? AMPM_TRANSLATION[i] : text
993
- 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)
994
1037
  end
995
1038
 
996
1039
  (select_options.join("\n") + "\n").html_safe
@@ -1009,12 +1052,12 @@ module ActionView
1009
1052
  select_options[:disabled] = "disabled" if @options[:disabled]
1010
1053
  select_options[:class] = css_class_attribute(type, select_options[:class], @options[:with_css_classes]) if @options[:with_css_classes]
1011
1054
 
1012
- select_html = "\n".dup
1013
- 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]
1014
1057
  select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt]
1015
1058
  select_html << select_options_as_html
1016
1059
 
1017
- (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
1018
1061
  end
1019
1062
 
1020
1063
  # Builds the css class value for the select element
@@ -1047,7 +1090,7 @@ module ActionView
1047
1090
  I18n.translate(:"datetime.prompts.#{type}", locale: @options[:locale])
1048
1091
  end
1049
1092
 
1050
- prompt ? content_tag("option".freeze, prompt, value: "") : ""
1093
+ prompt ? content_tag("option", prompt, value: "") : ""
1051
1094
  end
1052
1095
 
1053
1096
  # Builds hidden input tag for date part and value.
@@ -1091,11 +1134,11 @@ module ActionView
1091
1134
  # Given an ordering of datetime components, create the selection HTML
1092
1135
  # and join them with their appropriate separators.
1093
1136
  def build_selects_from_types(order)
1094
- select = "".dup
1137
+ select = +""
1095
1138
  first_visible = order.find { |type| !@options[:"discard_#{type}"] }
1096
1139
  order.reverse_each do |type|
1097
1140
  separator = separator(type) unless type == first_visible # don't add before first visible field
1098
- select.insert(0, separator.to_s + send("select_#{type}").to_s)
1141
+ select.insert(0, separator.to_s + public_send("select_#{type}").to_s)
1099
1142
  end
1100
1143
  select.html_safe
1101
1144
  end
@@ -11,6 +11,7 @@ require "active_support/core_ext/module/attribute_accessors"
11
11
  require "active_support/core_ext/hash/slice"
12
12
  require "active_support/core_ext/string/output_safety"
13
13
  require "active_support/core_ext/string/inflections"
14
+ require "active_support/core_ext/symbol/starts_ends_with"
14
15
 
15
16
  module ActionView
16
17
  # = Action View Form Helpers
@@ -185,8 +186,7 @@ module ActionView
185
186
  # get the authenticity token from the <tt>meta</tt> tag, so embedding is
186
187
  # unnecessary unless you support browsers without JavaScript.
187
188
  # * <tt>:remote</tt> - If set to true, will allow the Unobtrusive
188
- # JavaScript drivers to control the submit behavior. By default this
189
- # behavior is an ajax submit.
189
+ # JavaScript drivers to control the submit behavior.
190
190
  # * <tt>:enforce_utf8</tt> - If set to false, a hidden input with name
191
191
  # utf8 is not output.
192
192
  # * <tt>:html</tt> - Optional HTML attributes for the form tag.
@@ -322,10 +322,8 @@ module ActionView
322
322
  # remote: true
323
323
  #
324
324
  # in the options hash creates a form that will allow the unobtrusive JavaScript drivers to modify its
325
- # behavior. The expected default behavior is an XMLHttpRequest in the background instead of the regular
326
- # POST arrangement, but ultimately the behavior is the choice of the JavaScript driver implementor.
327
- # Even though it's using JavaScript to serialize the form elements, the form submission will work just like
328
- # 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>).
329
327
  #
330
328
  # Example:
331
329
  #
@@ -535,11 +533,6 @@ module ActionView
535
533
  # accessible as <tt>params[:title]</tt> and <tt>params[:post][:title]</tt>
536
534
  # respectively.
537
535
  #
538
- # By default +form_with+ attaches the <tt>data-remote</tt> attribute
539
- # submitting the form via an XMLHTTPRequest in the background if an
540
- # Unobtrusive JavaScript driver, like rails-ujs, is used. See the
541
- # <tt>:local</tt> option for more.
542
- #
543
536
  # For ease of comparison the examples above left out the submit button,
544
537
  # as well as the auto generated hidden fields that enable UTF-8 support
545
538
  # and adds an authenticity token needed for cross site request forgery
@@ -590,6 +583,9 @@ module ActionView
590
583
  # Skipped if a <tt>:url</tt> is passed.
591
584
  # * <tt>:scope</tt> - The scope to prefix input field names with and
592
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.
593
589
  # * <tt>:model</tt> - A model object to infer the <tt>:url</tt> and
594
590
  # <tt>:scope</tt> by, plus fill out input field values.
595
591
  # So if a +title+ attribute is set to "Ahoy!" then a +title+ input
@@ -608,10 +604,12 @@ module ActionView
608
604
  # This is helpful when fragment-caching the form. Remote forms
609
605
  # get the authenticity token from the <tt>meta</tt> tag, so embedding is
610
606
  # unnecessary unless you support browsers without JavaScript.
611
- # * <tt>:local</tt> - By default form submits are remote and unobtrusive XHRs.
612
- # Disable remote submits with <tt>local: true</tt>.
613
- # * <tt>:skip_enforcing_utf8</tt> - By default a hidden field named +utf8+
614
- # is output to enforce UTF-8 submits. Set to true to skip the field.
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.
615
613
  # * <tt>:builder</tt> - Override the object used to build the form.
616
614
  # * <tt>:id</tt> - Optional HTML id attribute.
617
615
  # * <tt>:class</tt> - Optional HTML class attribute.
@@ -752,10 +750,10 @@ module ActionView
752
750
  output = capture(builder, &block)
753
751
  options[:multipart] ||= builder.multipart?
754
752
 
755
- html_options = html_options_for_form_with(url, model, options)
753
+ html_options = html_options_for_form_with(url, model, **options)
756
754
  form_tag_with_body(html_options, output)
757
755
  else
758
- html_options = html_options_for_form_with(url, model, options)
756
+ html_options = html_options_for_form_with(url, model, **options)
759
757
  form_tag_html(html_options)
760
758
  end
761
759
  end
@@ -885,7 +883,7 @@ module ActionView
885
883
  #
886
884
  # Now, when you use a form element with the <tt>_destroy</tt> parameter,
887
885
  # with a value that evaluates to +true+, you will destroy the associated
888
- # model (eg. 1, '1', true, or 'true'):
886
+ # model (e.g. 1, '1', true, or 'true'):
889
887
  #
890
888
  # <%= form_for @person do |person_form| %>
891
889
  # ...
@@ -974,7 +972,7 @@ module ActionView
974
972
  # This will allow you to specify which models to destroy in the
975
973
  # attributes hash by adding a form element for the <tt>_destroy</tt>
976
974
  # parameter with a value that evaluates to +true+
977
- # (eg. 1, '1', true, or 'true'):
975
+ # (e.g. 1, '1', true, or 'true'):
978
976
  #
979
977
  # <%= form_for @person do |person_form| %>
980
978
  # ...
@@ -1107,6 +1105,16 @@ module ActionView
1107
1105
  # label(:post, :privacy, "Public Post", value: "public")
1108
1106
  # # => <label for="post_privacy_public">Public Post</label>
1109
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
+ #
1110
1118
  # label(:post, :terms) do
1111
1119
  # raw('Accept <a href="/terms">Terms</a>.')
1112
1120
  # end
@@ -1127,6 +1135,9 @@ module ActionView
1127
1135
  # text_field(:post, :title, class: "create_input")
1128
1136
  # # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
1129
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
+ #
1130
1141
  # text_field(:session, :user, onchange: "if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }")
1131
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!'); }"/>
1132
1143
  #
@@ -1519,10 +1530,10 @@ module ActionView
1519
1530
 
1520
1531
  private
1521
1532
  def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: !form_with_generates_remote_forms,
1522
- skip_enforcing_utf8: false, **options)
1533
+ skip_enforcing_utf8: nil, **options)
1523
1534
  html_options = options.slice(:id, :class, :multipart, :method, :data).merge(html)
1524
1535
  html_options[:method] ||= :patch if model.respond_to?(:persisted?) && model.persisted?
1525
- html_options[:enforce_utf8] = !skip_enforcing_utf8
1536
+ html_options[:enforce_utf8] = !skip_enforcing_utf8 unless skip_enforcing_utf8.nil?
1526
1537
 
1527
1538
  html_options[:enctype] = "multipart/form-data" if html_options.delete(:multipart)
1528
1539
 
@@ -1662,8 +1673,8 @@ module ActionView
1662
1673
 
1663
1674
  convert_to_legacy_options(@options)
1664
1675
 
1665
- if @object_name.to_s.match(/\[\]$/)
1666
- 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)
1667
1678
  @auto_index = object.to_param
1668
1679
  else
1669
1680
  raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
@@ -1674,11 +1685,232 @@ module ActionView
1674
1685
  @index = options[:index] || options[:child_index]
1675
1686
  end
1676
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
+
1677
1909
  (field_helpers - [:label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field]).each do |selector|
1678
1910
  class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
1679
1911
  def #{selector}(method, options = {}) # def text_field(method, options = {})
1680
- @template.send( # @template.send(
1681
- #{selector.inspect}, # "text_field",
1912
+ @template.public_send( # @template.public_send(
1913
+ #{selector.inspect}, # :text_field,
1682
1914
  @object_name, # @object_name,
1683
1915
  method, # method,
1684
1916
  objectify_options(options)) # objectify_options(options))
@@ -1811,7 +2043,7 @@ module ActionView
1811
2043
  #
1812
2044
  # Now, when you use a form element with the <tt>_destroy</tt> parameter,
1813
2045
  # with a value that evaluates to +true+, you will destroy the associated
1814
- # model (eg. 1, '1', true, or 'true'):
2046
+ # model (e.g. 1, '1', true, or 'true'):
1815
2047
  #
1816
2048
  # <%= form_for @person do |person_form| %>
1817
2049
  # ...
@@ -1900,7 +2132,7 @@ module ActionView
1900
2132
  # This will allow you to specify which models to destroy in the
1901
2133
  # attributes hash by adding a form element for the <tt>_destroy</tt>
1902
2134
  # parameter with a value that evaluates to +true+
1903
- # (eg. 1, '1', true, or 'true'):
2135
+ # (e.g. 1, '1', true, or 'true'):
1904
2136
  #
1905
2137
  # <%= form_for @person do |person_form| %>
1906
2138
  # ...
@@ -1947,15 +2179,14 @@ module ActionView
1947
2179
  index = if options.has_key?(:index)
1948
2180
  options[:index]
1949
2181
  elsif defined?(@auto_index)
1950
- object_name = object_name.to_s.sub(/\[\]$/, "")
2182
+ object_name = object_name.to_s.delete_suffix("[]")
1951
2183
  @auto_index
1952
2184
  end
1953
2185
 
1954
2186
  record_name = if index
1955
2187
  "#{object_name}[#{index}][#{record_name}]"
1956
- elsif record_name.to_s.end_with?("[]")
1957
- record_name = record_name.to_s.sub(/(.*)\[\]$/, "[\\1][#{record_object.id}]")
1958
- "#{object_name}#{record_name}"
2188
+ elsif record_name.end_with?("[]")
2189
+ "#{object_name}[#{record_name[0..-3]}][#{record_object.id}]"
1959
2190
  else
1960
2191
  "#{object_name}[#{record_name}]"
1961
2192
  end
@@ -2018,6 +2249,24 @@ module ActionView
2018
2249
  # label(:privacy, "Public Post", value: "public")
2019
2250
  # # => <label for="post_privacy_public">Public Post</label>
2020
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
+ #
2021
2270
  # label(:terms) do
2022
2271
  # raw('Accept <a href="/terms">Terms</a>.')
2023
2272
  # end
@@ -2241,19 +2490,33 @@ module ActionView
2241
2490
  # # <strong>Ask me!</strong>
2242
2491
  # # </button>
2243
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
+ #
2244
2500
  def button(value = nil, options = {}, &block)
2245
2501
  value, options = nil, value if value.is_a?(Hash)
2246
2502
  value ||= submit_default_value
2247
- @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)
2248
2509
  end
2249
2510
 
2250
- def emitted_hidden_id?
2511
+ def emitted_hidden_id? # :nodoc:
2251
2512
  @emitted_hidden_id ||= nil
2252
2513
  end
2253
2514
 
2254
2515
  private
2255
2516
  def objectify_options(options)
2256
- @default_options.merge(options.merge(object: @object))
2517
+ result = @default_options.merge(options)
2518
+ result[:object] = @object
2519
+ result
2257
2520
  end
2258
2521
 
2259
2522
  def submit_default_value
@@ -2267,7 +2530,12 @@ module ActionView
2267
2530
  end
2268
2531
 
2269
2532
  defaults = []
2270
- 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
2271
2539
  defaults << :"helpers.submit.#{key}"
2272
2540
  defaults << "#{key.to_s.humanize} #{model}"
2273
2541
 
@@ -2283,9 +2551,9 @@ module ActionView
2283
2551
  association = convert_to_model(association)
2284
2552
 
2285
2553
  if association.respond_to?(:persisted?)
2286
- association = [association] if @object.send(association_name).respond_to?(:to_ary)
2554
+ association = [association] if @object.public_send(association_name).respond_to?(:to_ary)
2287
2555
  elsif !association.respond_to?(:to_ary)
2288
- association = @object.send(association_name)
2556
+ association = @object.public_send(association_name)
2289
2557
  end
2290
2558
 
2291
2559
  if association.respond_to?(:to_ary)