actionview 4.1.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 (106) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +274 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +34 -0
  5. data/lib/action_view.rb +97 -0
  6. data/lib/action_view/base.rb +205 -0
  7. data/lib/action_view/buffers.rb +49 -0
  8. data/lib/action_view/context.rb +36 -0
  9. data/lib/action_view/dependency_tracker.rb +93 -0
  10. data/lib/action_view/digestor.rb +116 -0
  11. data/lib/action_view/flows.rb +76 -0
  12. data/lib/action_view/helpers.rb +64 -0
  13. data/lib/action_view/helpers/active_model_helper.rb +49 -0
  14. data/lib/action_view/helpers/asset_tag_helper.rb +322 -0
  15. data/lib/action_view/helpers/asset_url_helper.rb +355 -0
  16. data/lib/action_view/helpers/atom_feed_helper.rb +203 -0
  17. data/lib/action_view/helpers/cache_helper.rb +200 -0
  18. data/lib/action_view/helpers/capture_helper.rb +216 -0
  19. data/lib/action_view/helpers/controller_helper.rb +25 -0
  20. data/lib/action_view/helpers/csrf_helper.rb +30 -0
  21. data/lib/action_view/helpers/date_helper.rb +1075 -0
  22. data/lib/action_view/helpers/debug_helper.rb +39 -0
  23. data/lib/action_view/helpers/form_helper.rb +1876 -0
  24. data/lib/action_view/helpers/form_options_helper.rb +843 -0
  25. data/lib/action_view/helpers/form_tag_helper.rb +746 -0
  26. data/lib/action_view/helpers/javascript_helper.rb +75 -0
  27. data/lib/action_view/helpers/number_helper.rb +425 -0
  28. data/lib/action_view/helpers/output_safety_helper.rb +38 -0
  29. data/lib/action_view/helpers/record_tag_helper.rb +108 -0
  30. data/lib/action_view/helpers/rendering_helper.rb +90 -0
  31. data/lib/action_view/helpers/sanitize_helper.rb +256 -0
  32. data/lib/action_view/helpers/tag_helper.rb +176 -0
  33. data/lib/action_view/helpers/tags.rb +41 -0
  34. data/lib/action_view/helpers/tags/base.rb +148 -0
  35. data/lib/action_view/helpers/tags/check_box.rb +64 -0
  36. data/lib/action_view/helpers/tags/checkable.rb +16 -0
  37. data/lib/action_view/helpers/tags/collection_check_boxes.rb +44 -0
  38. data/lib/action_view/helpers/tags/collection_helpers.rb +85 -0
  39. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +36 -0
  40. data/lib/action_view/helpers/tags/collection_select.rb +28 -0
  41. data/lib/action_view/helpers/tags/color_field.rb +25 -0
  42. data/lib/action_view/helpers/tags/date_field.rb +13 -0
  43. data/lib/action_view/helpers/tags/date_select.rb +72 -0
  44. data/lib/action_view/helpers/tags/datetime_field.rb +22 -0
  45. data/lib/action_view/helpers/tags/datetime_local_field.rb +19 -0
  46. data/lib/action_view/helpers/tags/datetime_select.rb +8 -0
  47. data/lib/action_view/helpers/tags/email_field.rb +8 -0
  48. data/lib/action_view/helpers/tags/file_field.rb +8 -0
  49. data/lib/action_view/helpers/tags/grouped_collection_select.rb +29 -0
  50. data/lib/action_view/helpers/tags/hidden_field.rb +8 -0
  51. data/lib/action_view/helpers/tags/label.rb +65 -0
  52. data/lib/action_view/helpers/tags/month_field.rb +13 -0
  53. data/lib/action_view/helpers/tags/number_field.rb +18 -0
  54. data/lib/action_view/helpers/tags/password_field.rb +12 -0
  55. data/lib/action_view/helpers/tags/radio_button.rb +31 -0
  56. data/lib/action_view/helpers/tags/range_field.rb +8 -0
  57. data/lib/action_view/helpers/tags/search_field.rb +24 -0
  58. data/lib/action_view/helpers/tags/select.rb +41 -0
  59. data/lib/action_view/helpers/tags/tel_field.rb +8 -0
  60. data/lib/action_view/helpers/tags/text_area.rb +18 -0
  61. data/lib/action_view/helpers/tags/text_field.rb +29 -0
  62. data/lib/action_view/helpers/tags/time_field.rb +13 -0
  63. data/lib/action_view/helpers/tags/time_select.rb +8 -0
  64. data/lib/action_view/helpers/tags/time_zone_select.rb +20 -0
  65. data/lib/action_view/helpers/tags/url_field.rb +8 -0
  66. data/lib/action_view/helpers/tags/week_field.rb +13 -0
  67. data/lib/action_view/helpers/text_helper.rb +447 -0
  68. data/lib/action_view/helpers/translation_helper.rb +111 -0
  69. data/lib/action_view/helpers/url_helper.rb +625 -0
  70. data/lib/action_view/layouts.rb +426 -0
  71. data/lib/action_view/locale/en.yml +56 -0
  72. data/lib/action_view/log_subscriber.rb +44 -0
  73. data/lib/action_view/lookup_context.rb +249 -0
  74. data/lib/action_view/model_naming.rb +12 -0
  75. data/lib/action_view/path_set.rb +77 -0
  76. data/lib/action_view/railtie.rb +49 -0
  77. data/lib/action_view/record_identifier.rb +84 -0
  78. data/lib/action_view/renderer/abstract_renderer.rb +47 -0
  79. data/lib/action_view/renderer/partial_renderer.rb +492 -0
  80. data/lib/action_view/renderer/renderer.rb +50 -0
  81. data/lib/action_view/renderer/streaming_template_renderer.rb +103 -0
  82. data/lib/action_view/renderer/template_renderer.rb +96 -0
  83. data/lib/action_view/rendering.rb +145 -0
  84. data/lib/action_view/routing_url_for.rb +109 -0
  85. data/lib/action_view/tasks/dependencies.rake +17 -0
  86. data/lib/action_view/template.rb +340 -0
  87. data/lib/action_view/template/error.rb +141 -0
  88. data/lib/action_view/template/handlers.rb +53 -0
  89. data/lib/action_view/template/handlers/builder.rb +26 -0
  90. data/lib/action_view/template/handlers/erb.rb +145 -0
  91. data/lib/action_view/template/handlers/raw.rb +11 -0
  92. data/lib/action_view/template/resolver.rb +329 -0
  93. data/lib/action_view/template/text.rb +34 -0
  94. data/lib/action_view/template/types.rb +57 -0
  95. data/lib/action_view/test_case.rb +272 -0
  96. data/lib/action_view/testing/resolvers.rb +50 -0
  97. data/lib/action_view/vendor/html-scanner.rb +20 -0
  98. data/lib/action_view/vendor/html-scanner/html/document.rb +68 -0
  99. data/lib/action_view/vendor/html-scanner/html/node.rb +532 -0
  100. data/lib/action_view/vendor/html-scanner/html/sanitizer.rb +188 -0
  101. data/lib/action_view/vendor/html-scanner/html/selector.rb +830 -0
  102. data/lib/action_view/vendor/html-scanner/html/tokenizer.rb +107 -0
  103. data/lib/action_view/vendor/html-scanner/html/version.rb +11 -0
  104. data/lib/action_view/version.rb +11 -0
  105. data/lib/action_view/view_paths.rb +96 -0
  106. metadata +218 -0
@@ -0,0 +1,75 @@
1
+ require 'action_view/helpers/tag_helper'
2
+
3
+ module ActionView
4
+ module Helpers
5
+ module JavaScriptHelper
6
+ JS_ESCAPE_MAP = {
7
+ '\\' => '\\\\',
8
+ '</' => '<\/',
9
+ "\r\n" => '\n',
10
+ "\n" => '\n',
11
+ "\r" => '\n',
12
+ '"' => '\\"',
13
+ "'" => "\\'"
14
+ }
15
+
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;'
18
+
19
+ # Escapes carriage returns and single and double quotes for JavaScript segments.
20
+ #
21
+ # Also available through the alias j(). This is particularly helpful in JavaScript
22
+ # responses, like:
23
+ #
24
+ # $('some_element').replaceWith('<%=j render 'some/element_template' %>');
25
+ 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
29
+ else
30
+ ''
31
+ end
32
+ end
33
+
34
+ alias_method :j, :escape_javascript
35
+
36
+ # Returns a JavaScript tag with the +content+ inside. Example:
37
+ # javascript_tag "alert('All is good')"
38
+ #
39
+ # Returns:
40
+ # <script>
41
+ # //<![CDATA[
42
+ # alert('All is good')
43
+ # //]]>
44
+ # </script>
45
+ #
46
+ # +html_options+ may be a hash of attributes for the <tt>\<script></tt>
47
+ # tag.
48
+ #
49
+ # javascript_tag "alert('All is good')", defer: 'defer'
50
+ # # => <script defer="defer">alert('All is good')</script>
51
+ #
52
+ # Instead of passing the content as an argument, you can also use a block
53
+ # in which case, you pass your +html_options+ as the first parameter.
54
+ #
55
+ # <%= javascript_tag defer: 'defer' do -%>
56
+ # alert('All is good')
57
+ # <% end -%>
58
+ def javascript_tag(content_or_options_with_block = nil, html_options = {}, &block)
59
+ content =
60
+ if block_given?
61
+ html_options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
62
+ capture(&block)
63
+ else
64
+ content_or_options_with_block
65
+ end
66
+
67
+ content_tag(:script, javascript_cdata_section(content), html_options)
68
+ end
69
+
70
+ def javascript_cdata_section(content) #:nodoc:
71
+ "\n//#{cdata_section("\n#{content}\n//")}\n".html_safe
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,425 @@
1
+ # encoding: utf-8
2
+
3
+ require 'active_support/core_ext/hash/keys'
4
+ require 'active_support/core_ext/string/output_safety'
5
+ require 'active_support/number_helper'
6
+
7
+ module ActionView
8
+ # = Action View Number Helpers
9
+ module Helpers #:nodoc:
10
+
11
+ # Provides methods for converting numbers into formatted strings.
12
+ # Methods are provided for phone numbers, currency, percentage,
13
+ # precision, positional notation, file size and pretty printing.
14
+ #
15
+ # Most methods expect a +number+ argument, and will return it
16
+ # unchanged if can't be converted into a valid number.
17
+ module NumberHelper
18
+
19
+ # Raised when argument +number+ param given to the helpers is invalid and
20
+ # the option :raise is set to +true+.
21
+ class InvalidNumberError < StandardError
22
+ attr_accessor :number
23
+ def initialize(number)
24
+ @number = number
25
+ end
26
+ end
27
+
28
+ # Formats a +number+ into a US phone number (e.g., (555)
29
+ # 123-9876). You can customize the format in the +options+ hash.
30
+ #
31
+ # ==== Options
32
+ #
33
+ # * <tt>:area_code</tt> - Adds parentheses around the area code.
34
+ # * <tt>:delimiter</tt> - Specifies the delimiter to use
35
+ # (defaults to "-").
36
+ # * <tt>:extension</tt> - Specifies an extension to add to the
37
+ # end of the generated number.
38
+ # * <tt>:country_code</tt> - Sets the country code for the phone
39
+ # number.
40
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
41
+ # the argument is invalid.
42
+ #
43
+ # ==== Examples
44
+ #
45
+ # number_to_phone(5551234) # => 555-1234
46
+ # number_to_phone("5551234") # => 555-1234
47
+ # number_to_phone(1235551234) # => 123-555-1234
48
+ # number_to_phone(1235551234, area_code: true) # => (123) 555-1234
49
+ # number_to_phone(1235551234, delimiter: " ") # => 123 555 1234
50
+ # number_to_phone(1235551234, area_code: true, extension: 555) # => (123) 555-1234 x 555
51
+ # number_to_phone(1235551234, country_code: 1) # => +1-123-555-1234
52
+ # number_to_phone("123a456") # => 123a456
53
+ # number_to_phone("1234a567", raise: true) # => InvalidNumberError
54
+ #
55
+ # number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: ".")
56
+ # # => +1.123.555.1234 x 1343
57
+ def number_to_phone(number, options = {})
58
+ return unless number
59
+ options = options.symbolize_keys
60
+
61
+ parse_float(number, true) if options.delete(:raise)
62
+ ERB::Util.html_escape(ActiveSupport::NumberHelper.number_to_phone(number, options))
63
+ end
64
+
65
+ # Formats a +number+ into a currency string (e.g., $13.65). You
66
+ # can customize the format in the +options+ hash.
67
+ #
68
+ # ==== Options
69
+ #
70
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
71
+ # (defaults to current locale).
72
+ # * <tt>:precision</tt> - Sets the level of precision (defaults
73
+ # to 2).
74
+ # * <tt>:unit</tt> - Sets the denomination of the currency
75
+ # (defaults to "$").
76
+ # * <tt>:separator</tt> - Sets the separator between the units
77
+ # (defaults to ".").
78
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
79
+ # to ",").
80
+ # * <tt>:format</tt> - Sets the format for non-negative numbers
81
+ # (defaults to "%u%n"). Fields are <tt>%u</tt> for the
82
+ # currency, and <tt>%n</tt> for the number.
83
+ # * <tt>:negative_format</tt> - Sets the format for negative
84
+ # numbers (defaults to prepending an hyphen to the formatted
85
+ # number given by <tt>:format</tt>). Accepts the same fields
86
+ # than <tt>:format</tt>, except <tt>%n</tt> is here the
87
+ # absolute value of the number.
88
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
89
+ # the argument is invalid.
90
+ #
91
+ # ==== Examples
92
+ #
93
+ # number_to_currency(1234567890.50) # => $1,234,567,890.50
94
+ # number_to_currency(1234567890.506) # => $1,234,567,890.51
95
+ # number_to_currency(1234567890.506, precision: 3) # => $1,234,567,890.506
96
+ # number_to_currency(1234567890.506, locale: :fr) # => 1 234 567 890,51 €
97
+ # number_to_currency("123a456") # => $123a456
98
+ #
99
+ # number_to_currency("123a456", raise: true) # => InvalidNumberError
100
+ #
101
+ # number_to_currency(-1234567890.50, negative_format: "(%u%n)")
102
+ # # => ($1,234,567,890.50)
103
+ # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "")
104
+ # # => R$1234567890,50
105
+ # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "", format: "%n %u")
106
+ # # => 1234567890,50 R$
107
+ def number_to_currency(number, options = {})
108
+ delegate_number_helper_method(:number_to_currency, number, options)
109
+ end
110
+
111
+ # Formats a +number+ as a percentage string (e.g., 65%). You can
112
+ # customize the format in the +options+ hash.
113
+ #
114
+ # ==== Options
115
+ #
116
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
117
+ # (defaults to current locale).
118
+ # * <tt>:precision</tt> - Sets the precision of the number
119
+ # (defaults to 3).
120
+ # * <tt>:significant</tt> - If +true+, precision will be the #
121
+ # of significant_digits. If +false+, the # of fractional
122
+ # digits (defaults to +false+).
123
+ # * <tt>:separator</tt> - Sets the separator between the
124
+ # fractional and integer digits (defaults to ".").
125
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
126
+ # to "").
127
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
128
+ # insignificant zeros after the decimal separator (defaults to
129
+ # +false+).
130
+ # * <tt>:format</tt> - Specifies the format of the percentage
131
+ # string The number field is <tt>%n</tt> (defaults to "%n%").
132
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
133
+ # the argument is invalid.
134
+ #
135
+ # ==== Examples
136
+ #
137
+ # number_to_percentage(100) # => 100.000%
138
+ # number_to_percentage("98") # => 98.000%
139
+ # number_to_percentage(100, precision: 0) # => 100%
140
+ # number_to_percentage(1000, delimiter: '.', separator: ',') # => 1.000,000%
141
+ # number_to_percentage(302.24398923423, precision: 5) # => 302.24399%
142
+ # number_to_percentage(1000, locale: :fr) # => 1 000,000%
143
+ # number_to_percentage("98a") # => 98a%
144
+ # number_to_percentage(100, format: "%n %") # => 100 %
145
+ #
146
+ # number_to_percentage("98a", raise: true) # => InvalidNumberError
147
+ def number_to_percentage(number, options = {})
148
+ delegate_number_helper_method(:number_to_percentage, number, options)
149
+ end
150
+
151
+ # Formats a +number+ with grouped thousands using +delimiter+
152
+ # (e.g., 12,324). You can customize the format in the +options+
153
+ # hash.
154
+ #
155
+ # ==== Options
156
+ #
157
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
158
+ # (defaults to current locale).
159
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
160
+ # to ",").
161
+ # * <tt>:separator</tt> - Sets the separator between the
162
+ # fractional and integer digits (defaults to ".").
163
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
164
+ # the argument is invalid.
165
+ #
166
+ # ==== Examples
167
+ #
168
+ # number_with_delimiter(12345678) # => 12,345,678
169
+ # number_with_delimiter("123456") # => 123,456
170
+ # number_with_delimiter(12345678.05) # => 12,345,678.05
171
+ # number_with_delimiter(12345678, delimiter: ".") # => 12.345.678
172
+ # number_with_delimiter(12345678, delimiter: ",") # => 12,345,678
173
+ # number_with_delimiter(12345678.05, separator: " ") # => 12,345,678 05
174
+ # number_with_delimiter(12345678.05, locale: :fr) # => 12 345 678,05
175
+ # number_with_delimiter("112a") # => 112a
176
+ # number_with_delimiter(98765432.98, delimiter: " ", separator: ",")
177
+ # # => 98 765 432,98
178
+ #
179
+ # number_with_delimiter("112a", raise: true) # => raise InvalidNumberError
180
+ def number_with_delimiter(number, options = {})
181
+ delegate_number_helper_method(:number_to_delimited, number, options)
182
+ end
183
+
184
+ # Formats a +number+ with the specified level of
185
+ # <tt>:precision</tt> (e.g., 112.32 has a precision of 2 if
186
+ # +:significant+ is +false+, and 5 if +:significant+ is +true+).
187
+ # You can customize the format in the +options+ hash.
188
+ #
189
+ # ==== Options
190
+ #
191
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
192
+ # (defaults to current locale).
193
+ # * <tt>:precision</tt> - Sets the precision of the number
194
+ # (defaults to 3).
195
+ # * <tt>:significant</tt> - If +true+, precision will be the #
196
+ # of significant_digits. If +false+, the # of fractional
197
+ # digits (defaults to +false+).
198
+ # * <tt>:separator</tt> - Sets the separator between the
199
+ # fractional and integer digits (defaults to ".").
200
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
201
+ # to "").
202
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
203
+ # insignificant zeros after the decimal separator (defaults to
204
+ # +false+).
205
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
206
+ # the argument is invalid.
207
+ #
208
+ # ==== Examples
209
+ #
210
+ # number_with_precision(111.2345) # => 111.235
211
+ # number_with_precision(111.2345, precision: 2) # => 111.23
212
+ # number_with_precision(13, precision: 5) # => 13.00000
213
+ # number_with_precision(389.32314, precision: 0) # => 389
214
+ # number_with_precision(111.2345, significant: true) # => 111
215
+ # number_with_precision(111.2345, precision: 1, significant: true) # => 100
216
+ # number_with_precision(13, precision: 5, significant: true) # => 13.000
217
+ # number_with_precision(111.234, locale: :fr) # => 111,234
218
+ #
219
+ # number_with_precision(13, precision: 5, significant: true, strip_insignificant_zeros: true)
220
+ # # => 13
221
+ #
222
+ # number_with_precision(389.32314, precision: 4, significant: true) # => 389.3
223
+ # number_with_precision(1111.2345, precision: 2, separator: ',', delimiter: '.')
224
+ # # => 1.111,23
225
+ def number_with_precision(number, options = {})
226
+ delegate_number_helper_method(:number_to_rounded, number, options)
227
+ end
228
+
229
+ # Formats the bytes in +number+ into a more understandable
230
+ # representation (e.g., giving it 1500 yields 1.5 KB). This
231
+ # method is useful for reporting file sizes to users. You can
232
+ # customize the format in the +options+ hash.
233
+ #
234
+ # See <tt>number_to_human</tt> if you want to pretty-print a
235
+ # generic number.
236
+ #
237
+ # ==== Options
238
+ #
239
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
240
+ # (defaults to current locale).
241
+ # * <tt>:precision</tt> - Sets the precision of the number
242
+ # (defaults to 3).
243
+ # * <tt>:significant</tt> - If +true+, precision will be the #
244
+ # of significant_digits. If +false+, the # of fractional
245
+ # digits (defaults to +true+)
246
+ # * <tt>:separator</tt> - Sets the separator between the
247
+ # fractional and integer digits (defaults to ".").
248
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
249
+ # to "").
250
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
251
+ # insignificant zeros after the decimal separator (defaults to
252
+ # +true+)
253
+ # * <tt>:prefix</tt> - If +:si+ formats the number using the SI
254
+ # prefix (defaults to :binary)
255
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
256
+ # the argument is invalid.
257
+ #
258
+ # ==== Examples
259
+ #
260
+ # number_to_human_size(123) # => 123 Bytes
261
+ # number_to_human_size(1234) # => 1.21 KB
262
+ # number_to_human_size(12345) # => 12.1 KB
263
+ # number_to_human_size(1234567) # => 1.18 MB
264
+ # number_to_human_size(1234567890) # => 1.15 GB
265
+ # number_to_human_size(1234567890123) # => 1.12 TB
266
+ # number_to_human_size(1234567, precision: 2) # => 1.2 MB
267
+ # number_to_human_size(483989, precision: 2) # => 470 KB
268
+ # 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"
277
+ def number_to_human_size(number, options = {})
278
+ delegate_number_helper_method(:number_to_human_size, number, options)
279
+ end
280
+
281
+ # Pretty prints (formats and approximates) a number in a way it
282
+ # is more readable by humans (eg.: 1200000000 becomes "1.2
283
+ # Billion"). This is useful for numbers that can get very large
284
+ # (and too hard to read).
285
+ #
286
+ # See <tt>number_to_human_size</tt> if you want to print a file
287
+ # size.
288
+ #
289
+ # You can also define you own unit-quantifier names if you want
290
+ # to use other decimal units (eg.: 1500 becomes "1.5
291
+ # kilometers", 0.150 becomes "150 milliliters", etc). You may
292
+ # define a wide range of unit quantifiers, even fractional ones
293
+ # (centi, deci, mili, etc).
294
+ #
295
+ # ==== Options
296
+ #
297
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
298
+ # (defaults to current locale).
299
+ # * <tt>:precision</tt> - Sets the precision of the number
300
+ # (defaults to 3).
301
+ # * <tt>:significant</tt> - If +true+, precision will be the #
302
+ # of significant_digits. If +false+, the # of fractional
303
+ # digits (defaults to +true+)
304
+ # * <tt>:separator</tt> - Sets the separator between the
305
+ # fractional and integer digits (defaults to ".").
306
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
307
+ # to "").
308
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
309
+ # insignificant zeros after the decimal separator (defaults to
310
+ # +true+)
311
+ # * <tt>:units</tt> - A Hash of unit quantifier names. Or a
312
+ # string containing an i18n scope where to find this hash. It
313
+ # might have the following keys:
314
+ # * *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>
318
+ # * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>,
319
+ # *<tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>,
320
+ # *<tt>:pico</tt>, <tt>:femto</tt>
321
+ # * <tt>:format</tt> - Sets the format of the output string
322
+ # (defaults to "%n %u"). The field types are:
323
+ # * %u - The quantifier (ex.: 'thousand')
324
+ # * %n - The number
325
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
326
+ # the argument is invalid.
327
+ #
328
+ # ==== Examples
329
+ #
330
+ # number_to_human(123) # => "123"
331
+ # number_to_human(1234) # => "1.23 Thousand"
332
+ # number_to_human(12345) # => "12.3 Thousand"
333
+ # number_to_human(1234567) # => "1.23 Million"
334
+ # number_to_human(1234567890) # => "1.23 Billion"
335
+ # number_to_human(1234567890123) # => "1.23 Trillion"
336
+ # number_to_human(1234567890123456) # => "1.23 Quadrillion"
337
+ # number_to_human(1234567890123456789) # => "1230 Quadrillion"
338
+ # number_to_human(489939, precision: 2) # => "490 Thousand"
339
+ # number_to_human(489939, precision: 4) # => "489.9 Thousand"
340
+ # number_to_human(1234567, precision: 4,
341
+ # significant: false) # => "1.2346 Million"
342
+ # number_to_human(1234567, precision: 1,
343
+ # separator: ',',
344
+ # significant: false) # => "1,2 Million"
345
+ #
346
+ # Non-significant zeros after the decimal separator are stripped
347
+ # out by default (set <tt>:strip_insignificant_zeros</tt> to
348
+ # +false+ to change that):
349
+ # number_to_human(12345012345, significant_digits: 6) # => "12.345 Billion"
350
+ # number_to_human(500000000, precision: 5) # => "500 Million"
351
+ #
352
+ # ==== Custom Unit Quantifiers
353
+ #
354
+ # You can also use your own custom unit quantifiers:
355
+ # number_to_human(500000, units: {unit: "ml", thousand: "lt"}) # => "500 lt"
356
+ #
357
+ # If in your I18n locale you have:
358
+ # distance:
359
+ # centi:
360
+ # one: "centimeter"
361
+ # other: "centimeters"
362
+ # unit:
363
+ # one: "meter"
364
+ # other: "meters"
365
+ # thousand:
366
+ # one: "kilometer"
367
+ # other: "kilometers"
368
+ # billion: "gazillion-distance"
369
+ #
370
+ # Then you could do:
371
+ #
372
+ # number_to_human(543934, units: :distance) # => "544 kilometers"
373
+ # number_to_human(54393498, units: :distance) # => "54400 kilometers"
374
+ # number_to_human(54393498000, units: :distance) # => "54.4 gazillion-distance"
375
+ # number_to_human(343, units: :distance, precision: 1) # => "300 meters"
376
+ # number_to_human(1, units: :distance) # => "1 meter"
377
+ # number_to_human(0.34, units: :distance) # => "34 centimeters"
378
+ #
379
+ def number_to_human(number, options = {})
380
+ delegate_number_helper_method(:number_to_human, number, options)
381
+ end
382
+
383
+ private
384
+
385
+ def delegate_number_helper_method(method, number, options)
386
+ return unless number
387
+ options = escape_unsafe_delimiters_and_separators(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
393
+
394
+ def escape_unsafe_delimiters_and_separators(options)
395
+ options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator] && !options[:separator].html_safe?
396
+ options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter] && !options[:delimiter].html_safe?
397
+ options[:unit] = ERB::Util.html_escape(options[:unit]) if options[:unit] && !options[:unit].html_safe?
398
+ options
399
+ end
400
+
401
+ def wrap_with_output_safety_handling(number, raise_on_invalid, &block)
402
+ valid_float = valid_float?(number)
403
+ raise InvalidNumberError, number if raise_on_invalid && !valid_float
404
+
405
+ formatted_number = yield
406
+
407
+ if valid_float || number.html_safe?
408
+ formatted_number.html_safe
409
+ else
410
+ formatted_number
411
+ end
412
+ end
413
+
414
+ def valid_float?(number)
415
+ !parse_float(number, false).nil?
416
+ end
417
+
418
+ def parse_float(number, raise_error)
419
+ Float(number)
420
+ rescue ArgumentError, TypeError
421
+ raise InvalidNumberError, number if raise_error
422
+ end
423
+ end
424
+ end
425
+ end