actionview 4.2.11.1 → 6.0.4.8

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 (114) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +242 -186
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +9 -8
  5. data/lib/action_view/base.rb +144 -37
  6. data/lib/action_view/buffers.rb +18 -1
  7. data/lib/action_view/cache_expiry.rb +53 -0
  8. data/lib/action_view/context.rb +8 -12
  9. data/lib/action_view/dependency_tracker.rb +54 -20
  10. data/lib/action_view/digestor.rb +88 -85
  11. data/lib/action_view/flows.rb +11 -12
  12. data/lib/action_view/gem_version.rb +6 -4
  13. data/lib/action_view/helpers/active_model_helper.rb +16 -11
  14. data/lib/action_view/helpers/asset_tag_helper.rb +241 -82
  15. data/lib/action_view/helpers/asset_url_helper.rb +171 -67
  16. data/lib/action_view/helpers/atom_feed_helper.rb +19 -17
  17. data/lib/action_view/helpers/cache_helper.rb +112 -42
  18. data/lib/action_view/helpers/capture_helper.rb +20 -13
  19. data/lib/action_view/helpers/controller_helper.rb +15 -4
  20. data/lib/action_view/helpers/csp_helper.rb +26 -0
  21. data/lib/action_view/helpers/csrf_helper.rb +8 -6
  22. data/lib/action_view/helpers/date_helper.rb +230 -129
  23. data/lib/action_view/helpers/debug_helper.rb +7 -6
  24. data/lib/action_view/helpers/form_helper.rb +755 -129
  25. data/lib/action_view/helpers/form_options_helper.rb +130 -75
  26. data/lib/action_view/helpers/form_tag_helper.rb +116 -71
  27. data/lib/action_view/helpers/javascript_helper.rb +30 -14
  28. data/lib/action_view/helpers/number_helper.rb +84 -59
  29. data/lib/action_view/helpers/output_safety_helper.rb +36 -4
  30. data/lib/action_view/helpers/rendering_helper.rb +11 -8
  31. data/lib/action_view/helpers/sanitize_helper.rb +30 -31
  32. data/lib/action_view/helpers/tag_helper.rb +232 -75
  33. data/lib/action_view/helpers/tags/base.rb +138 -98
  34. data/lib/action_view/helpers/tags/check_box.rb +20 -19
  35. data/lib/action_view/helpers/tags/checkable.rb +4 -2
  36. data/lib/action_view/helpers/tags/collection_check_boxes.rb +12 -34
  37. data/lib/action_view/helpers/tags/collection_helpers.rb +69 -36
  38. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +6 -12
  39. data/lib/action_view/helpers/tags/collection_select.rb +4 -2
  40. data/lib/action_view/helpers/tags/color_field.rb +4 -3
  41. data/lib/action_view/helpers/tags/date_field.rb +2 -1
  42. data/lib/action_view/helpers/tags/date_select.rb +37 -36
  43. data/lib/action_view/helpers/tags/datetime_field.rb +4 -3
  44. data/lib/action_view/helpers/tags/datetime_local_field.rb +2 -1
  45. data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
  46. data/lib/action_view/helpers/tags/email_field.rb +2 -0
  47. data/lib/action_view/helpers/tags/file_field.rb +2 -0
  48. data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
  49. data/lib/action_view/helpers/tags/hidden_field.rb +2 -0
  50. data/lib/action_view/helpers/tags/label.rb +3 -2
  51. data/lib/action_view/helpers/tags/month_field.rb +2 -1
  52. data/lib/action_view/helpers/tags/number_field.rb +2 -0
  53. data/lib/action_view/helpers/tags/password_field.rb +3 -1
  54. data/lib/action_view/helpers/tags/placeholderable.rb +3 -1
  55. data/lib/action_view/helpers/tags/radio_button.rb +7 -6
  56. data/lib/action_view/helpers/tags/range_field.rb +2 -0
  57. data/lib/action_view/helpers/tags/search_field.rb +14 -9
  58. data/lib/action_view/helpers/tags/select.rb +11 -10
  59. data/lib/action_view/helpers/tags/tel_field.rb +2 -0
  60. data/lib/action_view/helpers/tags/text_area.rb +4 -2
  61. data/lib/action_view/helpers/tags/text_field.rb +8 -8
  62. data/lib/action_view/helpers/tags/time_field.rb +2 -1
  63. data/lib/action_view/helpers/tags/time_select.rb +2 -0
  64. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
  65. data/lib/action_view/helpers/tags/translator.rb +15 -16
  66. data/lib/action_view/helpers/tags/url_field.rb +2 -0
  67. data/lib/action_view/helpers/tags/week_field.rb +2 -1
  68. data/lib/action_view/helpers/tags.rb +3 -1
  69. data/lib/action_view/helpers/text_helper.rb +56 -38
  70. data/lib/action_view/helpers/translation_helper.rb +91 -47
  71. data/lib/action_view/helpers/url_helper.rb +160 -105
  72. data/lib/action_view/helpers.rb +5 -3
  73. data/lib/action_view/layouts.rb +65 -61
  74. data/lib/action_view/log_subscriber.rb +61 -10
  75. data/lib/action_view/lookup_context.rb +147 -89
  76. data/lib/action_view/model_naming.rb +3 -1
  77. data/lib/action_view/path_set.rb +28 -23
  78. data/lib/action_view/railtie.rb +62 -6
  79. data/lib/action_view/record_identifier.rb +53 -26
  80. data/lib/action_view/renderer/abstract_renderer.rb +71 -13
  81. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +103 -0
  82. data/lib/action_view/renderer/partial_renderer.rb +239 -225
  83. data/lib/action_view/renderer/renderer.rb +22 -8
  84. data/lib/action_view/renderer/streaming_template_renderer.rb +54 -54
  85. data/lib/action_view/renderer/template_renderer.rb +79 -73
  86. data/lib/action_view/rendering.rb +68 -44
  87. data/lib/action_view/routing_url_for.rb +33 -22
  88. data/lib/action_view/tasks/cache_digests.rake +25 -0
  89. data/lib/action_view/template/error.rb +44 -29
  90. data/lib/action_view/template/handlers/builder.rb +12 -13
  91. data/lib/action_view/template/handlers/erb/erubi.rb +87 -0
  92. data/lib/action_view/template/handlers/erb.rb +24 -86
  93. data/lib/action_view/template/handlers/html.rb +11 -0
  94. data/lib/action_view/template/handlers/raw.rb +4 -4
  95. data/lib/action_view/template/handlers.rb +38 -8
  96. data/lib/action_view/template/html.rb +19 -10
  97. data/lib/action_view/template/inline.rb +22 -0
  98. data/lib/action_view/template/raw_file.rb +28 -0
  99. data/lib/action_view/template/resolver.rb +217 -193
  100. data/lib/action_view/template/sources/file.rb +17 -0
  101. data/lib/action_view/template/sources.rb +13 -0
  102. data/lib/action_view/template/text.rb +11 -10
  103. data/lib/action_view/template/types.rb +18 -18
  104. data/lib/action_view/template.rb +146 -90
  105. data/lib/action_view/test_case.rb +52 -32
  106. data/lib/action_view/testing/resolvers.rb +46 -34
  107. data/lib/action_view/unbound_template.rb +31 -0
  108. data/lib/action_view/version.rb +3 -1
  109. data/lib/action_view/view_paths.rb +48 -31
  110. data/lib/action_view.rb +11 -8
  111. data/lib/assets/compiled/rails-ujs.js +746 -0
  112. metadata +41 -32
  113. data/lib/action_view/helpers/record_tag_helper.rb +0 -108
  114. data/lib/action_view/tasks/dependencies.rake +0 -23
@@ -1,20 +1,25 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  module Helpers
3
5
  module Tags # :nodoc:
4
6
  class SearchField < TextField # :nodoc:
5
7
  def render
6
- super do |options|
7
- if options["autosave"]
8
- if options["autosave"] == true
9
- options["autosave"] = request.host.split(".").reverse.join(".")
10
- end
11
- options["results"] ||= 10
12
- end
8
+ options = @options.stringify_keys
13
9
 
14
- if options["onsearch"]
15
- options["incremental"] = true unless options.has_key?("incremental")
10
+ if options["autosave"]
11
+ if options["autosave"] == true
12
+ options["autosave"] = request.host.split(".").reverse.join(".")
16
13
  end
14
+ options["results"] ||= 10
17
15
  end
16
+
17
+ if options["onsearch"]
18
+ options["incremental"] = true unless options.has_key?("incremental")
19
+ end
20
+
21
+ @options = options
22
+ super
18
23
  end
19
24
  end
20
25
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  module Helpers
3
5
  module Tags # :nodoc:
@@ -13,8 +15,8 @@ module ActionView
13
15
 
14
16
  def render
15
17
  option_tags_options = {
16
- :selected => @options.fetch(:selected) { value(@object) },
17
- :disabled => @options[:disabled]
18
+ selected: @options.fetch(:selected) { value },
19
+ disabled: @options[:disabled]
18
20
  }
19
21
 
20
22
  option_tags = if grouped_choices?
@@ -27,14 +29,13 @@ module ActionView
27
29
  end
28
30
 
29
31
  private
30
-
31
- # Grouped choices look like this:
32
- #
33
- # [nil, []]
34
- # { nil => [] }
35
- def grouped_choices?
36
- !@choices.empty? && @choices.first.respond_to?(:last) && Array === @choices.first.last
37
- end
32
+ # Grouped choices look like this:
33
+ #
34
+ # [nil, []]
35
+ # { nil => [] }
36
+ def grouped_choices?
37
+ !@choices.blank? && @choices.first.respond_to?(:last) && Array === @choices.first.last
38
+ end
38
39
  end
39
40
  end
40
41
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  module Helpers
3
5
  module Tags # :nodoc:
@@ -1,4 +1,6 @@
1
- require 'action_view/helpers/tags/placeholderable'
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view/helpers/tags/placeholderable"
2
4
 
3
5
  module ActionView
4
6
  module Helpers
@@ -14,7 +16,7 @@ module ActionView
14
16
  options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
15
17
  end
16
18
 
17
- content_tag("textarea", options.delete("value") { value_before_type_cast(object) }, options)
19
+ content_tag("textarea", options.delete("value") { value_before_type_cast }, options)
18
20
  end
19
21
  end
20
22
  end
@@ -1,4 +1,6 @@
1
- require 'action_view/helpers/tags/placeholderable'
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view/helpers/tags/placeholderable"
2
4
 
3
5
  module ActionView
4
6
  module Helpers
@@ -10,23 +12,21 @@ module ActionView
10
12
  options = @options.stringify_keys
11
13
  options["size"] = options["maxlength"] unless options.key?("size")
12
14
  options["type"] ||= field_type
13
- options["value"] = options.fetch("value") { value_before_type_cast(object) } unless field_type == "file"
14
- yield options if block_given?
15
+ options["value"] = options.fetch("value") { value_before_type_cast } unless field_type == "file"
15
16
  add_default_name_and_id(options)
16
17
  tag("input", options)
17
18
  end
18
19
 
19
20
  class << self
20
21
  def field_type
21
- @field_type ||= self.name.split("::").last.sub("Field", "").downcase
22
+ @field_type ||= name.split("::").last.sub("Field", "").downcase
22
23
  end
23
24
  end
24
25
 
25
26
  private
26
-
27
- def field_type
28
- self.class.field_type
29
- end
27
+ def field_type
28
+ self.class.field_type
29
+ end
30
30
  end
31
31
  end
32
32
  end
@@ -1,9 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  module Helpers
3
5
  module Tags # :nodoc:
4
6
  class TimeField < DatetimeField # :nodoc:
5
7
  private
6
-
7
8
  def format_date(value)
8
9
  value.try(:strftime, "%T.%L")
9
10
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  module Helpers
3
5
  module Tags # :nodoc:
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  module Helpers
3
5
  module Tags # :nodoc:
@@ -11,7 +13,7 @@ module ActionView
11
13
 
12
14
  def render
13
15
  select_content_tag(
14
- time_zone_options_for_select(value(@object) || @options[:default], @priority_zones, @options[:model] || ActiveSupport::TimeZone), @options, @html_options
16
+ time_zone_options_for_select(value || @options[:default], @priority_zones, @options[:model] || ActiveSupport::TimeZone), @options, @html_options
15
17
  )
16
18
  end
17
19
  end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  module Helpers
3
5
  module Tags # :nodoc:
4
6
  class Translator # :nodoc:
5
- def initialize(object, object_name, method_and_value, scope)
7
+ def initialize(object, object_name, method_and_value, scope:)
6
8
  @object_name = object_name.gsub(/\[(.*)_attributes\]\[\d+\]/, '.\1')
7
9
  @method_and_value = method_and_value
8
10
  @scope = scope
@@ -14,26 +16,23 @@ module ActionView
14
16
  translated_attribute || human_attribute_name
15
17
  end
16
18
 
17
- protected
18
-
19
- attr_reader :object_name, :method_and_value, :scope, :model
20
-
21
19
  private
20
+ attr_reader :object_name, :method_and_value, :scope, :model
22
21
 
23
- def i18n_default
24
- if model
25
- key = model.model_name.i18n_key
26
- ["#{key}.#{method_and_value}".to_sym, ""]
27
- else
28
- ""
22
+ def i18n_default
23
+ if model
24
+ key = model.model_name.i18n_key
25
+ ["#{key}.#{method_and_value}".to_sym, ""]
26
+ else
27
+ ""
28
+ end
29
29
  end
30
- end
31
30
 
32
- def human_attribute_name
33
- if model && model.class.respond_to?(:human_attribute_name)
34
- model.class.human_attribute_name(method_and_value)
31
+ def human_attribute_name
32
+ if model && model.class.respond_to?(:human_attribute_name)
33
+ model.class.human_attribute_name(method_and_value)
34
+ end
35
35
  end
36
- end
37
36
  end
38
37
  end
39
38
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  module Helpers
3
5
  module Tags # :nodoc:
@@ -1,9 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  module Helpers
3
5
  module Tags # :nodoc:
4
6
  class WeekField < DatetimeField # :nodoc:
5
7
  private
6
-
7
8
  def format_date(value)
8
9
  value.try(:strftime, "%Y-W%V")
9
10
  end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
- module Helpers
4
+ module Helpers #:nodoc:
3
5
  module Tags #:nodoc:
4
6
  extend ActiveSupport::Autoload
5
7
 
@@ -1,5 +1,7 @@
1
- require 'active_support/core_ext/string/filters'
2
- require 'active_support/core_ext/array/extract_options'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/filters"
4
+ require "active_support/core_ext/array/extract_options"
3
5
 
4
6
  module ActionView
5
7
  # = Action View Text Helpers
@@ -11,9 +13,9 @@ module ActionView
11
13
  #
12
14
  # ==== Sanitization
13
15
  #
14
- # Most text helpers by default sanitize the given content, but do not escape it.
15
- # This means HTML tags will appear in the page but all malicious code will be removed.
16
- # Let's look at some examples using the +simple_format+ method:
16
+ # Most text helpers that generate HTML output sanitize the given input by default,
17
+ # but do not escape it. This means HTML tags will appear in the page but all malicious
18
+ # code will be removed. Let's look at some examples using the +simple_format+ method:
17
19
  #
18
20
  # simple_format('<a href="http://example.com/">Example</a>')
19
21
  # # => "<p><a href=\"http://example.com/\">Example</a></p>"
@@ -103,7 +105,9 @@ module ActionView
103
105
  # Highlights one or more +phrases+ everywhere in +text+ by inserting it into
104
106
  # a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt>
105
107
  # as a single-quoted string with <tt>\1</tt> where the phrase is to be inserted (defaults to
106
- # '<mark>\1</mark>') or passing a block that receives each matched term.
108
+ # '<mark>\1</mark>') or passing a block that receives each matched term. By default +text+
109
+ # is sanitized to prevent possible XSS attacks. If the input is trustworthy, passing false
110
+ # for <tt>:sanitize</tt> will turn sanitizing off.
107
111
  #
108
112
  # highlight('You searched for: rails', 'rails')
109
113
  # # => You searched for: <mark>rails</mark>
@@ -122,6 +126,9 @@ module ActionView
122
126
  #
123
127
  # highlight('You searched for: rails', 'rails') { |match| link_to(search_path(q: match, match)) }
124
128
  # # => You searched for: <a href="search?q=rails">rails</a>
129
+ #
130
+ # highlight('<a href="javascript:alert(\'no!\')">ruby</a> on rails', 'rails', sanitize: false)
131
+ # # => <a href="javascript:alert('no!')">ruby</a> on <mark>rails</mark>
125
132
  def highlight(text, phrases, options = {})
126
133
  text = sanitize(text) if options.fetch(:sanitize, true)
127
134
 
@@ -130,7 +137,7 @@ module ActionView
130
137
  else
131
138
  match = Array(phrases).map do |p|
132
139
  Regexp === p ? p.to_s : Regexp.escape(p)
133
- end.join('|')
140
+ end.join("|")
134
141
 
135
142
  if block_given?
136
143
  text.gsub(/(#{match})(?![^<]*?>)/i) { |found| yield found }
@@ -146,7 +153,7 @@ module ActionView
146
153
  # defined in <tt>:radius</tt> (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+,
147
154
  # then the <tt>:omission</tt> option (which defaults to "...") will be prepended/appended accordingly. Use the
148
155
  # <tt>:separator</tt> option to choose the delimitation. The resulting string will be stripped in any case. If the +phrase+
149
- # isn't found, nil is returned.
156
+ # isn't found, +nil+ is returned.
150
157
  #
151
158
  # excerpt('This is an example', 'an', radius: 5)
152
159
  # # => ...s is an exam...
@@ -181,8 +188,8 @@ module ActionView
181
188
 
182
189
  unless separator.empty?
183
190
  text.split(separator).each do |value|
184
- if value.match(regex)
185
- regex = phrase = value
191
+ if value.match?(regex)
192
+ phrase = value
186
193
  break
187
194
  end
188
195
  end
@@ -199,7 +206,12 @@ module ActionView
199
206
 
200
207
  # Attempts to pluralize the +singular+ word unless +count+ is 1. If
201
208
  # +plural+ is supplied, it will use that when count is > 1, otherwise
202
- # it will use the Inflector to determine the plural form.
209
+ # it will use the Inflector to determine the plural form for the given locale,
210
+ # which defaults to I18n.locale
211
+ #
212
+ # The word will be pluralized using rules defined for the locale
213
+ # (you must define your own inflection rules for languages other than English).
214
+ # See ActiveSupport::Inflector.pluralize
203
215
  #
204
216
  # pluralize(1, 'person')
205
217
  # # => 1 person
@@ -207,16 +219,19 @@ module ActionView
207
219
  # pluralize(2, 'person')
208
220
  # # => 2 people
209
221
  #
210
- # pluralize(3, 'person', 'users')
222
+ # pluralize(3, 'person', plural: 'users')
211
223
  # # => 3 users
212
224
  #
213
225
  # pluralize(0, 'person')
214
226
  # # => 0 people
215
- def pluralize(count, singular, plural = nil)
216
- word = if (count == 1 || count =~ /^1(\.0+)?$/)
227
+ #
228
+ # pluralize(2, 'Person', locale: :de)
229
+ # # => 2 Personen
230
+ def pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale)
231
+ word = if count == 1 || count.to_s =~ /^1(\.0+)?$/
217
232
  singular
218
233
  else
219
- plural || singular.pluralize
234
+ plural || singular.pluralize(locale)
220
235
  end
221
236
 
222
237
  "#{count || 0} #{word}"
@@ -237,19 +252,23 @@ module ActionView
237
252
  #
238
253
  # word_wrap('Once upon a time', line_width: 1)
239
254
  # # => Once\nupon\na\ntime
240
- def word_wrap(text, options = {})
241
- line_width = options.fetch(:line_width, 80)
242
-
255
+ #
256
+ # You can also specify a custom +break_sequence+ ("\n" by default)
257
+ #
258
+ # word_wrap('Once upon a time', line_width: 1, break_sequence: "\r\n")
259
+ # # => Once\r\nupon\r\na\r\ntime
260
+ def word_wrap(text, line_width: 80, break_sequence: "\n")
243
261
  text.split("\n").collect! do |line|
244
- line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line
245
- end * "\n"
262
+ line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").rstrip : line
263
+ end * break_sequence
246
264
  end
247
265
 
248
266
  # Returns +text+ transformed into HTML using simple formatting rules.
249
- # Two or more consecutive newlines(<tt>\n\n</tt>) are considered as a
250
- # paragraph and wrapped in <tt><p></tt> tags. One newline (<tt>\n</tt>) is
251
- # considered as a linebreak and a <tt><br /></tt> tag is appended. This
252
- # method does not remove the newlines from the +text+.
267
+ # Two or more consecutive newlines(<tt>\n\n</tt> or <tt>\r\n\r\n</tt>) are
268
+ # considered a paragraph and wrapped in <tt><p></tt> tags. One newline
269
+ # (<tt>\n</tt> or <tt>\r\n</tt>) is considered a linebreak and a
270
+ # <tt><br /></tt> tag is appended. This method does not remove the
271
+ # newlines from the +text+.
253
272
  #
254
273
  # You can pass any HTML attributes into <tt>html_options</tt>. These
255
274
  # will be added to all created paragraphs.
@@ -309,7 +328,7 @@ module ActionView
309
328
  # <table>
310
329
  # <% @items.each do |item| %>
311
330
  # <tr class="<%= cycle("odd", "even") -%>">
312
- # <td>item</td>
331
+ # <td><%= item %></td>
313
332
  # </tr>
314
333
  # <% end %>
315
334
  # </table>
@@ -334,7 +353,7 @@ module ActionView
334
353
  # <% end %>
335
354
  def cycle(first_value, *values)
336
355
  options = values.extract_options!
337
- name = options.fetch(:name, 'default')
356
+ name = options.fetch(:name, "default")
338
357
 
339
358
  values.unshift(*first_value)
340
359
 
@@ -403,22 +422,21 @@ module ActionView
403
422
  def to_s
404
423
  value = @values[@index].to_s
405
424
  @index = next_index
406
- return value
425
+ value
407
426
  end
408
427
 
409
428
  private
429
+ def next_index
430
+ step_index(1)
431
+ end
410
432
 
411
- def next_index
412
- step_index(1)
413
- end
414
-
415
- def previous_index
416
- step_index(-1)
417
- end
433
+ def previous_index
434
+ step_index(-1)
435
+ end
418
436
 
419
- def step_index(n)
420
- (@index + n) % @values.size
421
- end
437
+ def step_index(n)
438
+ (@index + n) % @values.size
439
+ end
422
440
  end
423
441
 
424
442
  private
@@ -427,7 +445,7 @@ module ActionView
427
445
  # uses an instance variable of ActionView::Base.
428
446
  def get_cycle(name)
429
447
  @_cycles = Hash.new unless defined?(@_cycles)
430
- return @_cycles[name]
448
+ @_cycles[name]
431
449
  end
432
450
 
433
451
  def set_cycle(name, cycle_object)
@@ -1,99 +1,143 @@
1
- require 'action_view/helpers/tag_helper'
2
- require 'active_support/core_ext/string/access'
3
- require 'i18n/exceptions'
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view/helpers/tag_helper"
4
+ require "active_support/core_ext/string/access"
5
+ require "i18n/exceptions"
4
6
 
5
7
  module ActionView
6
8
  # = Action View Translation Helpers
7
- module Helpers
9
+ module Helpers #:nodoc:
8
10
  module TranslationHelper
11
+ extend ActiveSupport::Concern
12
+
9
13
  include TagHelper
10
- # Delegates to <tt>I18n#translate</tt> but also performs three additional functions.
14
+
15
+ included do
16
+ mattr_accessor :debug_missing_translation, default: true
17
+ end
18
+
19
+ # Delegates to <tt>I18n#translate</tt> but also performs three additional
20
+ # functions.
11
21
  #
12
- # First, it will ensure that any thrown +MissingTranslation+ messages will be turned
13
- # into inline spans that:
22
+ # First, it will ensure that any thrown +MissingTranslation+ messages will
23
+ # be rendered as inline spans that:
14
24
  #
15
- # * have a "translation-missing" class set,
16
- # * contain the missing key as a title attribute and
17
- # * a titleized version of the last key segment as a text.
25
+ # * Have a <tt>translation-missing</tt> class applied
26
+ # * Contain the missing key as the value of the +title+ attribute
27
+ # * Have a titleized version of the last key segment as text
18
28
  #
19
- # E.g. the value returned for a missing translation key :"blog.post.title" will be
20
- # <span class="translation_missing" title="translation missing: en.blog.post.title">Title</span>.
21
- # This way your views will display rather reasonable strings but it will still
22
- # be easy to spot missing translations.
29
+ # For example, the value returned for the missing translation key
30
+ # <tt>"blog.post.title"</tt> will be:
23
31
  #
24
- # Second, it'll scope the key by the current partial if the key starts
25
- # with a period. So if you call <tt>translate(".foo")</tt> from the
26
- # <tt>people/index.html.erb</tt> template, you'll actually be calling
27
- # <tt>I18n.translate("people.index.foo")</tt>. This makes it less repetitive
28
- # to translate many keys within the same partials and gives you a simple framework
29
- # for scoping them consistently. If you don't prepend the key with a period,
30
- # nothing is converted.
32
+ # <span
33
+ # class="translation_missing"
34
+ # title="translation missing: en.blog.post.title">Title</span>
31
35
  #
32
- # Third, it'll mark the translation as safe HTML if the key has the suffix
33
- # "_html" or the last element of the key is the word "html". For example,
34
- # calling translate("footer_html") or translate("footer.html") will return
35
- # a safe HTML string that won't be escaped by other HTML helper methods. This
36
- # naming convention helps to identify translations that include HTML tags so that
37
- # you know what kind of output to expect when you call translate in a template.
38
- def translate(key, options = {})
39
- options = options.dup
40
- has_default = options.has_key?(:default)
41
- remaining_defaults = Array(options.delete(:default)).compact
42
-
43
- if has_default && !remaining_defaults.first.kind_of?(Symbol)
44
- options[:default] = remaining_defaults
36
+ # This allows for views to display rather reasonable strings while still
37
+ # giving developers a way to find missing translations.
38
+ #
39
+ # If you would prefer missing translations to raise an error, you can
40
+ # opt out of span-wrapping behavior globally by setting
41
+ # <tt>ActionView::Base.raise_on_missing_translations = true</tt> or
42
+ # individually by passing <tt>raise: true</tt> as an option to
43
+ # <tt>translate</tt>.
44
+ #
45
+ # Second, if the key starts with a period <tt>translate</tt> will scope
46
+ # the key by the current partial. Calling <tt>translate(".foo")</tt> from
47
+ # the <tt>people/index.html.erb</tt> template is equivalent to calling
48
+ # <tt>translate("people.index.foo")</tt>. This makes it less
49
+ # repetitive to translate many keys within the same partial and provides
50
+ # a convention to scope keys consistently.
51
+ #
52
+ # Third, the translation will be marked as <tt>html_safe</tt> if the key
53
+ # has the suffix "_html" or the last element of the key is "html". Calling
54
+ # <tt>translate("footer_html")</tt> or <tt>translate("footer.html")</tt>
55
+ # will return an HTML safe string that won't be escaped by other HTML
56
+ # helper methods. This naming convention helps to identify translations
57
+ # that include HTML tags so that you know what kind of output to expect
58
+ # when you call translate in a template and translators know which keys
59
+ # they can provide HTML values for.
60
+ def translate(key, **options)
61
+ if options.has_key?(:default)
62
+ remaining_defaults = Array.wrap(options.delete(:default)).compact
63
+ options[:default] = remaining_defaults unless remaining_defaults.first.kind_of?(Symbol)
45
64
  end
46
65
 
47
66
  # If the user has explicitly decided to NOT raise errors, pass that option to I18n.
48
67
  # Otherwise, tell I18n to raise an exception, which we rescue further in this method.
49
68
  # Note: `raise_error` refers to us re-raising the error in this method. I18n is forced to raise by default.
50
- if options[:raise] == false || (options.key?(:rescue_format) && options[:rescue_format].nil?)
69
+ if options[:raise] == false
51
70
  raise_error = false
52
71
  i18n_raise = false
53
72
  else
54
- raise_error = options[:raise] || options[:rescue_format] || ActionView::Base.raise_on_missing_translations
73
+ raise_error = options[:raise] || ActionView::Base.raise_on_missing_translations
55
74
  i18n_raise = true
56
75
  end
57
76
 
58
77
  if html_safe_translation_key?(key)
59
78
  html_safe_options = options.dup
79
+
60
80
  options.except(*I18n::RESERVED_KEYS).each do |name, value|
61
81
  unless name == :count && value.is_a?(Numeric)
62
82
  html_safe_options[name] = ERB::Util.html_escape(value.to_s)
63
83
  end
64
84
  end
65
- translation = I18n.translate(scope_key_by_partial(key), html_safe_options.merge(raise: i18n_raise))
66
85
 
67
- translation.respond_to?(:html_safe) ? translation.html_safe : translation
86
+ html_safe_options[:default] = MISSING_TRANSLATION unless html_safe_options[:default].blank?
87
+
88
+ translation = I18n.translate(scope_key_by_partial(key), **html_safe_options.merge(raise: i18n_raise))
89
+
90
+ if translation.equal?(MISSING_TRANSLATION)
91
+ options[:default].first
92
+ elsif translation.respond_to?(:map)
93
+ translation.map { |element| element.respond_to?(:html_safe) ? element.html_safe : element }
94
+ else
95
+ translation.respond_to?(:html_safe) ? translation.html_safe : translation
96
+ end
68
97
  else
69
- I18n.translate(scope_key_by_partial(key), options.merge(raise: i18n_raise))
98
+ I18n.translate(scope_key_by_partial(key), **options.merge(raise: i18n_raise))
70
99
  end
71
100
  rescue I18n::MissingTranslationData => e
72
101
  if remaining_defaults.present?
73
- translate remaining_defaults.shift, options.merge(default: remaining_defaults)
102
+ translate remaining_defaults.shift, **options.merge(default: remaining_defaults)
74
103
  else
75
104
  raise e if raise_error
76
105
 
77
106
  keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
78
- content_tag('span', keys.last.to_s.titleize, :class => 'translation_missing', :title => "translation missing: #{keys.join('.')}")
107
+ title = +"translation missing: #{keys.join('.')}"
108
+
109
+ interpolations = options.except(:default, :scope)
110
+ if interpolations.any?
111
+ title << ", " << interpolations.map { |k, v| "#{k}: #{ERB::Util.html_escape(v)}" }.join(", ")
112
+ end
113
+
114
+ return title unless ActionView::Base.debug_missing_translation
115
+
116
+ content_tag("span", keys.last.to_s.titleize, class: "translation_missing", title: title)
79
117
  end
80
118
  end
81
119
  alias :t :translate
82
120
 
83
121
  # Delegates to <tt>I18n.localize</tt> with no additional functionality.
84
122
  #
85
- # See http://rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
123
+ # See https://www.rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
86
124
  # for more information.
87
- def localize(*args)
88
- I18n.localize(*args)
125
+ def localize(object, **options)
126
+ I18n.localize(object, **options)
89
127
  end
90
128
  alias :l :localize
91
129
 
92
130
  private
131
+ MISSING_TRANSLATION = Object.new
132
+ private_constant :MISSING_TRANSLATION
133
+
93
134
  def scope_key_by_partial(key)
94
- if key.to_s.first == "."
135
+ stringified_key = key.to_s
136
+ if stringified_key.first == "."
95
137
  if @virtual_path
96
- @virtual_path.gsub(%r{/_?}, ".") + key.to_s
138
+ @_scope_key_by_partial_cache ||= {}
139
+ @_scope_key_by_partial_cache[@virtual_path] ||= @virtual_path.gsub(%r{/_?}, ".")
140
+ "#{@_scope_key_by_partial_cache[@virtual_path]}#{stringified_key}"
97
141
  else
98
142
  raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
99
143
  end
@@ -103,7 +147,7 @@ module ActionView
103
147
  end
104
148
 
105
149
  def html_safe_translation_key?(key)
106
- key.to_s =~ /(\b|_|\.)html$/
150
+ /(?:_|\b)html\z/.match?(key.to_s)
107
151
  end
108
152
  end
109
153
  end