actionview 4.1.13 → 6.1.3.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionview might be problematic. Click here for more details.

Files changed (124) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +181 -359
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +12 -6
  5. data/lib/action_view/base.rb +115 -43
  6. data/lib/action_view/buffers.rb +22 -4
  7. data/lib/action_view/cache_expiry.rb +52 -0
  8. data/lib/action_view/context.rb +8 -12
  9. data/lib/action_view/dependency_tracker.rb +61 -21
  10. data/lib/action_view/digestor.rb +89 -84
  11. data/lib/action_view/flows.rb +12 -13
  12. data/lib/action_view/gem_version.rb +6 -4
  13. data/lib/action_view/helpers/active_model_helper.rb +16 -11
  14. data/lib/action_view/helpers/asset_tag_helper.rb +311 -105
  15. data/lib/action_view/helpers/asset_url_helper.rb +197 -80
  16. data/lib/action_view/helpers/atom_feed_helper.rb +20 -17
  17. data/lib/action_view/helpers/cache_helper.rb +109 -45
  18. data/lib/action_view/helpers/capture_helper.rb +20 -22
  19. data/lib/action_view/helpers/controller_helper.rb +15 -4
  20. data/lib/action_view/helpers/csp_helper.rb +26 -0
  21. data/lib/action_view/helpers/csrf_helper.rb +8 -6
  22. data/lib/action_view/helpers/date_helper.rb +245 -140
  23. data/lib/action_view/helpers/debug_helper.rb +14 -17
  24. data/lib/action_view/helpers/form_helper.rb +875 -148
  25. data/lib/action_view/helpers/form_options_helper.rb +128 -82
  26. data/lib/action_view/helpers/form_tag_helper.rb +253 -91
  27. data/lib/action_view/helpers/javascript_helper.rb +37 -15
  28. data/lib/action_view/helpers/number_helper.rb +100 -77
  29. data/lib/action_view/helpers/output_safety_helper.rb +42 -10
  30. data/lib/action_view/helpers/rendering_helper.rb +26 -15
  31. data/lib/action_view/helpers/sanitize_helper.rb +79 -164
  32. data/lib/action_view/helpers/tag_helper.rb +277 -64
  33. data/lib/action_view/helpers/tags/base.rb +143 -92
  34. data/lib/action_view/helpers/tags/check_box.rb +20 -19
  35. data/lib/action_view/helpers/tags/checkable.rb +4 -2
  36. data/lib/action_view/helpers/tags/collection_check_boxes.rb +12 -30
  37. data/lib/action_view/helpers/tags/collection_helpers.rb +69 -36
  38. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +6 -12
  39. data/lib/action_view/helpers/tags/collection_select.rb +4 -2
  40. data/lib/action_view/helpers/tags/color_field.rb +4 -3
  41. data/lib/action_view/helpers/tags/date_field.rb +3 -2
  42. data/lib/action_view/helpers/tags/date_select.rb +38 -37
  43. data/lib/action_view/helpers/tags/datetime_field.rb +14 -5
  44. data/lib/action_view/helpers/tags/datetime_local_field.rb +3 -2
  45. data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
  46. data/lib/action_view/helpers/tags/email_field.rb +2 -0
  47. data/lib/action_view/helpers/tags/file_field.rb +2 -0
  48. data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
  49. data/lib/action_view/helpers/tags/hidden_field.rb +2 -0
  50. data/lib/action_view/helpers/tags/label.rb +41 -22
  51. data/lib/action_view/helpers/tags/month_field.rb +3 -2
  52. data/lib/action_view/helpers/tags/number_field.rb +2 -0
  53. data/lib/action_view/helpers/tags/password_field.rb +3 -1
  54. data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
  55. data/lib/action_view/helpers/tags/radio_button.rb +7 -6
  56. data/lib/action_view/helpers/tags/range_field.rb +2 -0
  57. data/lib/action_view/helpers/tags/search_field.rb +3 -0
  58. data/lib/action_view/helpers/tags/select.rb +11 -10
  59. data/lib/action_view/helpers/tags/tel_field.rb +2 -0
  60. data/lib/action_view/helpers/tags/text_area.rb +7 -1
  61. data/lib/action_view/helpers/tags/text_field.rb +11 -7
  62. data/lib/action_view/helpers/tags/time_field.rb +3 -2
  63. data/lib/action_view/helpers/tags/time_select.rb +2 -0
  64. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
  65. data/lib/action_view/helpers/tags/translator.rb +39 -0
  66. data/lib/action_view/helpers/tags/url_field.rb +2 -0
  67. data/lib/action_view/helpers/tags/week_field.rb +3 -2
  68. data/lib/action_view/helpers/tags.rb +4 -1
  69. data/lib/action_view/helpers/text_helper.rb +80 -45
  70. data/lib/action_view/helpers/translation_helper.rb +148 -67
  71. data/lib/action_view/helpers/url_helper.rb +289 -147
  72. data/lib/action_view/helpers.rb +5 -3
  73. data/lib/action_view/layouts.rb +68 -63
  74. data/lib/action_view/log_subscriber.rb +80 -13
  75. data/lib/action_view/lookup_context.rb +137 -92
  76. data/lib/action_view/model_naming.rb +4 -2
  77. data/lib/action_view/path_set.rb +30 -16
  78. data/lib/action_view/railtie.rb +62 -13
  79. data/lib/action_view/record_identifier.rb +53 -26
  80. data/lib/action_view/renderer/abstract_renderer.rb +152 -13
  81. data/lib/action_view/renderer/collection_renderer.rb +196 -0
  82. data/lib/action_view/renderer/object_renderer.rb +34 -0
  83. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +102 -0
  84. data/lib/action_view/renderer/partial_renderer.rb +61 -261
  85. data/lib/action_view/renderer/renderer.rb +67 -6
  86. data/lib/action_view/renderer/streaming_template_renderer.rb +58 -54
  87. data/lib/action_view/renderer/template_renderer.rb +83 -75
  88. data/lib/action_view/rendering.rb +73 -46
  89. data/lib/action_view/routing_url_for.rb +54 -17
  90. data/lib/action_view/tasks/cache_digests.rake +25 -0
  91. data/lib/action_view/template/error.rb +44 -29
  92. data/lib/action_view/template/handlers/builder.rb +12 -13
  93. data/lib/action_view/template/handlers/erb/erubi.rb +89 -0
  94. data/lib/action_view/template/handlers/erb.rb +23 -89
  95. data/lib/action_view/template/handlers/html.rb +11 -0
  96. data/lib/action_view/template/handlers/raw.rb +4 -4
  97. data/lib/action_view/template/handlers.rb +22 -9
  98. data/lib/action_view/template/html.rb +10 -11
  99. data/lib/action_view/template/inline.rb +22 -0
  100. data/lib/action_view/template/raw_file.rb +25 -0
  101. data/lib/action_view/template/renderable.rb +24 -0
  102. data/lib/action_view/template/resolver.rb +267 -181
  103. data/lib/action_view/template/sources/file.rb +17 -0
  104. data/lib/action_view/template/sources.rb +13 -0
  105. data/lib/action_view/template/text.rb +8 -10
  106. data/lib/action_view/template/types.rb +18 -18
  107. data/lib/action_view/template.rb +109 -99
  108. data/lib/action_view/test_case.rb +73 -53
  109. data/lib/action_view/testing/resolvers.rb +24 -33
  110. data/lib/action_view/unbound_template.rb +31 -0
  111. data/lib/action_view/version.rb +3 -1
  112. data/lib/action_view/view_paths.rb +74 -44
  113. data/lib/action_view.rb +14 -9
  114. data/lib/assets/compiled/rails-ujs.js +746 -0
  115. metadata +71 -26
  116. data/lib/action_view/helpers/record_tag_helper.rb +0 -108
  117. data/lib/action_view/tasks/dependencies.rake +0 -23
  118. data/lib/action_view/vendor/html-scanner/html/document.rb +0 -68
  119. data/lib/action_view/vendor/html-scanner/html/node.rb +0 -532
  120. data/lib/action_view/vendor/html-scanner/html/sanitizer.rb +0 -188
  121. data/lib/action_view/vendor/html-scanner/html/selector.rb +0 -830
  122. data/lib/action_view/vendor/html-scanner/html/tokenizer.rb +0 -107
  123. data/lib/action_view/vendor/html-scanner/html/version.rb +0 -11
  124. data/lib/action_view/vendor/html-scanner.rb +0 -20
@@ -1,34 +1,39 @@
1
- require 'action_view/helpers/tag_helper'
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view/helpers/tag_helper"
2
4
 
3
5
  module ActionView
4
- module Helpers
6
+ module Helpers #:nodoc:
5
7
  module JavaScriptHelper
6
8
  JS_ESCAPE_MAP = {
7
9
  '\\' => '\\\\',
8
- '</' => '<\/',
10
+ "</" => '<\/',
9
11
  "\r\n" => '\n',
10
12
  "\n" => '\n',
11
13
  "\r" => '\n',
12
14
  '"' => '\\"',
13
- "'" => "\\'"
15
+ "'" => "\\'",
16
+ "`" => "\\`",
17
+ "$" => "\\$"
14
18
  }
15
19
 
16
- JS_ESCAPE_MAP["\342\200\250".force_encoding(Encoding::UTF_8).encode!] = '&#x2028;'
17
- JS_ESCAPE_MAP["\342\200\251".force_encoding(Encoding::UTF_8).encode!] = '&#x2029;'
20
+ JS_ESCAPE_MAP[(+"\342\200\250").force_encoding(Encoding::UTF_8).encode!] = "&#x2028;"
21
+ JS_ESCAPE_MAP[(+"\342\200\251").force_encoding(Encoding::UTF_8).encode!] = "&#x2029;"
18
22
 
19
23
  # Escapes carriage returns and single and double quotes for JavaScript segments.
20
24
  #
21
25
  # Also available through the alias j(). This is particularly helpful in JavaScript
22
26
  # responses, like:
23
27
  #
24
- # $('some_element').replaceWith('<%=j render 'some/element_template' %>');
28
+ # $('some_element').replaceWith('<%= j render 'some/element_template' %>');
25
29
  def escape_javascript(javascript)
26
- if javascript
27
- result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) {|match| JS_ESCAPE_MAP[match] }
28
- javascript.html_safe? ? result.html_safe : result
30
+ javascript = javascript.to_s
31
+ if javascript.empty?
32
+ result = ""
29
33
  else
30
- ''
34
+ result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"']|[`]|[$])/u, JS_ESCAPE_MAP)
31
35
  end
36
+ javascript.html_safe? ? result.html_safe : result
32
37
  end
33
38
 
34
39
  alias_method :j, :escape_javascript
@@ -46,13 +51,26 @@ module ActionView
46
51
  # +html_options+ may be a hash of attributes for the <tt>\<script></tt>
47
52
  # tag.
48
53
  #
49
- # javascript_tag "alert('All is good')", defer: 'defer'
50
- # # => <script defer="defer">alert('All is good')</script>
54
+ # javascript_tag "alert('All is good')", type: 'application/javascript'
55
+ #
56
+ # Returns:
57
+ # <script type="application/javascript">
58
+ # //<![CDATA[
59
+ # alert('All is good')
60
+ # //]]>
61
+ # </script>
51
62
  #
52
63
  # Instead of passing the content as an argument, you can also use a block
53
64
  # in which case, you pass your +html_options+ as the first parameter.
54
65
  #
55
- # <%= javascript_tag defer: 'defer' do -%>
66
+ # <%= javascript_tag type: 'application/javascript' do -%>
67
+ # alert('All is good')
68
+ # <% end -%>
69
+ #
70
+ # If you have a content security policy enabled then you can add an automatic
71
+ # nonce value by passing <tt>nonce: true</tt> as part of +html_options+. Example:
72
+ #
73
+ # <%= javascript_tag nonce: true do -%>
56
74
  # alert('All is good')
57
75
  # <% end -%>
58
76
  def javascript_tag(content_or_options_with_block = nil, html_options = {}, &block)
@@ -64,7 +82,11 @@ module ActionView
64
82
  content_or_options_with_block
65
83
  end
66
84
 
67
- content_tag(:script, javascript_cdata_section(content), html_options)
85
+ if html_options[:nonce] == true
86
+ html_options[:nonce] = content_security_policy_nonce
87
+ end
88
+
89
+ content_tag("script", javascript_cdata_section(content), html_options)
68
90
  end
69
91
 
70
92
  def javascript_cdata_section(content) #:nodoc:
@@ -1,13 +1,12 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- require 'active_support/core_ext/hash/keys'
4
- require 'active_support/core_ext/string/output_safety'
5
- require 'active_support/number_helper'
3
+ require "active_support/core_ext/hash/keys"
4
+ require "active_support/core_ext/string/output_safety"
5
+ require "active_support/number_helper"
6
6
 
7
7
  module ActionView
8
8
  # = Action View Number Helpers
9
9
  module Helpers #:nodoc:
10
-
11
10
  # Provides methods for converting numbers into formatted strings.
12
11
  # Methods are provided for phone numbers, currency, percentage,
13
12
  # precision, positional notation, file size and pretty printing.
@@ -15,7 +14,6 @@ module ActionView
15
14
  # Most methods expect a +number+ argument, and will return it
16
15
  # unchanged if can't be converted into a valid number.
17
16
  module NumberHelper
18
-
19
17
  # Raised when argument +number+ param given to the helpers is invalid and
20
18
  # the option :raise is set to +true+.
21
19
  class InvalidNumberError < StandardError
@@ -25,7 +23,7 @@ module ActionView
25
23
  end
26
24
  end
27
25
 
28
- # Formats a +number+ into a US phone number (e.g., (555)
26
+ # Formats a +number+ into a phone number (US by default e.g., (555)
29
27
  # 123-9876). You can customize the format in the +options+ hash.
30
28
  #
31
29
  # ==== Options
@@ -37,6 +35,8 @@ module ActionView
37
35
  # end of the generated number.
38
36
  # * <tt>:country_code</tt> - Sets the country code for the phone
39
37
  # number.
38
+ # * <tt>:pattern</tt> - Specifies how the number is divided into three
39
+ # groups with the custom regexp to override the default format.
40
40
  # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
41
41
  # the argument is invalid.
42
42
  #
@@ -54,6 +54,11 @@ module ActionView
54
54
  #
55
55
  # number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: ".")
56
56
  # # => +1.123.555.1234 x 1343
57
+ #
58
+ # number_to_phone(75561234567, pattern: /(\d{1,4})(\d{4})(\d{4})$/, area_code: true)
59
+ # # => "(755) 6123-4567"
60
+ # number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/)
61
+ # # => "133-1234-5678"
57
62
  def number_to_phone(number, options = {})
58
63
  return unless number
59
64
  options = options.symbolize_keys
@@ -65,6 +70,14 @@ module ActionView
65
70
  # Formats a +number+ into a currency string (e.g., $13.65). You
66
71
  # can customize the format in the +options+ hash.
67
72
  #
73
+ # The currency unit and number formatting of the current locale will be used
74
+ # unless otherwise specified in the provided options. No currency conversion
75
+ # is performed. If the user is given a way to change their locale, they will
76
+ # also be able to change the relative value of the currency displayed with
77
+ # this helper. If your application will ever support multiple locales, you
78
+ # may want to specify a constant <tt>:locale</tt> option or consider
79
+ # using a library capable of currency conversion.
80
+ #
68
81
  # ==== Options
69
82
  #
70
83
  # * <tt>:locale</tt> - Sets the locale to be used for formatting
@@ -81,12 +94,15 @@ module ActionView
81
94
  # (defaults to "%u%n"). Fields are <tt>%u</tt> for the
82
95
  # currency, and <tt>%n</tt> for the number.
83
96
  # * <tt>:negative_format</tt> - Sets the format for negative
84
- # numbers (defaults to prepending an hyphen to the formatted
97
+ # numbers (defaults to prepending a hyphen to the formatted
85
98
  # number given by <tt>:format</tt>). Accepts the same fields
86
99
  # than <tt>:format</tt>, except <tt>%n</tt> is here the
87
100
  # absolute value of the number.
88
101
  # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
89
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+).
90
106
  #
91
107
  # ==== Examples
92
108
  #
@@ -98,12 +114,16 @@ module ActionView
98
114
  #
99
115
  # number_to_currency("123a456", raise: true) # => InvalidNumberError
100
116
  #
117
+ # number_to_currency(-0.456789, precision: 0)
118
+ # # => "$0"
101
119
  # number_to_currency(-1234567890.50, negative_format: "(%u%n)")
102
120
  # # => ($1,234,567,890.50)
103
121
  # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "")
104
122
  # # => R$1234567890,50
105
123
  # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "", format: "%n %u")
106
124
  # # => 1234567890,50 R$
125
+ # number_to_currency(1234567890.50, strip_insignificant_zeros: true)
126
+ # # => "$1,234,567,890.5"
107
127
  def number_to_currency(number, options = {})
108
128
  delegate_number_helper_method(:number_to_currency, number, options)
109
129
  end
@@ -117,8 +137,8 @@ module ActionView
117
137
  # (defaults to current locale).
118
138
  # * <tt>:precision</tt> - Sets the precision of the number
119
139
  # (defaults to 3).
120
- # * <tt>:significant</tt> - If +true+, precision will be the #
121
- # of significant_digits. If +false+, the # of fractional
140
+ # * <tt>:significant</tt> - If +true+, precision will be the number
141
+ # of significant_digits. If +false+, the number of fractional
122
142
  # digits (defaults to +false+).
123
143
  # * <tt>:separator</tt> - Sets the separator between the
124
144
  # fractional and integer digits (defaults to ".").
@@ -141,7 +161,7 @@ module ActionView
141
161
  # number_to_percentage(302.24398923423, precision: 5) # => 302.24399%
142
162
  # number_to_percentage(1000, locale: :fr) # => 1 000,000%
143
163
  # number_to_percentage("98a") # => 98a%
144
- # number_to_percentage(100, format: "%n %") # => 100 %
164
+ # number_to_percentage(100, format: "%n %") # => 100.000 %
145
165
  #
146
166
  # number_to_percentage("98a", raise: true) # => InvalidNumberError
147
167
  def number_to_percentage(number, options = {})
@@ -160,6 +180,9 @@ module ActionView
160
180
  # to ",").
161
181
  # * <tt>:separator</tt> - Sets the separator between the
162
182
  # fractional and integer digits (defaults to ".").
183
+ # * <tt>:delimiter_pattern</tt> - Sets a custom regular expression used for
184
+ # deriving the placement of delimiter. Helpful when using currency formats
185
+ # like INR.
163
186
  # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
164
187
  # the argument is invalid.
165
188
  #
@@ -176,6 +199,9 @@ module ActionView
176
199
  # number_with_delimiter(98765432.98, delimiter: " ", separator: ",")
177
200
  # # => 98 765 432,98
178
201
  #
202
+ # number_with_delimiter("123456.78",
203
+ # delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/) # => "1,23,456.78"
204
+ #
179
205
  # number_with_delimiter("112a", raise: true) # => raise InvalidNumberError
180
206
  def number_with_delimiter(number, options = {})
181
207
  delegate_number_helper_method(:number_to_delimited, number, options)
@@ -192,8 +218,8 @@ module ActionView
192
218
  # (defaults to current locale).
193
219
  # * <tt>:precision</tt> - Sets the precision of the number
194
220
  # (defaults to 3).
195
- # * <tt>:significant</tt> - If +true+, precision will be the #
196
- # of significant_digits. If +false+, the # of fractional
221
+ # * <tt>:significant</tt> - If +true+, precision will be the number
222
+ # of significant_digits. If +false+, the number of fractional
197
223
  # digits (defaults to +false+).
198
224
  # * <tt>:separator</tt> - Sets the separator between the
199
225
  # fractional and integer digits (defaults to ".").
@@ -227,7 +253,7 @@ module ActionView
227
253
  end
228
254
 
229
255
  # Formats the bytes in +number+ into a more understandable
230
- # representation (e.g., giving it 1500 yields 1.5 KB). This
256
+ # representation (e.g., giving it 1500 yields 1.46 KB). This
231
257
  # method is useful for reporting file sizes to users. You can
232
258
  # customize the format in the +options+ hash.
233
259
  #
@@ -240,8 +266,8 @@ module ActionView
240
266
  # (defaults to current locale).
241
267
  # * <tt>:precision</tt> - Sets the precision of the number
242
268
  # (defaults to 3).
243
- # * <tt>:significant</tt> - If +true+, precision will be the #
244
- # of significant_digits. If +false+, the # of fractional
269
+ # * <tt>:significant</tt> - If +true+, precision will be the number
270
+ # of significant_digits. If +false+, the number of fractional
245
271
  # digits (defaults to +true+)
246
272
  # * <tt>:separator</tt> - Sets the separator between the
247
273
  # fractional and integer digits (defaults to ".").
@@ -250,8 +276,6 @@ module ActionView
250
276
  # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
251
277
  # insignificant zeros after the decimal separator (defaults to
252
278
  # +true+)
253
- # * <tt>:prefix</tt> - If +:si+ formats the number using the SI
254
- # prefix (defaults to :binary)
255
279
  # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
256
280
  # the argument is invalid.
257
281
  #
@@ -263,31 +287,27 @@ module ActionView
263
287
  # number_to_human_size(1234567) # => 1.18 MB
264
288
  # number_to_human_size(1234567890) # => 1.15 GB
265
289
  # number_to_human_size(1234567890123) # => 1.12 TB
290
+ # number_to_human_size(1234567890123456) # => 1.1 PB
291
+ # number_to_human_size(1234567890123456789) # => 1.07 EB
266
292
  # number_to_human_size(1234567, precision: 2) # => 1.2 MB
267
293
  # number_to_human_size(483989, precision: 2) # => 470 KB
268
294
  # number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB
269
- #
270
- # Non-significant zeros after the fractional separator are
271
- # stripped out by default (set
272
- # <tt>:strip_insignificant_zeros</tt> to +false+ to change
273
- # that):
274
- #
275
- # number_to_human_size(1234567890123, precision: 5) # => "1.1229 TB"
276
- # number_to_human_size(524288000, precision: 5) # => "500 MB"
295
+ # number_to_human_size(1234567890123, precision: 5) # => "1.1228 TB"
296
+ # number_to_human_size(524288000, precision: 5) # => "500 MB"
277
297
  def number_to_human_size(number, options = {})
278
298
  delegate_number_helper_method(:number_to_human_size, number, options)
279
299
  end
280
300
 
281
301
  # Pretty prints (formats and approximates) a number in a way it
282
- # is more readable by humans (eg.: 1200000000 becomes "1.2
302
+ # is more readable by humans (e.g.: 1200000000 becomes "1.2
283
303
  # Billion"). This is useful for numbers that can get very large
284
304
  # (and too hard to read).
285
305
  #
286
306
  # See <tt>number_to_human_size</tt> if you want to print a file
287
307
  # size.
288
308
  #
289
- # You can also define you own unit-quantifier names if you want
290
- # to use other decimal units (eg.: 1500 becomes "1.5
309
+ # You can also define your own unit-quantifier names if you want
310
+ # to use other decimal units (e.g.: 1500 becomes "1.5
291
311
  # kilometers", 0.150 becomes "150 milliliters", etc). You may
292
312
  # define a wide range of unit quantifiers, even fractional ones
293
313
  # (centi, deci, mili, etc).
@@ -298,8 +318,8 @@ module ActionView
298
318
  # (defaults to current locale).
299
319
  # * <tt>:precision</tt> - Sets the precision of the number
300
320
  # (defaults to 3).
301
- # * <tt>:significant</tt> - If +true+, precision will be the #
302
- # of significant_digits. If +false+, the # of fractional
321
+ # * <tt>:significant</tt> - If +true+, precision will be the number
322
+ # of significant_digits. If +false+, the number of fractional
303
323
  # digits (defaults to +true+)
304
324
  # * <tt>:separator</tt> - Sets the separator between the
305
325
  # fractional and integer digits (defaults to ".").
@@ -312,12 +332,12 @@ module ActionView
312
332
  # string containing an i18n scope where to find this hash. It
313
333
  # might have the following keys:
314
334
  # * *integers*: <tt>:unit</tt>, <tt>:ten</tt>,
315
- # *<tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>,
316
- # *<tt>:billion</tt>, <tt>:trillion</tt>,
317
- # *<tt>:quadrillion</tt>
335
+ # <tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>,
336
+ # <tt>:billion</tt>, <tt>:trillion</tt>,
337
+ # <tt>:quadrillion</tt>
318
338
  # * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>,
319
- # *<tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>,
320
- # *<tt>:pico</tt>, <tt>:femto</tt>
339
+ # <tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>,
340
+ # <tt>:pico</tt>, <tt>:femto</tt>
321
341
  # * <tt>:format</tt> - Sets the format of the output string
322
342
  # (defaults to "%n %u"). The field types are:
323
343
  # * %u - The quantifier (ex.: 'thousand')
@@ -343,11 +363,15 @@ module ActionView
343
363
  # separator: ',',
344
364
  # significant: false) # => "1,2 Million"
345
365
  #
366
+ # number_to_human(500000000, precision: 5) # => "500 Million"
367
+ # number_to_human(12345012345, significant: false) # => "12.345 Billion"
368
+ #
346
369
  # Non-significant zeros after the decimal separator are stripped
347
370
  # out by default (set <tt>:strip_insignificant_zeros</tt> to
348
371
  # +false+ to change that):
349
- # number_to_human(12345012345, significant_digits: 6) # => "12.345 Billion"
350
- # number_to_human(500000000, precision: 5) # => "500 Million"
372
+ #
373
+ # number_to_human(12.00001) # => "12"
374
+ # number_to_human(12.00001, strip_insignificant_zeros: false) # => "12.0"
351
375
  #
352
376
  # ==== Custom Unit Quantifiers
353
377
  #
@@ -381,54 +405,53 @@ module ActionView
381
405
  end
382
406
 
383
407
  private
408
+ def delegate_number_helper_method(method, number, options)
409
+ return unless number
410
+ options = escape_unsafe_options(options.symbolize_keys)
384
411
 
385
- def delegate_number_helper_method(method, number, options)
386
- return unless number
387
- options = escape_unsafe_options(options.symbolize_keys)
388
-
389
- wrap_with_output_safety_handling(number, options.delete(:raise)) {
390
- ActiveSupport::NumberHelper.public_send(method, number, options)
391
- }
392
- end
412
+ wrap_with_output_safety_handling(number, options.delete(:raise)) {
413
+ ActiveSupport::NumberHelper.public_send(method, number, options)
414
+ }
415
+ end
393
416
 
394
- def escape_unsafe_options(options)
395
- options[:format] = ERB::Util.html_escape(options[:format]) if options[:format]
396
- options[:negative_format] = ERB::Util.html_escape(options[:negative_format]) if options[:negative_format]
397
- options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator]
398
- options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter]
399
- options[:unit] = ERB::Util.html_escape(options[:unit]) if options[:unit] && !options[:unit].html_safe?
400
- options[:units] = escape_units(options[:units]) if options[:units] && Hash === options[:units]
401
- options
402
- end
417
+ def escape_unsafe_options(options)
418
+ options[:format] = ERB::Util.html_escape(options[:format]) if options[:format]
419
+ options[:negative_format] = ERB::Util.html_escape(options[:negative_format]) if options[:negative_format]
420
+ options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator]
421
+ options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter]
422
+ options[:unit] = ERB::Util.html_escape(options[:unit]) if options[:unit] && !options[:unit].html_safe?
423
+ options[:units] = escape_units(options[:units]) if options[:units] && Hash === options[:units]
424
+ options
425
+ end
403
426
 
404
- def escape_units(units)
405
- Hash[units.map do |k, v|
406
- [k, ERB::Util.html_escape(v)]
407
- end]
408
- end
427
+ def escape_units(units)
428
+ units.transform_values do |v|
429
+ ERB::Util.html_escape(v)
430
+ end
431
+ end
409
432
 
410
- def wrap_with_output_safety_handling(number, raise_on_invalid, &block)
411
- valid_float = valid_float?(number)
412
- raise InvalidNumberError, number if raise_on_invalid && !valid_float
433
+ def wrap_with_output_safety_handling(number, raise_on_invalid, &block)
434
+ valid_float = valid_float?(number)
435
+ raise InvalidNumberError, number if raise_on_invalid && !valid_float
413
436
 
414
- formatted_number = yield
437
+ formatted_number = yield
415
438
 
416
- if valid_float || number.html_safe?
417
- formatted_number.html_safe
418
- else
419
- formatted_number
439
+ if valid_float || number.html_safe?
440
+ formatted_number.html_safe
441
+ else
442
+ formatted_number
443
+ end
420
444
  end
421
- end
422
445
 
423
- def valid_float?(number)
424
- !parse_float(number, false).nil?
425
- end
446
+ def valid_float?(number)
447
+ !parse_float(number, false).nil?
448
+ end
426
449
 
427
- def parse_float(number, raise_error)
428
- Float(number)
429
- rescue ArgumentError, TypeError
430
- raise InvalidNumberError, number if raise_error
431
- end
450
+ def parse_float(number, raise_error)
451
+ Float(number)
452
+ rescue ArgumentError, TypeError
453
+ raise InvalidNumberError, number if raise_error
454
+ end
432
455
  end
433
456
  end
434
457
  end
@@ -1,4 +1,6 @@
1
- require 'active_support/core_ext/string/output_safety'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/output_safety"
2
4
 
3
5
  module ActionView #:nodoc:
4
6
  # = Action View Raw Output Helper
@@ -17,21 +19,51 @@ module ActionView #:nodoc:
17
19
  stringish.to_s.html_safe
18
20
  end
19
21
 
20
- # This method returns a html safe string similar to what <tt>Array#join</tt>
21
- # would return. All items in the array, including the supplied separator, are
22
- # html escaped unless they are html safe, and the returned string is marked
23
- # as html safe.
22
+ # This method returns an HTML safe string similar to what <tt>Array#join</tt>
23
+ # would return. The array is flattened, and all items, including
24
+ # the supplied separator, are HTML escaped unless they are HTML
25
+ # safe, and the returned string is marked as HTML safe.
24
26
  #
25
- # safe_join(["<p>foo</p>".html_safe, "<p>bar</p>"], "<br />")
27
+ # safe_join([raw("<p>foo</p>"), "<p>bar</p>"], "<br />")
26
28
  # # => "<p>foo</p>&lt;br /&gt;&lt;p&gt;bar&lt;/p&gt;"
27
29
  #
28
- # safe_join(["<p>foo</p>".html_safe, "<p>bar</p>".html_safe], "<br />".html_safe)
30
+ # safe_join([raw("<p>foo</p>"), raw("<p>bar</p>")], raw("<br />"))
29
31
  # # => "<p>foo</p><br /><p>bar</p>"
30
32
  #
31
- def safe_join(array, sep=$,)
32
- sep = ERB::Util.html_escape(sep)
33
+ def safe_join(array, sep = $,)
34
+ sep = ERB::Util.unwrapped_html_escape(sep)
35
+
36
+ array.flatten.map! { |i| ERB::Util.unwrapped_html_escape(i) }.join(sep).html_safe
37
+ end
38
+
39
+ # Converts the array to a comma-separated sentence where the last element is
40
+ # joined by the connector word. This is the html_safe-aware version of
41
+ # ActiveSupport's {Array#to_sentence}[https://api.rubyonrails.org/classes/Array.html#method-i-to_sentence].
42
+ #
43
+ def to_sentence(array, options = {})
44
+ options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
45
+
46
+ default_connectors = {
47
+ words_connector: ", ",
48
+ two_words_connector: " and ",
49
+ last_word_connector: ", and "
50
+ }
51
+ if defined?(I18n)
52
+ i18n_connectors = I18n.translate(:'support.array', locale: options[:locale], default: {})
53
+ default_connectors.merge!(i18n_connectors)
54
+ end
55
+ options = default_connectors.merge!(options)
33
56
 
34
- array.map { |i| ERB::Util.html_escape(i) }.join(sep).html_safe
57
+ case array.length
58
+ when 0
59
+ "".html_safe
60
+ when 1
61
+ ERB::Util.html_escape(array[0])
62
+ when 2
63
+ safe_join([array[0], array[1]], options[:two_words_connector])
64
+ else
65
+ safe_join([safe_join(array[0...-1], options[:words_connector]), options[:last_word_connector], array[-1]], nil)
66
+ end
35
67
  end
36
68
  end
37
69
  end