activesupport 3.2.22.5 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +364 -135
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -4
  5. data/lib/active_support/backtrace_cleaner.rb +33 -25
  6. data/lib/active_support/basic_object.rb +7 -17
  7. data/lib/active_support/benchmarkable.rb +19 -15
  8. data/lib/active_support/buffered_logger.rb +9 -113
  9. data/lib/active_support/cache/file_store.rb +14 -14
  10. data/lib/active_support/cache/mem_cache_store.rb +24 -30
  11. data/lib/active_support/cache/memory_store.rb +2 -0
  12. data/lib/active_support/cache/strategy/local_cache.rb +48 -37
  13. data/lib/active_support/cache.rb +228 -183
  14. data/lib/active_support/callbacks.rb +201 -253
  15. data/lib/active_support/concern.rb +16 -23
  16. data/lib/active_support/concurrency/latch.rb +27 -0
  17. data/lib/active_support/configurable.rb +69 -12
  18. data/lib/active_support/core_ext/array/access.rb +17 -9
  19. data/lib/active_support/core_ext/array/conversions.rb +115 -55
  20. data/lib/active_support/core_ext/array/extract_options.rb +2 -2
  21. data/lib/active_support/core_ext/array/grouping.rb +21 -22
  22. data/lib/active_support/core_ext/array/uniq_by.rb +12 -9
  23. data/lib/active_support/core_ext/array/wrap.rb +13 -16
  24. data/lib/active_support/core_ext/array.rb +0 -1
  25. data/lib/active_support/core_ext/benchmark.rb +7 -0
  26. data/lib/active_support/core_ext/big_decimal/conversions.rb +8 -24
  27. data/lib/active_support/core_ext/class/attribute.rb +40 -32
  28. data/lib/active_support/core_ext/class/attribute_accessors.rb +14 -12
  29. data/lib/active_support/core_ext/class/delegating_attributes.rb +15 -19
  30. data/lib/active_support/core_ext/class/subclasses.rb +11 -5
  31. data/lib/active_support/core_ext/date/calculations.rb +44 -187
  32. data/lib/active_support/core_ext/date/conversions.rb +16 -38
  33. data/lib/active_support/core_ext/date/zones.rb +25 -2
  34. data/lib/active_support/core_ext/date.rb +5 -0
  35. data/lib/active_support/core_ext/date_and_time/calculations.rb +232 -0
  36. data/lib/active_support/core_ext/date_time/acts_like.rb +0 -1
  37. data/lib/active_support/core_ext/date_time/calculations.rb +77 -62
  38. data/lib/active_support/core_ext/date_time/conversions.rb +21 -33
  39. data/lib/active_support/core_ext/date_time/zones.rb +11 -8
  40. data/lib/active_support/core_ext/date_time.rb +4 -0
  41. data/lib/active_support/core_ext/enumerable.rb +26 -73
  42. data/lib/active_support/core_ext/file/atomic.rb +27 -11
  43. data/lib/active_support/core_ext/file.rb +0 -1
  44. data/lib/active_support/core_ext/hash/conversions.rb +145 -79
  45. data/lib/active_support/core_ext/hash/deep_merge.rb +14 -8
  46. data/lib/active_support/core_ext/hash/diff.rb +5 -4
  47. data/lib/active_support/core_ext/hash/except.rb +1 -9
  48. data/lib/active_support/core_ext/hash/indifferent_access.rb +4 -5
  49. data/lib/active_support/core_ext/hash/keys.rb +108 -24
  50. data/lib/active_support/core_ext/hash/reverse_merge.rb +2 -3
  51. data/lib/active_support/core_ext/hash/slice.rb +12 -12
  52. data/lib/active_support/core_ext/hash.rb +0 -1
  53. data/lib/active_support/core_ext/integer/inflections.rb +13 -1
  54. data/lib/active_support/core_ext/integer/time.rb +17 -12
  55. data/lib/active_support/core_ext/kernel/debugger.rb +2 -2
  56. data/lib/active_support/core_ext/kernel/reporting.rb +36 -22
  57. data/lib/active_support/core_ext/kernel/singleton_class.rb +0 -7
  58. data/lib/active_support/core_ext/load_error.rb +7 -5
  59. data/lib/active_support/core_ext/logger.rb +7 -23
  60. data/lib/active_support/core_ext/marshal.rb +21 -0
  61. data/lib/active_support/core_ext/module/aliasing.rb +8 -9
  62. data/lib/active_support/core_ext/module/anonymous.rb +2 -7
  63. data/lib/active_support/core_ext/module/attr_internal.rb +0 -1
  64. data/lib/active_support/core_ext/module/attribute_accessors.rb +12 -10
  65. data/lib/active_support/core_ext/module/delegation.rb +92 -49
  66. data/lib/active_support/core_ext/module/deprecation.rb +19 -3
  67. data/lib/active_support/core_ext/module/introspection.rb +17 -27
  68. data/lib/active_support/core_ext/module/qualified_const.rb +8 -20
  69. data/lib/active_support/core_ext/module/remove_method.rb +1 -5
  70. data/lib/active_support/core_ext/module.rb +1 -3
  71. data/lib/active_support/core_ext/numeric/conversions.rb +135 -0
  72. data/lib/active_support/core_ext/numeric/time.rb +6 -6
  73. data/lib/active_support/core_ext/numeric.rb +1 -0
  74. data/lib/active_support/core_ext/object/acts_like.rb +4 -4
  75. data/lib/active_support/core_ext/object/blank.rb +7 -23
  76. data/lib/active_support/core_ext/object/deep_dup.rb +46 -0
  77. data/lib/active_support/core_ext/object/duplicable.rb +1 -30
  78. data/lib/active_support/core_ext/object/inclusion.rb +8 -7
  79. data/lib/active_support/core_ext/object/instance_variables.rb +7 -12
  80. data/lib/active_support/core_ext/object/to_json.rb +8 -0
  81. data/lib/active_support/core_ext/object/to_param.rb +5 -2
  82. data/lib/active_support/core_ext/object/try.rb +46 -25
  83. data/lib/active_support/core_ext/object/with_options.rb +7 -8
  84. data/lib/active_support/core_ext/object.rb +1 -0
  85. data/lib/active_support/core_ext/proc.rb +3 -0
  86. data/lib/active_support/core_ext/range/conversions.rb +0 -2
  87. data/lib/active_support/core_ext/range/include_range.rb +3 -1
  88. data/lib/active_support/core_ext/range/overlaps.rb +1 -1
  89. data/lib/active_support/core_ext/range.rb +0 -2
  90. data/lib/active_support/core_ext/string/access.rb +95 -90
  91. data/lib/active_support/core_ext/string/conversions.rb +27 -38
  92. data/lib/active_support/core_ext/string/encoding.rb +6 -9
  93. data/lib/active_support/core_ext/string/filters.rb +24 -18
  94. data/lib/active_support/core_ext/string/indent.rb +43 -0
  95. data/lib/active_support/core_ext/string/inflections.rb +70 -60
  96. data/lib/active_support/core_ext/string/inquiry.rb +2 -2
  97. data/lib/active_support/core_ext/string/multibyte.rb +41 -64
  98. data/lib/active_support/core_ext/string/output_safety.rb +59 -51
  99. data/lib/active_support/core_ext/string/zones.rb +13 -0
  100. data/lib/active_support/core_ext/string.rb +2 -3
  101. data/lib/active_support/core_ext/struct.rb +6 -0
  102. data/lib/active_support/core_ext/thread.rb +74 -0
  103. data/lib/active_support/core_ext/time/calculations.rb +108 -184
  104. data/lib/active_support/core_ext/time/conversions.rb +27 -51
  105. data/lib/active_support/core_ext/time/marshal.rb +0 -27
  106. data/lib/active_support/core_ext/time/zones.rb +27 -17
  107. data/lib/active_support/core_ext/time.rb +5 -0
  108. data/lib/active_support/core_ext/uri.rb +13 -17
  109. data/lib/active_support/core_ext.rb +3 -2
  110. data/lib/active_support/dependencies/autoload.rb +47 -20
  111. data/lib/active_support/dependencies.rb +160 -141
  112. data/lib/active_support/deprecation/behaviors.rb +44 -30
  113. data/lib/active_support/deprecation/instance_delegator.rb +24 -0
  114. data/lib/active_support/deprecation/method_wrappers.rb +33 -18
  115. data/lib/active_support/deprecation/proxy_wrappers.rb +58 -13
  116. data/lib/active_support/deprecation/reporting.rb +40 -11
  117. data/lib/active_support/deprecation.rb +39 -14
  118. data/lib/active_support/descendants_tracker.rb +34 -19
  119. data/lib/active_support/duration.rb +6 -8
  120. data/lib/active_support/file_update_checker.rb +63 -47
  121. data/lib/active_support/gzip.rb +11 -5
  122. data/lib/active_support/hash_with_indifferent_access.rb +127 -42
  123. data/lib/active_support/i18n.rb +4 -0
  124. data/lib/active_support/i18n_railtie.rb +5 -22
  125. data/lib/active_support/inflections.rb +14 -12
  126. data/lib/active_support/inflector/inflections.rb +108 -71
  127. data/lib/active_support/inflector/methods.rb +181 -160
  128. data/lib/active_support/inflector/transliterate.rb +16 -17
  129. data/lib/active_support/json/decoding.rb +18 -17
  130. data/lib/active_support/json/encoding.rb +93 -39
  131. data/lib/active_support/json/variable.rb +10 -1
  132. data/lib/active_support/key_generator.rb +75 -0
  133. data/lib/active_support/lazy_load_hooks.rb +21 -19
  134. data/lib/active_support/locale/en.yml +100 -3
  135. data/lib/active_support/log_subscriber/test_helper.rb +18 -15
  136. data/lib/active_support/log_subscriber.rb +35 -52
  137. data/lib/active_support/logger.rb +57 -0
  138. data/lib/active_support/logger_silence.rb +24 -0
  139. data/lib/active_support/message_encryptor.rb +38 -35
  140. data/lib/active_support/message_verifier.rb +9 -15
  141. data/lib/active_support/multibyte/chars.rb +80 -333
  142. data/lib/active_support/multibyte/unicode.rb +74 -64
  143. data/lib/active_support/multibyte.rb +5 -28
  144. data/lib/active_support/notifications/fanout.rb +112 -18
  145. data/lib/active_support/notifications/instrumenter.rb +33 -14
  146. data/lib/active_support/notifications.rb +79 -26
  147. data/lib/active_support/number_helper.rb +637 -0
  148. data/lib/active_support/ordered_hash.rb +8 -190
  149. data/lib/active_support/ordered_options.rb +21 -23
  150. data/lib/active_support/per_thread_registry.rb +52 -0
  151. data/lib/active_support/proxy_object.rb +13 -0
  152. data/lib/active_support/rails.rb +27 -0
  153. data/lib/active_support/railtie.rb +12 -32
  154. data/lib/active_support/rescuable.rb +9 -4
  155. data/lib/active_support/string_inquirer.rb +13 -8
  156. data/lib/active_support/subscriber.rb +93 -0
  157. data/lib/active_support/tagged_logging.rb +51 -73
  158. data/lib/active_support/test_case.rb +46 -17
  159. data/lib/active_support/testing/assertions.rb +56 -26
  160. data/lib/active_support/testing/autorun.rb +5 -0
  161. data/lib/active_support/testing/constant_lookup.rb +54 -0
  162. data/lib/active_support/testing/declarative.rb +1 -1
  163. data/lib/active_support/testing/deprecation.rb +0 -19
  164. data/lib/active_support/testing/isolation.rb +29 -58
  165. data/lib/active_support/testing/pending.rb +5 -43
  166. data/lib/active_support/testing/setup_and_teardown.rb +6 -92
  167. data/lib/active_support/testing/tagged_logging.rb +25 -0
  168. data/lib/active_support/time.rb +6 -21
  169. data/lib/active_support/time_with_zone.rb +80 -43
  170. data/lib/active_support/values/time_zone.rb +81 -60
  171. data/lib/active_support/values/unicode_tables.dat +0 -0
  172. data/lib/active_support/version.rb +7 -6
  173. data/lib/active_support/xml_mini/jdom.rb +9 -11
  174. data/lib/active_support/xml_mini/libxml.rb +1 -2
  175. data/lib/active_support/xml_mini/libxmlsax.rb +2 -3
  176. data/lib/active_support/xml_mini/nokogiri.rb +1 -2
  177. data/lib/active_support/xml_mini/nokogirisax.rb +2 -3
  178. data/lib/active_support/xml_mini/rexml.rb +6 -8
  179. data/lib/active_support/xml_mini.rb +35 -17
  180. data/lib/active_support.rb +8 -21
  181. metadata +104 -72
  182. data/lib/active_support/base64.rb +0 -54
  183. data/lib/active_support/core_ext/array/random_access.rb +0 -30
  184. data/lib/active_support/core_ext/date/freeze.rb +0 -33
  185. data/lib/active_support/core_ext/exception.rb +0 -3
  186. data/lib/active_support/core_ext/file/path.rb +0 -5
  187. data/lib/active_support/core_ext/float/rounding.rb +0 -19
  188. data/lib/active_support/core_ext/float.rb +0 -1
  189. data/lib/active_support/core_ext/hash/deep_dup.rb +0 -18
  190. data/lib/active_support/core_ext/io.rb +0 -15
  191. data/lib/active_support/core_ext/module/method_names.rb +0 -14
  192. data/lib/active_support/core_ext/module/synchronization.rb +0 -45
  193. data/lib/active_support/core_ext/process/daemon.rb +0 -23
  194. data/lib/active_support/core_ext/process.rb +0 -1
  195. data/lib/active_support/core_ext/range/blockless_step.rb +0 -29
  196. data/lib/active_support/core_ext/range/cover.rb +0 -3
  197. data/lib/active_support/core_ext/rexml.rb +0 -46
  198. data/lib/active_support/core_ext/string/interpolation.rb +0 -2
  199. data/lib/active_support/core_ext/string/xchar.rb +0 -18
  200. data/lib/active_support/core_ext/time/publicize_conversion_methods.rb +0 -10
  201. data/lib/active_support/memoizable.rb +0 -116
  202. data/lib/active_support/multibyte/exceptions.rb +0 -8
  203. data/lib/active_support/multibyte/utils.rb +0 -60
  204. data/lib/active_support/ruby/shim.rb +0 -22
  205. data/lib/active_support/security_utils.rb +0 -27
  206. data/lib/active_support/testing/mochaing.rb +0 -7
  207. data/lib/active_support/testing/performance/jruby.rb +0 -115
  208. data/lib/active_support/testing/performance/rubinius.rb +0 -113
  209. data/lib/active_support/testing/performance/ruby/mri.rb +0 -57
  210. data/lib/active_support/testing/performance/ruby/yarv.rb +0 -57
  211. data/lib/active_support/testing/performance/ruby.rb +0 -152
  212. data/lib/active_support/testing/performance.rb +0 -317
  213. data/lib/active_support/time/autoload.rb +0 -5
  214. data/lib/active_support/whiny_nil.rb +0 -24
@@ -0,0 +1,637 @@
1
+ require 'active_support/core_ext/big_decimal/conversions'
2
+ require 'active_support/core_ext/object/blank'
3
+ require 'active_support/core_ext/hash/keys'
4
+ require 'active_support/i18n'
5
+
6
+ module ActiveSupport
7
+ module NumberHelper
8
+ extend self
9
+
10
+ DEFAULTS = {
11
+ # Used in number_to_delimited
12
+ # These are also the defaults for 'currency', 'percentage', 'precision', and 'human'
13
+ format: {
14
+ # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)
15
+ separator: ".",
16
+ # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three)
17
+ delimiter: ",",
18
+ # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
19
+ precision: 3,
20
+ # If set to true, precision will mean the number of significant digits instead
21
+ # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2)
22
+ significant: false,
23
+ # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2)
24
+ strip_insignificant_zeros: false
25
+ },
26
+
27
+ # Used in number_to_currency
28
+ currency: {
29
+ format: {
30
+ format: "%u%n",
31
+ negative_format: "-%u%n",
32
+ unit: "$",
33
+ # These five are to override number.format and are optional
34
+ separator: ".",
35
+ delimiter: ",",
36
+ precision: 2,
37
+ significant: false,
38
+ strip_insignificant_zeros: false
39
+ }
40
+ },
41
+
42
+ # Used in number_to_percentage
43
+ percentage: {
44
+ format: {
45
+ delimiter: "",
46
+ format: "%n%"
47
+ }
48
+ },
49
+
50
+ # Used in number_to_rounded
51
+ precision: {
52
+ format: {
53
+ delimiter: ""
54
+ }
55
+ },
56
+
57
+ # Used in number_to_human_size and number_to_human
58
+ human: {
59
+ format: {
60
+ # These five are to override number.format and are optional
61
+ delimiter: "",
62
+ precision: 3,
63
+ significant: true,
64
+ strip_insignificant_zeros: true
65
+ },
66
+ # Used in number_to_human_size
67
+ storage_units: {
68
+ # Storage units output formatting.
69
+ # %u is the storage unit, %n is the number (default: 2 MB)
70
+ format: "%n %u",
71
+ units: {
72
+ byte: "Bytes",
73
+ kb: "KB",
74
+ mb: "MB",
75
+ gb: "GB",
76
+ tb: "TB"
77
+ }
78
+ },
79
+ # Used in number_to_human
80
+ decimal_units: {
81
+ format: "%n %u",
82
+ # Decimal units output formatting
83
+ # By default we will only quantify some of the exponents
84
+ # but the commented ones might be defined or overridden
85
+ # by the user.
86
+ units: {
87
+ # femto: Quadrillionth
88
+ # pico: Trillionth
89
+ # nano: Billionth
90
+ # micro: Millionth
91
+ # mili: Thousandth
92
+ # centi: Hundredth
93
+ # deci: Tenth
94
+ unit: "",
95
+ # ten:
96
+ # one: Ten
97
+ # other: Tens
98
+ # hundred: Hundred
99
+ thousand: "Thousand",
100
+ million: "Million",
101
+ billion: "Billion",
102
+ trillion: "Trillion",
103
+ quadrillion: "Quadrillion"
104
+ }
105
+ }
106
+ }
107
+ }
108
+
109
+ DECIMAL_UNITS = { 0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion,
110
+ -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto }
111
+
112
+ STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb]
113
+
114
+ # Formats a +number+ into a US phone number (e.g., (555)
115
+ # 123-9876). You can customize the format in the +options+ hash.
116
+ #
117
+ # ==== Options
118
+ #
119
+ # * <tt>:area_code</tt> - Adds parentheses around the area code.
120
+ # * <tt>:delimiter</tt> - Specifies the delimiter to use
121
+ # (defaults to "-").
122
+ # * <tt>:extension</tt> - Specifies an extension to add to the
123
+ # end of the generated number.
124
+ # * <tt>:country_code</tt> - Sets the country code for the phone
125
+ # number.
126
+ # ==== Examples
127
+ #
128
+ # number_to_phone(5551234) # => 555-1234
129
+ # number_to_phone('5551234') # => 555-1234
130
+ # number_to_phone(1235551234) # => 123-555-1234
131
+ # number_to_phone(1235551234, area_code: true) # => (123) 555-1234
132
+ # number_to_phone(1235551234, delimiter: ' ') # => 123 555 1234
133
+ # number_to_phone(1235551234, area_code: true, extension: 555) # => (123) 555-1234 x 555
134
+ # number_to_phone(1235551234, country_code: 1) # => +1-123-555-1234
135
+ # number_to_phone('123a456') # => 123a456
136
+ #
137
+ # number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: '.')
138
+ # # => +1.123.555.1234 x 1343
139
+ def number_to_phone(number, options = {})
140
+ return unless number
141
+ options = options.symbolize_keys
142
+
143
+ number = number.to_s.strip
144
+ area_code = options[:area_code]
145
+ delimiter = options[:delimiter] || "-"
146
+ extension = options[:extension]
147
+ country_code = options[:country_code]
148
+
149
+ if area_code
150
+ number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3")
151
+ else
152
+ number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
153
+ number.slice!(0, 1) if number.start_with?(delimiter) && !delimiter.blank?
154
+ end
155
+
156
+ str = ''
157
+ str << "+#{country_code}#{delimiter}" unless country_code.blank?
158
+ str << number
159
+ str << " x #{extension}" unless extension.blank?
160
+ str
161
+ end
162
+
163
+ # Formats a +number+ into a currency string (e.g., $13.65). You
164
+ # can customize the format in the +options+ hash.
165
+ #
166
+ # ==== Options
167
+ #
168
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
169
+ # (defaults to current locale).
170
+ # * <tt>:precision</tt> - Sets the level of precision (defaults
171
+ # to 2).
172
+ # * <tt>:unit</tt> - Sets the denomination of the currency
173
+ # (defaults to "$").
174
+ # * <tt>:separator</tt> - Sets the separator between the units
175
+ # (defaults to ".").
176
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
177
+ # to ",").
178
+ # * <tt>:format</tt> - Sets the format for non-negative numbers
179
+ # (defaults to "%u%n"). Fields are <tt>%u</tt> for the
180
+ # currency, and <tt>%n</tt> for the number.
181
+ # * <tt>:negative_format</tt> - Sets the format for negative
182
+ # numbers (defaults to prepending an hyphen to the formatted
183
+ # number given by <tt>:format</tt>). Accepts the same fields
184
+ # than <tt>:format</tt>, except <tt>%n</tt> is here the
185
+ # absolute value of the number.
186
+ #
187
+ # ==== Examples
188
+ #
189
+ # number_to_currency(1234567890.50) # => $1,234,567,890.50
190
+ # number_to_currency(1234567890.506) # => $1,234,567,890.51
191
+ # number_to_currency(1234567890.506, precision: 3) # => $1,234,567,890.506
192
+ # number_to_currency(1234567890.506, locale: :fr) # => 1 234 567 890,51 €
193
+ # number_to_currency('123a456') # => $123a456
194
+ #
195
+ # number_to_currency(-1234567890.50, negative_format: '(%u%n)')
196
+ # # => ($1,234,567,890.50)
197
+ # number_to_currency(1234567890.50, unit: '&pound;', separator: ',', delimiter: '')
198
+ # # => &pound;1234567890,50
199
+ # number_to_currency(1234567890.50, unit: '&pound;', separator: ',', delimiter: '', format: '%n %u')
200
+ # # => 1234567890,50 &pound;
201
+ def number_to_currency(number, options = {})
202
+ return unless number
203
+ options = options.symbolize_keys
204
+
205
+ currency = i18n_format_options(options[:locale], :currency)
206
+ currency[:negative_format] ||= "-" + currency[:format] if currency[:format]
207
+
208
+ defaults = default_format_options(:currency).merge!(currency)
209
+ defaults[:negative_format] = "-" + options[:format] if options[:format]
210
+ options = defaults.merge!(options)
211
+
212
+ unit = options.delete(:unit)
213
+ format = options.delete(:format)
214
+
215
+ if number.to_f.phase != 0
216
+ format = options.delete(:negative_format)
217
+ number = number.respond_to?("abs") ? number.abs : number.sub(/^-/, '')
218
+ end
219
+
220
+ format.gsub('%n', self.number_to_rounded(number, options)).gsub('%u', unit)
221
+ end
222
+
223
+ # Formats a +number+ as a percentage string (e.g., 65%). You can
224
+ # customize the format in the +options+ hash.
225
+ #
226
+ # ==== Options
227
+ #
228
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
229
+ # (defaults to current locale).
230
+ # * <tt>:precision</tt> - Sets the precision of the number
231
+ # (defaults to 3).
232
+ # * <tt>:significant</tt> - If +true+, precision will be the #
233
+ # of significant_digits. If +false+, the # of fractional
234
+ # digits (defaults to +false+).
235
+ # * <tt>:separator</tt> - Sets the separator between the
236
+ # fractional and integer digits (defaults to ".").
237
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
238
+ # to "").
239
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
240
+ # insignificant zeros after the decimal separator (defaults to
241
+ # +false+).
242
+ # * <tt>:format</tt> - Specifies the format of the percentage
243
+ # string The number field is <tt>%n</tt> (defaults to "%n%").
244
+ #
245
+ # ==== Examples
246
+ #
247
+ # number_to_percentage(100) # => 100.000%
248
+ # number_to_percentage('98') # => 98.000%
249
+ # number_to_percentage(100, precision: 0) # => 100%
250
+ # number_to_percentage(1000, delimiter: '.', separator: ,') # => 1.000,000%
251
+ # number_to_percentage(302.24398923423, precision: 5) # => 302.24399%
252
+ # number_to_percentage(1000, locale: :fr) # => 1 000,000%
253
+ # number_to_percentage('98a') # => 98a%
254
+ # number_to_percentage(100, format: '%n %') # => 100 %
255
+ def number_to_percentage(number, options = {})
256
+ return unless number
257
+ options = options.symbolize_keys
258
+
259
+ defaults = format_options(options[:locale], :percentage)
260
+ options = defaults.merge!(options)
261
+
262
+ format = options[:format] || "%n%"
263
+ format.gsub('%n', self.number_to_rounded(number, options))
264
+ end
265
+
266
+ # Formats a +number+ with grouped thousands using +delimiter+
267
+ # (e.g., 12,324). You can customize the format in the +options+
268
+ # hash.
269
+ #
270
+ # ==== Options
271
+ #
272
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
273
+ # (defaults to current locale).
274
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
275
+ # to ",").
276
+ # * <tt>:separator</tt> - Sets the separator between the
277
+ # fractional and integer digits (defaults to ".").
278
+ #
279
+ # ==== Examples
280
+ #
281
+ # number_to_delimited(12345678) # => 12,345,678
282
+ # number_to_delimited('123456') # => 123,456
283
+ # number_to_delimited(12345678.05) # => 12,345,678.05
284
+ # number_to_delimited(12345678, delimiter: '.') # => 12.345.678
285
+ # number_to_delimited(12345678, delimiter: ',') # => 12,345,678
286
+ # number_to_delimited(12345678.05, separator: ' ') # => 12,345,678 05
287
+ # number_to_delimited(12345678.05, locale: :fr) # => 12 345 678,05
288
+ # number_to_delimited('112a') # => 112a
289
+ # number_to_delimited(98765432.98, delimiter: ' ', separator: ',')
290
+ # # => 98 765 432,98
291
+ def number_to_delimited(number, options = {})
292
+ options = options.symbolize_keys
293
+
294
+ return number unless valid_float?(number)
295
+
296
+ options = format_options(options[:locale]).merge!(options)
297
+
298
+ parts = number.to_s.split('.')
299
+ parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
300
+ parts.join(options[:separator])
301
+ end
302
+
303
+ # Formats a +number+ with the specified level of
304
+ # <tt>:precision</tt> (e.g., 112.32 has a precision of 2 if
305
+ # +:significant+ is +false+, and 5 if +:significant+ is +true+).
306
+ # You can customize the format in the +options+ hash.
307
+ #
308
+ # ==== Options
309
+ #
310
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
311
+ # (defaults to current locale).
312
+ # * <tt>:precision</tt> - Sets the precision of the number
313
+ # (defaults to 3).
314
+ # * <tt>:significant</tt> - If +true+, precision will be the #
315
+ # of significant_digits. If +false+, the # of fractional
316
+ # digits (defaults to +false+).
317
+ # * <tt>:separator</tt> - Sets the separator between the
318
+ # fractional and integer digits (defaults to ".").
319
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
320
+ # to "").
321
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
322
+ # insignificant zeros after the decimal separator (defaults to
323
+ # +false+).
324
+ #
325
+ # ==== Examples
326
+ #
327
+ # number_to_rounded(111.2345) # => 111.235
328
+ # number_to_rounded(111.2345, precision: 2) # => 111.23
329
+ # number_to_rounded(13, precision: 5) # => 13.00000
330
+ # number_to_rounded(389.32314, precision: 0) # => 389
331
+ # number_to_rounded(111.2345, significant: true) # => 111
332
+ # number_to_rounded(111.2345, precision: 1, significant: true) # => 100
333
+ # number_to_rounded(13, precision: 5, significant: true) # => 13.000
334
+ # number_to_rounded(111.234, locale: :fr) # => 111,234
335
+ #
336
+ # number_to_rounded(13, precision: 5, significant: true, strip_insignificant_zeros: true)
337
+ # # => 13
338
+ #
339
+ # number_to_rounded(389.32314, precision: 4, significant: true) # => 389.3
340
+ # number_to_rounded(1111.2345, precision: 2, separator: ',', delimiter: '.')
341
+ # # => 1.111,23
342
+ def number_to_rounded(number, options = {})
343
+ return number unless valid_float?(number)
344
+ number = Float(number)
345
+ options = options.symbolize_keys
346
+
347
+ defaults = format_options(options[:locale], :precision)
348
+ options = defaults.merge!(options)
349
+
350
+ precision = options.delete :precision
351
+ significant = options.delete :significant
352
+ strip_insignificant_zeros = options.delete :strip_insignificant_zeros
353
+
354
+ if significant && precision > 0
355
+ if number == 0
356
+ digits, rounded_number = 1, 0
357
+ else
358
+ digits = (Math.log10(number.abs) + 1).floor
359
+ multiplier = 10 ** (digits - precision)
360
+ rounded_number = (BigDecimal.new(number.to_s) / BigDecimal.new(multiplier.to_f.to_s)).round.to_f * multiplier
361
+ digits = (Math.log10(rounded_number.abs) + 1).floor # After rounding, the number of digits may have changed
362
+ end
363
+ precision -= digits
364
+ precision = 0 if precision < 0 # don't let it be negative
365
+ else
366
+ rounded_number = BigDecimal.new(number.to_s).round(precision).to_f
367
+ rounded_number = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros
368
+ end
369
+ formatted_number = self.number_to_delimited("%01.#{precision}f" % rounded_number, options)
370
+ if strip_insignificant_zeros
371
+ escaped_separator = Regexp.escape(options[:separator])
372
+ formatted_number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '')
373
+ else
374
+ formatted_number
375
+ end
376
+ end
377
+
378
+ # Formats the bytes in +number+ into a more understandable
379
+ # representation (e.g., giving it 1500 yields 1.5 KB). This
380
+ # method is useful for reporting file sizes to users. You can
381
+ # customize the format in the +options+ hash.
382
+ #
383
+ # See <tt>number_to_human</tt> if you want to pretty-print a
384
+ # generic number.
385
+ #
386
+ # ==== Options
387
+ #
388
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
389
+ # (defaults to current locale).
390
+ # * <tt>:precision</tt> - Sets the precision of the number
391
+ # (defaults to 3).
392
+ # * <tt>:significant</tt> - If +true+, precision will be the #
393
+ # of significant_digits. If +false+, the # of fractional
394
+ # digits (defaults to +true+)
395
+ # * <tt>:separator</tt> - Sets the separator between the
396
+ # fractional and integer digits (defaults to ".").
397
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
398
+ # to "").
399
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
400
+ # insignificant zeros after the decimal separator (defaults to
401
+ # +true+)
402
+ # * <tt>:prefix</tt> - If +:si+ formats the number using the SI
403
+ # prefix (defaults to :binary)
404
+ #
405
+ # ==== Examples
406
+ #
407
+ # number_to_human_size(123) # => 123 Bytes
408
+ # number_to_human_size(1234) # => 1.21 KB
409
+ # number_to_human_size(12345) # => 12.1 KB
410
+ # number_to_human_size(1234567) # => 1.18 MB
411
+ # number_to_human_size(1234567890) # => 1.15 GB
412
+ # number_to_human_size(1234567890123) # => 1.12 TB
413
+ # number_to_human_size(1234567, precision: 2) # => 1.2 MB
414
+ # number_to_human_size(483989, precision: 2) # => 470 KB
415
+ # number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB
416
+ #
417
+ # Non-significant zeros after the fractional separator are stripped out by
418
+ # default (set <tt>:strip_insignificant_zeros</tt> to +false+ to change that):
419
+ #
420
+ # number_to_human_size(1234567890123, precision: 5) # => "1.1229 TB"
421
+ # number_to_human_size(524288000, precision: 5) # => "500 MB"
422
+ def number_to_human_size(number, options = {})
423
+ options = options.symbolize_keys
424
+
425
+ return number unless valid_float?(number)
426
+ number = Float(number)
427
+
428
+ defaults = format_options(options[:locale], :human)
429
+ options = defaults.merge!(options)
430
+
431
+ #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
432
+ options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
433
+
434
+ storage_units_format = translate_number_value_with_default('human.storage_units.format', :locale => options[:locale], :raise => true)
435
+
436
+ base = options[:prefix] == :si ? 1000 : 1024
437
+
438
+ if number.to_i < base
439
+ unit = translate_number_value_with_default('human.storage_units.units.byte', :locale => options[:locale], :count => number.to_i, :raise => true)
440
+ storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit)
441
+ else
442
+ max_exp = STORAGE_UNITS.size - 1
443
+ exponent = (Math.log(number) / Math.log(base)).to_i # Convert to base
444
+ exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
445
+ number /= base ** exponent
446
+
447
+ unit_key = STORAGE_UNITS[exponent]
448
+ unit = translate_number_value_with_default("human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true)
449
+
450
+ formatted_number = self.number_to_rounded(number, options)
451
+ storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit)
452
+ end
453
+ end
454
+
455
+ # Pretty prints (formats and approximates) a number in a way it
456
+ # is more readable by humans (eg.: 1200000000 becomes "1.2
457
+ # Billion"). This is useful for numbers that can get very large
458
+ # (and too hard to read).
459
+ #
460
+ # See <tt>number_to_human_size</tt> if you want to print a file
461
+ # size.
462
+ #
463
+ # You can also define you own unit-quantifier names if you want
464
+ # to use other decimal units (eg.: 1500 becomes "1.5
465
+ # kilometers", 0.150 becomes "150 milliliters", etc). You may
466
+ # define a wide range of unit quantifiers, even fractional ones
467
+ # (centi, deci, mili, etc).
468
+ #
469
+ # ==== Options
470
+ #
471
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
472
+ # (defaults to current locale).
473
+ # * <tt>:precision</tt> - Sets the precision of the number
474
+ # (defaults to 3).
475
+ # * <tt>:significant</tt> - If +true+, precision will be the #
476
+ # of significant_digits. If +false+, the # of fractional
477
+ # digits (defaults to +true+)
478
+ # * <tt>:separator</tt> - Sets the separator between the
479
+ # fractional and integer digits (defaults to ".").
480
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
481
+ # to "").
482
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
483
+ # insignificant zeros after the decimal separator (defaults to
484
+ # +true+)
485
+ # * <tt>:units</tt> - A Hash of unit quantifier names. Or a
486
+ # string containing an i18n scope where to find this hash. It
487
+ # might have the following keys:
488
+ # * *integers*: <tt>:unit</tt>, <tt>:ten</tt>,
489
+ # *<tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>,
490
+ # *<tt>:billion</tt>, <tt>:trillion</tt>,
491
+ # *<tt>:quadrillion</tt>
492
+ # * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>,
493
+ # *<tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>,
494
+ # *<tt>:pico</tt>, <tt>:femto</tt>
495
+ # * <tt>:format</tt> - Sets the format of the output string
496
+ # (defaults to "%n %u"). The field types are:
497
+ # * %u - The quantifier (ex.: 'thousand')
498
+ # * %n - The number
499
+ #
500
+ # ==== Examples
501
+ #
502
+ # number_to_human(123) # => "123"
503
+ # number_to_human(1234) # => "1.23 Thousand"
504
+ # number_to_human(12345) # => "12.3 Thousand"
505
+ # number_to_human(1234567) # => "1.23 Million"
506
+ # number_to_human(1234567890) # => "1.23 Billion"
507
+ # number_to_human(1234567890123) # => "1.23 Trillion"
508
+ # number_to_human(1234567890123456) # => "1.23 Quadrillion"
509
+ # number_to_human(1234567890123456789) # => "1230 Quadrillion"
510
+ # number_to_human(489939, precision: 2) # => "490 Thousand"
511
+ # number_to_human(489939, precision: 4) # => "489.9 Thousand"
512
+ # number_to_human(1234567, precision: 4,
513
+ # significant: false) # => "1.2346 Million"
514
+ # number_to_human(1234567, precision: 1,
515
+ # separator: ',',
516
+ # significant: false) # => "1,2 Million"
517
+ #
518
+ # Non-significant zeros after the decimal separator are stripped
519
+ # out by default (set <tt>:strip_insignificant_zeros</tt> to
520
+ # +false+ to change that):
521
+ #
522
+ # number_to_human(12345012345, significant_digits: 6) # => "12.345 Billion"
523
+ # number_to_human(500000000, precision: 5) # => "500 Million"
524
+ #
525
+ # ==== Custom Unit Quantifiers
526
+ #
527
+ # You can also use your own custom unit quantifiers:
528
+ # number_to_human(500000, units: { unit: 'ml', thousand: 'lt' }) # => "500 lt"
529
+ #
530
+ # If in your I18n locale you have:
531
+ #
532
+ # distance:
533
+ # centi:
534
+ # one: "centimeter"
535
+ # other: "centimeters"
536
+ # unit:
537
+ # one: "meter"
538
+ # other: "meters"
539
+ # thousand:
540
+ # one: "kilometer"
541
+ # other: "kilometers"
542
+ # billion: "gazillion-distance"
543
+ #
544
+ # Then you could do:
545
+ #
546
+ # number_to_human(543934, units: :distance) # => "544 kilometers"
547
+ # number_to_human(54393498, units: :distance) # => "54400 kilometers"
548
+ # number_to_human(54393498000, units: :distance) # => "54.4 gazillion-distance"
549
+ # number_to_human(343, units: :distance, precision: 1) # => "300 meters"
550
+ # number_to_human(1, units: :distance) # => "1 meter"
551
+ # number_to_human(0.34, units: :distance) # => "34 centimeters"
552
+ def number_to_human(number, options = {})
553
+ options = options.symbolize_keys
554
+
555
+ return number unless valid_float?(number)
556
+ number = Float(number)
557
+
558
+ defaults = format_options(options[:locale], :human)
559
+ options = defaults.merge!(options)
560
+
561
+ #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
562
+ options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
563
+
564
+ inverted_du = DECIMAL_UNITS.invert
565
+
566
+ units = options.delete :units
567
+ unit_exponents = case units
568
+ when Hash
569
+ units
570
+ when String, Symbol
571
+ I18n.translate(:"#{units}", :locale => options[:locale], :raise => true)
572
+ when nil
573
+ translate_number_value_with_default("human.decimal_units.units", :locale => options[:locale], :raise => true)
574
+ else
575
+ raise ArgumentError, ":units must be a Hash or String translation scope."
576
+ end.keys.map{|e_name| inverted_du[e_name] }.sort_by{|e| -e}
577
+
578
+ number_exponent = number != 0 ? Math.log10(number.abs).floor : 0
579
+ display_exponent = unit_exponents.find{ |e| number_exponent >= e } || 0
580
+ number /= 10 ** display_exponent
581
+
582
+ unit = case units
583
+ when Hash
584
+ units[DECIMAL_UNITS[display_exponent]] || ''
585
+ when String, Symbol
586
+ I18n.translate(:"#{units}.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
587
+ else
588
+ translate_number_value_with_default("human.decimal_units.units.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
589
+ end
590
+
591
+ decimal_format = options[:format] || translate_number_value_with_default('human.decimal_units.format', :locale => options[:locale])
592
+ formatted_number = self.number_to_rounded(number, options)
593
+ decimal_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).strip
594
+ end
595
+
596
+ def self.private_module_and_instance_method(method_name) #:nodoc:
597
+ private method_name
598
+ private_class_method method_name
599
+ end
600
+ private_class_method :private_module_and_instance_method
601
+
602
+ def format_options(locale, namespace = nil) #:nodoc:
603
+ default_format_options(namespace).merge!(i18n_format_options(locale, namespace))
604
+ end
605
+ private_module_and_instance_method :format_options
606
+
607
+ def default_format_options(namespace = nil) #:nodoc:
608
+ options = DEFAULTS[:format].dup
609
+ options.merge!(DEFAULTS[namespace][:format]) if namespace
610
+ options
611
+ end
612
+ private_module_and_instance_method :default_format_options
613
+
614
+ def i18n_format_options(locale, namespace = nil) #:nodoc:
615
+ options = I18n.translate(:'number.format', locale: locale, default: {}).dup
616
+ if namespace
617
+ options.merge!(I18n.translate(:"number.#{namespace}.format", locale: locale, default: {}))
618
+ end
619
+ options
620
+ end
621
+ private_module_and_instance_method :i18n_format_options
622
+
623
+ def translate_number_value_with_default(key, i18n_options = {}) #:nodoc:
624
+ default = key.split('.').reduce(DEFAULTS) { |defaults, k| defaults[k.to_sym] }
625
+
626
+ I18n.translate(key, { default: default, scope: :number }.merge!(i18n_options))
627
+ end
628
+ private_module_and_instance_method :translate_number_value_with_default
629
+
630
+ def valid_float?(number) #:nodoc:
631
+ Float(number)
632
+ rescue ArgumentError, TypeError
633
+ false
634
+ end
635
+ private_module_and_instance_method :valid_float?
636
+ end
637
+ end