actionview 5.2.3.rc1 → 6.0.0.beta1

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +115 -66
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/action_view.rb +1 -1
  6. data/lib/action_view/buffers.rb +15 -0
  7. data/lib/action_view/context.rb +5 -4
  8. data/lib/action_view/digestor.rb +7 -6
  9. data/lib/action_view/gem_version.rb +4 -4
  10. data/lib/action_view/helpers.rb +0 -2
  11. data/lib/action_view/helpers/asset_tag_helper.rb +4 -27
  12. data/lib/action_view/helpers/asset_url_helper.rb +4 -3
  13. data/lib/action_view/helpers/cache_helper.rb +18 -10
  14. data/lib/action_view/helpers/capture_helper.rb +4 -0
  15. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  16. data/lib/action_view/helpers/date_helper.rb +69 -25
  17. data/lib/action_view/helpers/form_helper.rb +238 -6
  18. data/lib/action_view/helpers/form_options_helper.rb +23 -15
  19. data/lib/action_view/helpers/form_tag_helper.rb +9 -7
  20. data/lib/action_view/helpers/javascript_helper.rb +9 -8
  21. data/lib/action_view/helpers/number_helper.rb +5 -0
  22. data/lib/action_view/helpers/sanitize_helper.rb +3 -3
  23. data/lib/action_view/helpers/tag_helper.rb +7 -6
  24. data/lib/action_view/helpers/tags/base.rb +8 -4
  25. data/lib/action_view/helpers/tags/color_field.rb +1 -1
  26. data/lib/action_view/helpers/tags/translator.rb +1 -6
  27. data/lib/action_view/helpers/text_helper.rb +3 -3
  28. data/lib/action_view/helpers/translation_helper.rb +14 -10
  29. data/lib/action_view/helpers/url_helper.rb +13 -13
  30. data/lib/action_view/log_subscriber.rb +6 -6
  31. data/lib/action_view/lookup_context.rb +4 -4
  32. data/lib/action_view/railtie.rb +18 -0
  33. data/lib/action_view/record_identifier.rb +2 -2
  34. data/lib/action_view/renderer/partial_renderer.rb +2 -2
  35. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +40 -1
  36. data/lib/action_view/renderer/streaming_template_renderer.rb +1 -1
  37. data/lib/action_view/rendering.rb +5 -4
  38. data/lib/action_view/routing_url_for.rb +12 -11
  39. data/lib/action_view/template.rb +25 -8
  40. data/lib/action_view/template/handlers/erb.rb +12 -2
  41. data/lib/action_view/template/resolver.rb +56 -16
  42. data/lib/action_view/test_case.rb +1 -1
  43. data/lib/action_view/testing/resolvers.rb +1 -1
  44. data/lib/assets/compiled/rails-ujs.js +34 -17
  45. metadata +11 -12
  46. data/lib/action_view/helpers/record_tag_helper.rb +0 -23
@@ -16,7 +16,7 @@ module ActionView
16
16
  #
17
17
  # * <tt>:include_blank</tt> - set to true or a prompt string if the first option element of the select element is a blank. Useful if there is not a default value required for the select element.
18
18
  #
19
- # select("post", "category", Post::CATEGORIES, {include_blank: true})
19
+ # select("post", "category", Post::CATEGORIES, { include_blank: true })
20
20
  #
21
21
  # could become:
22
22
  #
@@ -30,7 +30,7 @@ module ActionView
30
30
  #
31
31
  # Example with <tt>@post.person_id => 2</tt>:
32
32
  #
33
- # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {include_blank: 'None'})
33
+ # select("post", "person_id", Person.all.collect { |p| [ p.name, p.id ] }, { include_blank: 'None' })
34
34
  #
35
35
  # could become:
36
36
  #
@@ -43,7 +43,7 @@ module ActionView
43
43
  #
44
44
  # * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this prepends an option with a generic prompt -- "Please select" -- or the given prompt string.
45
45
  #
46
- # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {prompt: 'Select Person'})
46
+ # select("post", "person_id", Person.all.collect { |p| [ p.name, p.id ] }, { prompt: 'Select Person' })
47
47
  #
48
48
  # could become:
49
49
  #
@@ -69,7 +69,7 @@ module ActionView
69
69
  #
70
70
  # * <tt>:disabled</tt> - can be a single value or an array of values that will be disabled options in the final output.
71
71
  #
72
- # select("post", "category", Post::CATEGORIES, {disabled: 'restricted'})
72
+ # select("post", "category", Post::CATEGORIES, { disabled: 'restricted' })
73
73
  #
74
74
  # could become:
75
75
  #
@@ -82,7 +82,7 @@ module ActionView
82
82
  #
83
83
  # When used with the <tt>collection_select</tt> helper, <tt>:disabled</tt> can also be a Proc that identifies those options that should be disabled.
84
84
  #
85
- # collection_select(:post, :category_id, Category.all, :id, :name, {disabled: -> (category) { category.archived? }})
85
+ # collection_select(:post, :category_id, Category.all, :id, :name, { disabled: -> (category) { category.archived? } })
86
86
  #
87
87
  # If the categories "2008 stuff" and "Christmas" return true when the method <tt>archived?</tt> is called, this would return:
88
88
  # <select name="post[category_id]" id="post_category_id">
@@ -107,7 +107,7 @@ module ActionView
107
107
  #
108
108
  # For example:
109
109
  #
110
- # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, { include_blank: true })
110
+ # select("post", "person_id", Person.all.collect { |p| [ p.name, p.id ] }, { include_blank: true })
111
111
  #
112
112
  # would become:
113
113
  #
@@ -323,12 +323,12 @@ module ActionView
323
323
  #
324
324
  # You can optionally provide HTML attributes as the last element of the array.
325
325
  #
326
- # options_for_select([ "Denmark", ["USA", {class: 'bold'}], "Sweden" ], ["USA", "Sweden"])
326
+ # options_for_select([ "Denmark", ["USA", { class: 'bold' }], "Sweden" ], ["USA", "Sweden"])
327
327
  # # => <option value="Denmark">Denmark</option>
328
328
  # # => <option value="USA" class="bold" selected="selected">USA</option>
329
329
  # # => <option value="Sweden" selected="selected">Sweden</option>
330
330
  #
331
- # options_for_select([["Dollar", "$", {class: "bold"}], ["Kroner", "DKK", {onclick: "alert('HI');"}]])
331
+ # options_for_select([["Dollar", "$", { class: "bold" }], ["Kroner", "DKK", { onclick: "alert('HI');" }]])
332
332
  # # => <option value="$" class="bold">Dollar</option>
333
333
  # # => <option value="DKK" onclick="alert('HI');">Kroner</option>
334
334
  #
@@ -463,7 +463,7 @@ module ActionView
463
463
  option_tags = options_from_collection_for_select(
464
464
  value_for_collection(group, group_method), option_key_method, option_value_method, selected_key)
465
465
 
466
- content_tag("optgroup".freeze, option_tags, label: value_for_collection(group, group_label_method))
466
+ content_tag("optgroup", option_tags, label: value_for_collection(group, group_label_method))
467
467
  end.join.html_safe
468
468
  end
469
469
 
@@ -535,7 +535,7 @@ module ActionView
535
535
  body = "".html_safe
536
536
 
537
537
  if prompt
538
- body.safe_concat content_tag("option".freeze, prompt_text(prompt), value: "")
538
+ body.safe_concat content_tag("option", prompt_text(prompt), value: "")
539
539
  end
540
540
 
541
541
  grouped_options.each do |container|
@@ -548,7 +548,7 @@ module ActionView
548
548
  end
549
549
 
550
550
  html_attributes = { label: label }.merge!(html_attributes)
551
- body.safe_concat content_tag("optgroup".freeze, options_for_select(container, selected_key), html_attributes)
551
+ body.safe_concat content_tag("optgroup", options_for_select(container, selected_key), html_attributes)
552
552
  end
553
553
 
554
554
  body
@@ -584,7 +584,7 @@ module ActionView
584
584
  end
585
585
 
586
586
  zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected)
587
- zone_options.safe_concat content_tag("option".freeze, "-------------", value: "", disabled: true)
587
+ zone_options.safe_concat content_tag("option", "-------------", value: "", disabled: true)
588
588
  zone_options.safe_concat "\n"
589
589
 
590
590
  zones = zones - priority_zones
@@ -654,7 +654,7 @@ module ActionView
654
654
  #
655
655
  # ==== Gotcha
656
656
  #
657
- # The HTML specification says when nothing is select on a collection of radio buttons
657
+ # The HTML specification says when nothing is selected on a collection of radio buttons
658
658
  # web browsers do not send any value to server.
659
659
  # Unfortunately this introduces a gotcha:
660
660
  # if a +User+ model has a +category_id+ field and in the form no category is selected, no +category_id+ parameter is sent. So,
@@ -794,7 +794,7 @@ module ActionView
794
794
  def extract_values_from_collection(collection, value_method, selected)
795
795
  if selected.is_a?(Proc)
796
796
  collection.map do |element|
797
- element.send(value_method) if selected.call(element)
797
+ public_or_deprecated_send(element, value_method) if selected.call(element)
798
798
  end.compact
799
799
  else
800
800
  selected
@@ -802,7 +802,15 @@ module ActionView
802
802
  end
803
803
 
804
804
  def value_for_collection(item, value)
805
- value.respond_to?(:call) ? value.call(item) : item.send(value)
805
+ value.respond_to?(:call) ? value.call(item) : public_or_deprecated_send(item, value)
806
+ end
807
+
808
+ def public_or_deprecated_send(item, value)
809
+ item.public_send(value)
810
+ rescue NoMethodError
811
+ raise unless item.respond_to?(value, true) && !item.respond_to?(value)
812
+ ActiveSupport::Deprecation.warn "Using private methods from view helpers is deprecated (calling private #{item.class}##{value})"
813
+ item.send(value)
806
814
  end
807
815
 
808
816
  def prompt_text(prompt)
@@ -22,6 +22,8 @@ module ActionView
22
22
  mattr_accessor :embed_authenticity_token_in_remote_forms
23
23
  self.embed_authenticity_token_in_remote_forms = nil
24
24
 
25
+ mattr_accessor :default_enforce_utf8, default: true
26
+
25
27
  # Starts a form tag that points the action to a url configured with <tt>url_for_options</tt> just like
26
28
  # ActionController::Base#url_for. The method for the form defaults to POST.
27
29
  #
@@ -144,15 +146,15 @@ module ActionView
144
146
  end
145
147
 
146
148
  if include_blank
147
- option_tags = content_tag("option".freeze, include_blank, options_for_blank_options_tag).safe_concat(option_tags)
149
+ option_tags = content_tag("option", include_blank, options_for_blank_options_tag).safe_concat(option_tags)
148
150
  end
149
151
  end
150
152
 
151
153
  if prompt = options.delete(:prompt)
152
- option_tags = content_tag("option".freeze, prompt, value: "").safe_concat(option_tags)
154
+ option_tags = content_tag("option", prompt, value: "").safe_concat(option_tags)
153
155
  end
154
156
 
155
- content_tag "select".freeze, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
157
+ content_tag "select", option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
156
158
  end
157
159
 
158
160
  # Creates a standard text field; use these text fields to input smaller chunks of text like a username
@@ -387,8 +389,8 @@ module ActionView
387
389
  # * Any other key creates standard HTML options for the tag.
388
390
  #
389
391
  # ==== Examples
390
- # radio_button_tag 'gender', 'male'
391
- # # => <input id="gender_male" name="gender" type="radio" value="male" />
392
+ # radio_button_tag 'favorite_color', 'maroon'
393
+ # # => <input id="favorite_color_maroon" name="favorite_color" type="radio" value="maroon" />
392
394
  #
393
395
  # radio_button_tag 'receive_updates', 'no', true
394
396
  # # => <input checked="checked" id="receive_updates_no" name="receive_updates" type="radio" value="no" />
@@ -575,7 +577,7 @@ module ActionView
575
577
  # # => <fieldset class="format"><p><input id="name" name="name" type="text" /></p></fieldset>
576
578
  def field_set_tag(legend = nil, options = nil, &block)
577
579
  output = tag(:fieldset, options, true)
578
- output.safe_concat(content_tag("legend".freeze, legend)) unless legend.blank?
580
+ output.safe_concat(content_tag("legend", legend)) unless legend.blank?
579
581
  output.concat(capture(&block)) if block_given?
580
582
  output.safe_concat("</fieldset>")
581
583
  end
@@ -867,7 +869,7 @@ module ActionView
867
869
  })
868
870
  end
869
871
 
870
- if html_options.delete("enforce_utf8") { true }
872
+ if html_options.delete("enforce_utf8") { default_enforce_utf8 }
871
873
  utf8_enforcer_tag + method_tag
872
874
  else
873
875
  method_tag
@@ -15,8 +15,8 @@ module ActionView
15
15
  "'" => "\\'"
16
16
  }
17
17
 
18
- JS_ESCAPE_MAP["\342\200\250".dup.force_encoding(Encoding::UTF_8).encode!] = "&#x2028;"
19
- JS_ESCAPE_MAP["\342\200\251".dup.force_encoding(Encoding::UTF_8).encode!] = "&#x2029;"
18
+ JS_ESCAPE_MAP[(+"\342\200\250").force_encoding(Encoding::UTF_8).encode!] = "&#x2028;"
19
+ JS_ESCAPE_MAP[(+"\342\200\251").force_encoding(Encoding::UTF_8).encode!] = "&#x2029;"
20
20
 
21
21
  # Escapes carriage returns and single and double quotes for JavaScript segments.
22
22
  #
@@ -25,12 +25,13 @@ module ActionView
25
25
  #
26
26
  # $('some_element').replaceWith('<%= j render 'some/element_template' %>');
27
27
  def escape_javascript(javascript)
28
- if javascript
29
- result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) { |match| JS_ESCAPE_MAP[match] }
30
- javascript.html_safe? ? result.html_safe : result
28
+ javascript = javascript.to_s
29
+ if javascript.empty?
30
+ result = ""
31
31
  else
32
- ""
32
+ result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) { |match| JS_ESCAPE_MAP[match] }
33
33
  end
34
+ javascript.html_safe? ? result.html_safe : result
34
35
  end
35
36
 
36
37
  alias_method :j, :escape_javascript
@@ -65,7 +66,7 @@ module ActionView
65
66
  # <% end -%>
66
67
  #
67
68
  # If you have a content security policy enabled then you can add an automatic
68
- # nonce value by passing +nonce: true+ as part of +html_options+. Example:
69
+ # nonce value by passing <tt>nonce: true</tt> as part of +html_options+. Example:
69
70
  #
70
71
  # <%= javascript_tag nonce: true do -%>
71
72
  # alert('All is good')
@@ -83,7 +84,7 @@ module ActionView
83
84
  html_options[:nonce] = content_security_policy_nonce
84
85
  end
85
86
 
86
- content_tag("script".freeze, javascript_cdata_section(content), html_options)
87
+ content_tag("script", javascript_cdata_section(content), html_options)
87
88
  end
88
89
 
89
90
  def javascript_cdata_section(content) #:nodoc:
@@ -100,6 +100,9 @@ module ActionView
100
100
  # absolute value of the number.
101
101
  # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
102
102
  # the argument is invalid.
103
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
104
+ # insignificant zeros after the decimal separator (defaults to
105
+ # +false+).
103
106
  #
104
107
  # ==== Examples
105
108
  #
@@ -117,6 +120,8 @@ module ActionView
117
120
  # # => R$1234567890,50
118
121
  # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "", format: "%n %u")
119
122
  # # => 1234567890,50 R$
123
+ # number_to_currency(1234567890.50, strip_insignificant_zeros: true)
124
+ # # => "$1,234,567,890.5"
120
125
  def number_to_currency(number, options = {})
121
126
  delegate_number_helper_method(:number_to_currency, number, options)
122
127
  end
@@ -10,7 +10,7 @@ module ActionView
10
10
  # These helper methods extend Action View making them callable within your template files.
11
11
  module SanitizeHelper
12
12
  extend ActiveSupport::Concern
13
- # Sanitizes HTML input, stripping all tags and attributes that aren't whitelisted.
13
+ # Sanitizes HTML input, stripping all but known-safe tags and attributes.
14
14
  #
15
15
  # It also strips href/src attributes with unsafe protocols like
16
16
  # <tt>javascript:</tt>, while also protecting against attempts to use Unicode,
@@ -40,7 +40,7 @@ module ActionView
40
40
  #
41
41
  # <%= sanitize @comment.body %>
42
42
  #
43
- # Providing custom whitelisted tags and attributes:
43
+ # Providing custom lists of permitted tags and attributes:
44
44
  #
45
45
  # <%= sanitize @comment.body, tags: %w(strong em a), attributes: %w(href) %>
46
46
  #
@@ -126,7 +126,7 @@ module ActionView
126
126
  attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer
127
127
 
128
128
  # Vendors the full, link and white list sanitizers.
129
- # Provided strictly for compatibility and can be removed in Rails 5.1.
129
+ # Provided strictly for compatibility and can be removed in Rails 6.
130
130
  def sanitizer_vendor
131
131
  Rails::Html::Sanitizer
132
132
  end
@@ -58,7 +58,7 @@ module ActionView
58
58
 
59
59
  def tag_options(options, escape = true)
60
60
  return if options.blank?
61
- output = "".dup
61
+ output = +""
62
62
  sep = " "
63
63
  options.each_pair do |key, value|
64
64
  if TAG_PREFIXES.include?(key) && value.is_a?(Hash)
@@ -86,11 +86,12 @@ module ActionView
86
86
 
87
87
  def tag_option(key, value, escape)
88
88
  if value.is_a?(Array)
89
- value = escape ? safe_join(value, " ".freeze) : value.join(" ".freeze)
89
+ value = escape ? safe_join(value, " ") : value.join(" ")
90
90
  else
91
- value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
91
+ value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s.dup
92
92
  end
93
- %(#{key}="#{value.gsub('"'.freeze, '&quot;'.freeze)}")
93
+ value.gsub!('"', "&quot;")
94
+ %(#{key}="#{value}")
94
95
  end
95
96
 
96
97
  private
@@ -227,10 +228,10 @@ module ActionView
227
228
  # tag("img", src: "open & shut.png")
228
229
  # # => <img src="open &amp; shut.png" />
229
230
  #
230
- # tag("img", {src: "open &amp; shut.png"}, false, false)
231
+ # tag("img", { src: "open &amp; shut.png" }, false, false)
231
232
  # # => <img src="open &amp; shut.png" />
232
233
  #
233
- # tag("div", data: {name: 'Stephen', city_state: %w(Chicago IL)})
234
+ # tag("div", data: { name: 'Stephen', city_state: %w(Chicago IL) })
234
235
  # # => <div data-name="Stephen" data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]" />
235
236
  def tag(name = nil, options = nil, open = false, escape = true)
236
237
  if name.nil?
@@ -109,11 +109,11 @@ module ActionView
109
109
  # a little duplication to construct less strings
110
110
  case
111
111
  when @object_name.empty?
112
- "#{sanitized_method_name}#{"[]" if multiple}"
112
+ "#{sanitized_method_name}#{multiple ? "[]" : ""}"
113
113
  when index
114
- "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}"
114
+ "#{@object_name}[#{index}][#{sanitized_method_name}]#{multiple ? "[]" : ""}"
115
115
  else
116
- "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}"
116
+ "#{@object_name}[#{sanitized_method_name}]#{multiple ? "[]" : ""}"
117
117
  end
118
118
  end
119
119
 
@@ -170,7 +170,11 @@ module ActionView
170
170
  option_tags = tag_builder.content_tag_string("option", options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, value: "") + "\n" + option_tags
171
171
  end
172
172
  if value.blank? && options[:prompt]
173
- option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), value: "") + "\n" + option_tags
173
+ tag_options = { value: "" }.tap do |prompt_opts|
174
+ prompt_opts[:disabled] = true if options[:disabled] == ""
175
+ prompt_opts[:selected] = true if options[:selected] == ""
176
+ end
177
+ option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), tag_options) + "\n" + option_tags
174
178
  end
175
179
  option_tags
176
180
  end
@@ -15,7 +15,7 @@ module ActionView
15
15
 
16
16
  def validate_color_string(string)
17
17
  regex = /#[0-9a-fA-F]{6}/
18
- if regex.match(string)
18
+ if regex.match?(string)
19
19
  string.downcase
20
20
  else
21
21
  "#000000"
@@ -16,13 +16,8 @@ module ActionView
16
16
  translated_attribute || human_attribute_name
17
17
  end
18
18
 
19
- # TODO Change this to private once we've dropped Ruby 2.2 support.
20
- # Workaround for Ruby 2.2 "private attribute?" warning.
21
- protected
22
-
23
- attr_reader :object_name, :method_and_value, :scope, :model
24
-
25
19
  private
20
+ attr_reader :object_name, :method_and_value, :scope, :model
26
21
 
27
22
  def i18n_default
28
23
  if model
@@ -188,7 +188,7 @@ module ActionView
188
188
 
189
189
  unless separator.empty?
190
190
  text.split(separator).each do |value|
191
- if value.match(regex)
191
+ if value.match?(regex)
192
192
  phrase = value
193
193
  break
194
194
  end
@@ -228,7 +228,7 @@ module ActionView
228
228
  # pluralize(2, 'Person', locale: :de)
229
229
  # # => 2 Personen
230
230
  def pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale)
231
- word = if (count == 1 || count.to_s =~ /^1(\.0+)?$/)
231
+ word = if count == 1 || count.to_s =~ /^1(\.0+)?$/
232
232
  singular
233
233
  else
234
234
  plural || singular.pluralize(locale)
@@ -259,7 +259,7 @@ module ActionView
259
259
  # # => Once\r\nupon\r\na\r\ntime
260
260
  def word_wrap(text, line_width: 80, break_sequence: "\n")
261
261
  text.split("\n").collect! do |line|
262
- line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").strip : line
262
+ line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").rstrip : line
263
263
  end * break_sequence
264
264
  end
265
265
 
@@ -59,11 +59,9 @@ module ActionView
59
59
  # they can provide HTML values for.
60
60
  def translate(key, options = {})
61
61
  options = options.dup
62
- has_default = options.has_key?(:default)
63
- remaining_defaults = Array(options.delete(:default)).compact
64
-
65
- if has_default && !remaining_defaults.first.kind_of?(Symbol)
66
- options[:default] = remaining_defaults
62
+ if options.has_key?(:default)
63
+ remaining_defaults = Array(options.delete(:default)).compact
64
+ options[:default] = remaining_defaults unless remaining_defaults.first.kind_of?(Symbol)
67
65
  end
68
66
 
69
67
  # If the user has explicitly decided to NOT raise errors, pass that option to I18n.
@@ -85,8 +83,11 @@ module ActionView
85
83
  end
86
84
  end
87
85
  translation = I18n.translate(scope_key_by_partial(key), html_safe_options.merge(raise: i18n_raise))
88
-
89
- translation.respond_to?(:html_safe) ? translation.html_safe : translation
86
+ if translation.respond_to?(:map)
87
+ translation.map { |element| element.respond_to?(:html_safe) ? element.html_safe : element }
88
+ else
89
+ translation.respond_to?(:html_safe) ? translation.html_safe : translation
90
+ end
90
91
  else
91
92
  I18n.translate(scope_key_by_partial(key), options.merge(raise: i18n_raise))
92
93
  end
@@ -97,7 +98,7 @@ module ActionView
97
98
  raise e if raise_error
98
99
 
99
100
  keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
100
- title = "translation missing: #{keys.join('.')}".dup
101
+ title = +"translation missing: #{keys.join('.')}"
101
102
 
102
103
  interpolations = options.except(:default, :scope)
103
104
  if interpolations.any?
@@ -122,9 +123,12 @@ module ActionView
122
123
 
123
124
  private
124
125
  def scope_key_by_partial(key)
125
- if key.to_s.first == "."
126
+ stringified_key = key.to_s
127
+ if stringified_key.first == "."
126
128
  if @virtual_path
127
- @virtual_path.gsub(%r{/_?}, ".") + key.to_s
129
+ @_scope_key_by_partial_cache ||= {}
130
+ @_scope_key_by_partial_cache[@virtual_path] ||= @virtual_path.gsub(%r{/_?}, ".")
131
+ "#{@_scope_key_by_partial_cache[@virtual_path]}#{stringified_key}"
128
132
  else
129
133
  raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
130
134
  end