activesupport 5.2.0 → 6.1.0

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

Potentially problematic release.


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

Files changed (190) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +362 -333
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -3
  5. data/lib/active_support/actionable_error.rb +48 -0
  6. data/lib/active_support/array_inquirer.rb +4 -2
  7. data/lib/active_support/backtrace_cleaner.rb +29 -3
  8. data/lib/active_support/benchmarkable.rb +1 -1
  9. data/lib/active_support/cache/file_store.rb +33 -33
  10. data/lib/active_support/cache/mem_cache_store.rb +31 -29
  11. data/lib/active_support/cache/memory_store.rb +59 -33
  12. data/lib/active_support/cache/null_store.rb +8 -3
  13. data/lib/active_support/cache/redis_cache_store.rb +84 -45
  14. data/lib/active_support/cache/strategy/local_cache.rb +41 -26
  15. data/lib/active_support/cache.rb +174 -113
  16. data/lib/active_support/callbacks.rb +81 -64
  17. data/lib/active_support/concern.rb +76 -5
  18. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +18 -0
  19. data/lib/active_support/concurrency/share_lock.rb +0 -1
  20. data/lib/active_support/configurable.rb +10 -14
  21. data/lib/active_support/configuration_file.rb +46 -0
  22. data/lib/active_support/core_ext/array/access.rb +18 -6
  23. data/lib/active_support/core_ext/array/conversions.rb +5 -5
  24. data/lib/active_support/core_ext/array/extract.rb +21 -0
  25. data/lib/active_support/core_ext/array.rb +1 -1
  26. data/lib/active_support/core_ext/benchmark.rb +2 -2
  27. data/lib/active_support/core_ext/class/attribute.rb +32 -47
  28. data/lib/active_support/core_ext/class/subclasses.rb +17 -38
  29. data/lib/active_support/core_ext/date/calculations.rb +6 -5
  30. data/lib/active_support/core_ext/date/conversions.rb +2 -1
  31. data/lib/active_support/core_ext/date_and_time/calculations.rb +37 -47
  32. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  33. data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
  34. data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
  35. data/lib/active_support/core_ext/date_time/conversions.rb +0 -1
  36. data/lib/active_support/core_ext/digest.rb +3 -0
  37. data/lib/active_support/core_ext/enumerable.rb +171 -70
  38. data/lib/active_support/core_ext/file/atomic.rb +1 -1
  39. data/lib/active_support/core_ext/hash/conversions.rb +3 -3
  40. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  41. data/lib/active_support/core_ext/hash/except.rb +2 -2
  42. data/lib/active_support/core_ext/hash/keys.rb +1 -30
  43. data/lib/active_support/core_ext/hash/slice.rb +6 -27
  44. data/lib/active_support/core_ext/hash.rb +1 -2
  45. data/lib/active_support/core_ext/integer/multiple.rb +1 -1
  46. data/lib/active_support/core_ext/kernel.rb +0 -1
  47. data/lib/active_support/core_ext/load_error.rb +1 -1
  48. data/lib/active_support/core_ext/marshal.rb +2 -0
  49. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  50. data/lib/active_support/core_ext/module/attribute_accessors.rb +30 -39
  51. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +17 -19
  52. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  53. data/lib/active_support/core_ext/module/delegation.rb +76 -33
  54. data/lib/active_support/core_ext/module/introspection.rb +16 -15
  55. data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
  56. data/lib/active_support/core_ext/module.rb +0 -1
  57. data/lib/active_support/core_ext/name_error.rb +29 -2
  58. data/lib/active_support/core_ext/numeric/conversions.rb +129 -129
  59. data/lib/active_support/core_ext/numeric.rb +0 -1
  60. data/lib/active_support/core_ext/object/blank.rb +1 -2
  61. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  62. data/lib/active_support/core_ext/object/duplicable.rb +7 -114
  63. data/lib/active_support/core_ext/object/json.rb +7 -2
  64. data/lib/active_support/core_ext/object/to_query.rb +5 -2
  65. data/lib/active_support/core_ext/object/try.rb +17 -7
  66. data/lib/active_support/core_ext/object/with_options.rb +1 -1
  67. data/lib/active_support/core_ext/range/compare_range.rb +82 -0
  68. data/lib/active_support/core_ext/range/conversions.rb +31 -29
  69. data/lib/active_support/core_ext/range/each.rb +0 -1
  70. data/lib/active_support/core_ext/range/include_time_with_zone.rb +8 -3
  71. data/lib/active_support/core_ext/range.rb +1 -1
  72. data/lib/active_support/core_ext/regexp.rb +8 -5
  73. data/lib/active_support/core_ext/securerandom.rb +23 -3
  74. data/lib/active_support/core_ext/string/access.rb +5 -16
  75. data/lib/active_support/core_ext/string/conversions.rb +1 -0
  76. data/lib/active_support/core_ext/string/filters.rb +42 -1
  77. data/lib/active_support/core_ext/string/inflections.rb +45 -6
  78. data/lib/active_support/core_ext/string/inquiry.rb +1 -0
  79. data/lib/active_support/core_ext/string/multibyte.rb +6 -5
  80. data/lib/active_support/core_ext/string/output_safety.rb +69 -12
  81. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  82. data/lib/active_support/core_ext/string/strip.rb +3 -1
  83. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  84. data/lib/active_support/core_ext/symbol.rb +3 -0
  85. data/lib/active_support/core_ext/time/calculations.rb +50 -3
  86. data/lib/active_support/core_ext/time/conversions.rb +1 -0
  87. data/lib/active_support/core_ext/uri.rb +7 -5
  88. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  89. data/lib/active_support/current_attributes.rb +15 -2
  90. data/lib/active_support/dependencies/zeitwerk_integration.rb +117 -0
  91. data/lib/active_support/dependencies.rb +118 -35
  92. data/lib/active_support/deprecation/behaviors.rb +20 -3
  93. data/lib/active_support/deprecation/disallowed.rb +56 -0
  94. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  95. data/lib/active_support/deprecation/method_wrappers.rb +21 -13
  96. data/lib/active_support/deprecation/proxy_wrappers.rb +29 -6
  97. data/lib/active_support/deprecation/reporting.rb +51 -8
  98. data/lib/active_support/deprecation.rb +6 -1
  99. data/lib/active_support/descendants_tracker.rb +59 -9
  100. data/lib/active_support/duration/iso8601_parser.rb +2 -4
  101. data/lib/active_support/duration/iso8601_serializer.rb +18 -14
  102. data/lib/active_support/duration.rb +90 -38
  103. data/lib/active_support/encrypted_configuration.rb +1 -5
  104. data/lib/active_support/encrypted_file.rb +23 -5
  105. data/lib/active_support/environment_inquirer.rb +20 -0
  106. data/lib/active_support/evented_file_update_checker.rb +82 -117
  107. data/lib/active_support/execution_wrapper.rb +1 -0
  108. data/lib/active_support/file_update_checker.rb +0 -1
  109. data/lib/active_support/fork_tracker.rb +62 -0
  110. data/lib/active_support/gem_version.rb +2 -2
  111. data/lib/active_support/hash_with_indifferent_access.rb +78 -41
  112. data/lib/active_support/i18n.rb +1 -0
  113. data/lib/active_support/i18n_railtie.rb +16 -5
  114. data/lib/active_support/inflector/inflections.rb +2 -7
  115. data/lib/active_support/inflector/methods.rb +50 -57
  116. data/lib/active_support/inflector/transliterate.rb +47 -18
  117. data/lib/active_support/json/decoding.rb +25 -26
  118. data/lib/active_support/json/encoding.rb +11 -3
  119. data/lib/active_support/key_generator.rb +1 -33
  120. data/lib/active_support/lazy_load_hooks.rb +5 -2
  121. data/lib/active_support/locale/en.rb +33 -0
  122. data/lib/active_support/locale/en.yml +7 -3
  123. data/lib/active_support/log_subscriber.rb +39 -9
  124. data/lib/active_support/logger.rb +2 -17
  125. data/lib/active_support/logger_silence.rb +11 -19
  126. data/lib/active_support/logger_thread_safe_level.rb +52 -7
  127. data/lib/active_support/message_encryptor.rb +8 -13
  128. data/lib/active_support/message_verifier.rb +10 -10
  129. data/lib/active_support/messages/metadata.rb +11 -2
  130. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  131. data/lib/active_support/messages/rotator.rb +10 -9
  132. data/lib/active_support/multibyte/chars.rb +10 -68
  133. data/lib/active_support/multibyte/unicode.rb +15 -327
  134. data/lib/active_support/notifications/fanout.rb +116 -16
  135. data/lib/active_support/notifications/instrumenter.rb +71 -9
  136. data/lib/active_support/notifications.rb +72 -8
  137. data/lib/active_support/number_helper/number_converter.rb +5 -6
  138. data/lib/active_support/number_helper/number_to_currency_converter.rb +4 -9
  139. data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -2
  140. data/lib/active_support/number_helper/number_to_human_converter.rb +4 -3
  141. data/lib/active_support/number_helper/number_to_human_size_converter.rb +4 -3
  142. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  143. data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
  144. data/lib/active_support/number_helper/number_to_rounded_converter.rb +8 -7
  145. data/lib/active_support/number_helper/rounding_helper.rb +12 -28
  146. data/lib/active_support/number_helper.rb +38 -12
  147. data/lib/active_support/option_merger.rb +22 -3
  148. data/lib/active_support/ordered_hash.rb +1 -1
  149. data/lib/active_support/ordered_options.rb +13 -3
  150. data/lib/active_support/parameter_filter.rb +133 -0
  151. data/lib/active_support/per_thread_registry.rb +1 -1
  152. data/lib/active_support/rails.rb +1 -10
  153. data/lib/active_support/railtie.rb +23 -1
  154. data/lib/active_support/reloader.rb +4 -5
  155. data/lib/active_support/secure_compare_rotator.rb +51 -0
  156. data/lib/active_support/security_utils.rb +19 -12
  157. data/lib/active_support/string_inquirer.rb +4 -3
  158. data/lib/active_support/subscriber.rb +72 -24
  159. data/lib/active_support/tagged_logging.rb +42 -8
  160. data/lib/active_support/test_case.rb +92 -1
  161. data/lib/active_support/testing/assertions.rb +30 -9
  162. data/lib/active_support/testing/deprecation.rb +0 -1
  163. data/lib/active_support/testing/file_fixtures.rb +2 -0
  164. data/lib/active_support/testing/isolation.rb +2 -2
  165. data/lib/active_support/testing/method_call_assertions.rb +28 -1
  166. data/lib/active_support/testing/parallelization/server.rb +78 -0
  167. data/lib/active_support/testing/parallelization/worker.rb +100 -0
  168. data/lib/active_support/testing/parallelization.rb +51 -0
  169. data/lib/active_support/testing/setup_and_teardown.rb +5 -9
  170. data/lib/active_support/testing/stream.rb +1 -2
  171. data/lib/active_support/testing/time_helpers.rb +47 -12
  172. data/lib/active_support/time_with_zone.rb +81 -47
  173. data/lib/active_support/values/time_zone.rb +34 -18
  174. data/lib/active_support/xml_mini/jdom.rb +2 -3
  175. data/lib/active_support/xml_mini/libxml.rb +2 -2
  176. data/lib/active_support/xml_mini/libxmlsax.rb +4 -4
  177. data/lib/active_support/xml_mini/nokogiri.rb +2 -2
  178. data/lib/active_support/xml_mini/nokogirisax.rb +3 -3
  179. data/lib/active_support/xml_mini/rexml.rb +10 -3
  180. data/lib/active_support/xml_mini.rb +2 -10
  181. data/lib/active_support.rb +14 -1
  182. metadata +57 -30
  183. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -9
  184. data/lib/active_support/core_ext/hash/compact.rb +0 -29
  185. data/lib/active_support/core_ext/hash/transform_values.rb +0 -32
  186. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
  187. data/lib/active_support/core_ext/module/reachable.rb +0 -11
  188. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -28
  189. data/lib/active_support/core_ext/range/include_range.rb +0 -25
  190. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -10,57 +10,41 @@ module ActiveSupport
10
10
  end
11
11
 
12
12
  def round(number)
13
+ precision = absolute_precision(number)
13
14
  return number unless precision
14
- number = convert_to_decimal(number)
15
- if significant && precision > 0
16
- round_significant(number)
17
- else
18
- round_without_significant(number)
19
- end
15
+
16
+ rounded_number = convert_to_decimal(number).round(precision, options.fetch(:round_mode, :default))
17
+ rounded_number.zero? ? rounded_number.abs : rounded_number # prevent showing negative zeros
20
18
  end
21
19
 
22
20
  def digit_count(number)
23
21
  return 1 if number.zero?
24
- (Math.log10(absolute_number(number)) + 1).floor
22
+ (Math.log10(number.abs) + 1).floor
25
23
  end
26
24
 
27
25
  private
28
- def round_without_significant(number)
29
- number = number.round(precision)
30
- number = number.to_i if precision == 0 && number.finite?
31
- number = number.abs if number.zero? # prevent showing negative zeros
32
- number
33
- end
34
-
35
- def round_significant(number)
36
- return 0 if number.zero?
37
- digits = digit_count(number)
38
- multiplier = 10**(digits - precision)
39
- (number / BigDecimal(multiplier.to_f.to_s)).round * multiplier
40
- end
41
-
42
26
  def convert_to_decimal(number)
43
27
  case number
44
28
  when Float, String
45
29
  BigDecimal(number.to_s)
46
30
  when Rational
47
- BigDecimal(number, digit_count(number.to_i) + precision)
31
+ BigDecimal(number, digit_count(number.to_i) + options[:precision])
48
32
  else
49
33
  number.to_d
50
34
  end
51
35
  end
52
36
 
53
- def precision
54
- options[:precision]
37
+ def absolute_precision(number)
38
+ if significant && options[:precision] > 0
39
+ options[:precision] - digit_count(convert_to_decimal(number))
40
+ else
41
+ options[:precision]
42
+ end
55
43
  end
56
44
 
57
45
  def significant
58
46
  options[:significant]
59
47
  end
60
-
61
- def absolute_number(number)
62
- number.respond_to?(:abs) ? number.abs : number.to_d.abs
63
- end
64
48
  end
65
49
  end
66
50
  end
@@ -71,6 +71,8 @@ module ActiveSupport
71
71
  # (defaults to current locale).
72
72
  # * <tt>:precision</tt> - Sets the level of precision (defaults
73
73
  # to 2).
74
+ # * <tt>:round_mode</tt> - Determine how rounding is performed
75
+ # (defaults to :default. See BigDecimal::mode)
74
76
  # * <tt>:unit</tt> - Sets the denomination of the currency
75
77
  # (defaults to "$").
76
78
  # * <tt>:separator</tt> - Sets the separator between the units
@@ -85,6 +87,9 @@ module ActiveSupport
85
87
  # number given by <tt>:format</tt>). Accepts the same fields
86
88
  # than <tt>:format</tt>, except <tt>%n</tt> is here the
87
89
  # absolute value of the number.
90
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
91
+ # insignificant zeros after the decimal separator (defaults to
92
+ # +false+).
88
93
  #
89
94
  # ==== Examples
90
95
  #
@@ -94,12 +99,20 @@ module ActiveSupport
94
99
  # number_to_currency(1234567890.506, locale: :fr) # => "1 234 567 890,51 €"
95
100
  # number_to_currency('123a456') # => "$123a456"
96
101
  #
102
+ # number_to_currency("123a456", raise: true) # => InvalidNumberError
103
+ #
104
+ # number_to_currency(-0.456789, precision: 0)
105
+ # # => "$0"
97
106
  # number_to_currency(-1234567890.50, negative_format: '(%u%n)')
98
107
  # # => "($1,234,567,890.50)"
99
108
  # number_to_currency(1234567890.50, unit: '&pound;', separator: ',', delimiter: '')
100
109
  # # => "&pound;1234567890,50"
101
110
  # number_to_currency(1234567890.50, unit: '&pound;', separator: ',', delimiter: '', format: '%n %u')
102
111
  # # => "1234567890,50 &pound;"
112
+ # number_to_currency(1234567890.50, strip_insignificant_zeros: true)
113
+ # # => "$1,234,567,890.5"
114
+ # number_to_currency(1234567890.50, precision: 0, round_mode: :up)
115
+ # # => "$1,234,567,891"
103
116
  def number_to_currency(number, options = {})
104
117
  NumberToCurrencyConverter.convert(number, options)
105
118
  end
@@ -113,6 +126,8 @@ module ActiveSupport
113
126
  # (defaults to current locale).
114
127
  # * <tt>:precision</tt> - Sets the precision of the number
115
128
  # (defaults to 3). Keeps the number's precision if +nil+.
129
+ # * <tt>:round_mode</tt> - Determine how rounding is performed
130
+ # (defaults to :default. See BigDecimal::mode)
116
131
  # * <tt>:significant</tt> - If +true+, precision will be the number
117
132
  # of significant_digits. If +false+, the number of fractional
118
133
  # digits (defaults to +false+).
@@ -128,15 +143,16 @@ module ActiveSupport
128
143
  #
129
144
  # ==== Examples
130
145
  #
131
- # number_to_percentage(100) # => "100.000%"
132
- # number_to_percentage('98') # => "98.000%"
133
- # number_to_percentage(100, precision: 0) # => "100%"
134
- # number_to_percentage(1000, delimiter: '.', separator: ',') # => "1.000,000%"
135
- # number_to_percentage(302.24398923423, precision: 5) # => "302.24399%"
136
- # number_to_percentage(1000, locale: :fr) # => "1000,000%"
137
- # number_to_percentage(1000, precision: nil) # => "1000%"
138
- # number_to_percentage('98a') # => "98a%"
139
- # number_to_percentage(100, format: '%n %') # => "100.000 %"
146
+ # number_to_percentage(100) # => "100.000%"
147
+ # number_to_percentage('98') # => "98.000%"
148
+ # number_to_percentage(100, precision: 0) # => "100%"
149
+ # number_to_percentage(1000, delimiter: '.', separator: ',') # => "1.000,000%"
150
+ # number_to_percentage(302.24398923423, precision: 5) # => "302.24399%"
151
+ # number_to_percentage(1000, locale: :fr) # => "1000,000%"
152
+ # number_to_percentage(1000, precision: nil) # => "1000%"
153
+ # number_to_percentage('98a') # => "98a%"
154
+ # number_to_percentage(100, format: '%n %') # => "100.000 %"
155
+ # number_to_percentage(302.24398923423, precision: 5, round_mode: :down) # => "302.24398%"
140
156
  def number_to_percentage(number, options = {})
141
157
  NumberToPercentageConverter.convert(number, options)
142
158
  end
@@ -187,6 +203,8 @@ module ActiveSupport
187
203
  # (defaults to current locale).
188
204
  # * <tt>:precision</tt> - Sets the precision of the number
189
205
  # (defaults to 3). Keeps the number's precision if +nil+.
206
+ # * <tt>:round_mode</tt> - Determine how rounding is performed
207
+ # (defaults to :default. See BigDecimal::mode)
190
208
  # * <tt>:significant</tt> - If +true+, precision will be the number
191
209
  # of significant_digits. If +false+, the number of fractional
192
210
  # digits (defaults to +false+).
@@ -208,6 +226,7 @@ module ActiveSupport
208
226
  # number_to_rounded(111.2345, precision: 1, significant: true) # => "100"
209
227
  # number_to_rounded(13, precision: 5, significant: true) # => "13.000"
210
228
  # number_to_rounded(13, precision: nil) # => "13"
229
+ # number_to_rounded(389.32314, precision: 0, round_mode: :up) # => "390"
211
230
  # number_to_rounded(111.234, locale: :fr) # => "111,234"
212
231
  #
213
232
  # number_to_rounded(13, precision: 5, significant: true, strip_insignificant_zeros: true)
@@ -221,7 +240,7 @@ module ActiveSupport
221
240
  end
222
241
 
223
242
  # Formats the bytes in +number+ into a more understandable
224
- # representation (e.g., giving it 1500 yields 1.5 KB). This
243
+ # representation (e.g., giving it 1500 yields 1.46 KB). This
225
244
  # method is useful for reporting file sizes to users. You can
226
245
  # customize the format in the +options+ hash.
227
246
  #
@@ -234,6 +253,8 @@ module ActiveSupport
234
253
  # (defaults to current locale).
235
254
  # * <tt>:precision</tt> - Sets the precision of the number
236
255
  # (defaults to 3).
256
+ # * <tt>:round_mode</tt> - Determine how rounding is performed
257
+ # (defaults to :default. See BigDecimal::mode)
237
258
  # * <tt>:significant</tt> - If +true+, precision will be the number
238
259
  # of significant_digits. If +false+, the number of fractional
239
260
  # digits (defaults to +true+)
@@ -257,6 +278,7 @@ module ActiveSupport
257
278
  # number_to_human_size(1234567890123456789) # => "1.07 EB"
258
279
  # number_to_human_size(1234567, precision: 2) # => "1.2 MB"
259
280
  # number_to_human_size(483989, precision: 2) # => "470 KB"
281
+ # number_to_human_size(483989, precision: 2, round_mode: :up) # => "480 KB"
260
282
  # number_to_human_size(1234567, precision: 2, separator: ',') # => "1,2 MB"
261
283
  # number_to_human_size(1234567890123, precision: 5) # => "1.1228 TB"
262
284
  # number_to_human_size(524288000, precision: 5) # => "500 MB"
@@ -265,7 +287,7 @@ module ActiveSupport
265
287
  end
266
288
 
267
289
  # Pretty prints (formats and approximates) a number in a way it
268
- # is more readable by humans (eg.: 1200000000 becomes "1.2
290
+ # is more readable by humans (e.g.: 1200000000 becomes "1.2
269
291
  # Billion"). This is useful for numbers that can get very large
270
292
  # (and too hard to read).
271
293
  #
@@ -273,7 +295,7 @@ module ActiveSupport
273
295
  # size.
274
296
  #
275
297
  # You can also define your own unit-quantifier names if you want
276
- # to use other decimal units (eg.: 1500 becomes "1.5
298
+ # to use other decimal units (e.g.: 1500 becomes "1.5
277
299
  # kilometers", 0.150 becomes "150 milliliters", etc). You may
278
300
  # define a wide range of unit quantifiers, even fractional ones
279
301
  # (centi, deci, mili, etc).
@@ -284,6 +306,8 @@ module ActiveSupport
284
306
  # (defaults to current locale).
285
307
  # * <tt>:precision</tt> - Sets the precision of the number
286
308
  # (defaults to 3).
309
+ # * <tt>:round_mode</tt> - Determine how rounding is performed
310
+ # (defaults to :default. See BigDecimal::mode)
287
311
  # * <tt>:significant</tt> - If +true+, precision will be the number
288
312
  # of significant_digits. If +false+, the number of fractional
289
313
  # digits (defaults to +true+)
@@ -321,6 +345,8 @@ module ActiveSupport
321
345
  # number_to_human(1234567890123456789) # => "1230 Quadrillion"
322
346
  # number_to_human(489939, precision: 2) # => "490 Thousand"
323
347
  # number_to_human(489939, precision: 4) # => "489.9 Thousand"
348
+ # number_to_human(489939, precision: 2
349
+ # , round_mode: :down) # => "480 Thousand"
324
350
  # number_to_human(1234567, precision: 4,
325
351
  # significant: false) # => "1.2346 Million"
326
352
  # number_to_human(1234567, precision: 1,
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/hash/deep_merge"
4
+ require "active_support/core_ext/symbol/starts_ends_with"
4
5
 
5
6
  module ActiveSupport
6
7
  class OptionMerger #:nodoc:
7
8
  instance_methods.each do |method|
8
- undef_method(method) if method !~ /^(__|instance_eval|class|object_id)/
9
+ undef_method(method) unless method.start_with?("__", "instance_eval", "class", "object_id")
9
10
  end
10
11
 
11
12
  def initialize(context, options)
@@ -14,14 +15,32 @@ module ActiveSupport
14
15
 
15
16
  private
16
17
  def method_missing(method, *arguments, &block)
18
+ options = nil
17
19
  if arguments.first.is_a?(Proc)
18
20
  proc = arguments.pop
19
21
  arguments << lambda { |*args| @options.deep_merge(proc.call(*args)) }
22
+ elsif arguments.last.respond_to?(:to_hash)
23
+ options = @options.deep_merge(arguments.pop)
20
24
  else
21
- arguments << (arguments.last.respond_to?(:to_hash) ? @options.deep_merge(arguments.pop) : @options.dup)
25
+ options = @options
22
26
  end
23
27
 
24
- @context.__send__(method, *arguments, &block)
28
+ invoke_method(method, arguments, options, &block)
29
+ end
30
+
31
+ if RUBY_VERSION >= "2.7"
32
+ def invoke_method(method, arguments, options, &block)
33
+ if options
34
+ @context.__send__(method, *arguments, **options, &block)
35
+ else
36
+ @context.__send__(method, *arguments, &block)
37
+ end
38
+ end
39
+ else
40
+ def invoke_method(method, arguments, options, &block)
41
+ arguments << options.dup if options
42
+ @context.__send__(method, *arguments, &block)
43
+ end
25
44
  end
26
45
  end
27
46
  end
@@ -16,7 +16,7 @@ module ActiveSupport
16
16
  # oh.keys # => [:a, :b], this order is guaranteed
17
17
  #
18
18
  # Also, maps the +omap+ feature for YAML files
19
- # (See http://yaml.org/type/omap.html) to support ordered items
19
+ # (See https://yaml.org/type/omap.html) to support ordered items
20
20
  # when loading from yaml.
21
21
  #
22
22
  # <tt>ActiveSupport::OrderedHash</tt> is namespaced to prevent conflicts
@@ -3,7 +3,9 @@
3
3
  require "active_support/core_ext/object/blank"
4
4
 
5
5
  module ActiveSupport
6
- # Usually key value pairs are handled something like this:
6
+ # +OrderedOptions+ inherits from +Hash+ and provides dynamic accessor methods.
7
+ #
8
+ # With a +Hash+, key-value pairs are typically managed like this:
7
9
  #
8
10
  # h = {}
9
11
  # h[:boy] = 'John'
@@ -12,7 +14,7 @@ module ActiveSupport
12
14
  # h[:girl] # => 'Mary'
13
15
  # h[:dog] # => nil
14
16
  #
15
- # Using +OrderedOptions+, the above code could be reduced to:
17
+ # Using +OrderedOptions+, the above code can be written as:
16
18
  #
17
19
  # h = ActiveSupport::OrderedOptions.new
18
20
  # h.boy = 'John'
@@ -39,7 +41,7 @@ module ActiveSupport
39
41
  end
40
42
 
41
43
  def method_missing(name, *args)
42
- name_string = name.to_s
44
+ name_string = +name.to_s
43
45
  if name_string.chomp!("=")
44
46
  self[name_string] = args.first
45
47
  else
@@ -56,6 +58,14 @@ module ActiveSupport
56
58
  def respond_to_missing?(name, include_private)
57
59
  true
58
60
  end
61
+
62
+ def extractable_options?
63
+ true
64
+ end
65
+
66
+ def inspect
67
+ "#<#{self.class.name} #{super}>"
68
+ end
59
69
  end
60
70
 
61
71
  # +InheritableOptions+ provides a constructor to build an +OrderedOptions+
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/object/duplicable"
4
+
5
+ module ActiveSupport
6
+ # +ParameterFilter+ allows you to specify keys for sensitive data from
7
+ # hash-like object and replace corresponding value. Filtering only certain
8
+ # sub-keys from a hash is possible by using the dot notation:
9
+ # 'credit_card.number'. If a proc is given, each key and value of a hash and
10
+ # all sub-hashes are passed to it, where the value or the key can be replaced
11
+ # using String#replace or similar methods.
12
+ #
13
+ # ActiveSupport::ParameterFilter.new([:password])
14
+ # => replaces the value to all keys matching /password/i with "[FILTERED]"
15
+ #
16
+ # ActiveSupport::ParameterFilter.new([:foo, "bar"])
17
+ # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
18
+ #
19
+ # ActiveSupport::ParameterFilter.new(["credit_card.code"])
20
+ # => replaces { credit_card: {code: "xxxx"} } with "[FILTERED]", does not
21
+ # change { file: { code: "xxxx"} }
22
+ #
23
+ # ActiveSupport::ParameterFilter.new([-> (k, v) do
24
+ # v.reverse! if /secret/i.match?(k)
25
+ # end])
26
+ # => reverses the value to all keys matching /secret/i
27
+ class ParameterFilter
28
+ FILTERED = "[FILTERED]" # :nodoc:
29
+
30
+ # Create instance with given filters. Supported type of filters are +String+, +Regexp+, and +Proc+.
31
+ # Other types of filters are treated as +String+ using +to_s+.
32
+ # For +Proc+ filters, key, value, and optional original hash is passed to block arguments.
33
+ #
34
+ # ==== Options
35
+ #
36
+ # * <tt>:mask</tt> - A replaced object when filtered. Defaults to +"[FILTERED]"+
37
+ def initialize(filters = [], mask: FILTERED)
38
+ @filters = filters
39
+ @mask = mask
40
+ end
41
+
42
+ # Mask value of +params+ if key matches one of filters.
43
+ def filter(params)
44
+ compiled_filter.call(params)
45
+ end
46
+
47
+ # Returns filtered value for given key. For +Proc+ filters, third block argument is not populated.
48
+ def filter_param(key, value)
49
+ @filters.empty? ? value : compiled_filter.value_for_key(key, value)
50
+ end
51
+
52
+ private
53
+ def compiled_filter
54
+ @compiled_filter ||= CompiledFilter.compile(@filters, mask: @mask)
55
+ end
56
+
57
+ class CompiledFilter # :nodoc:
58
+ def self.compile(filters, mask:)
59
+ return lambda { |params| params.dup } if filters.empty?
60
+
61
+ strings, regexps, blocks, deep_regexps, deep_strings = [], [], [], nil, nil
62
+
63
+ filters.each do |item|
64
+ case item
65
+ when Proc
66
+ blocks << item
67
+ when Regexp
68
+ if item.to_s.include?("\\.")
69
+ (deep_regexps ||= []) << item
70
+ else
71
+ regexps << item
72
+ end
73
+ else
74
+ s = Regexp.escape(item.to_s)
75
+ if s.include?("\\.")
76
+ (deep_strings ||= []) << s
77
+ else
78
+ strings << s
79
+ end
80
+ end
81
+ end
82
+
83
+ regexps << Regexp.new(strings.join("|"), true) unless strings.empty?
84
+ (deep_regexps ||= []) << Regexp.new(deep_strings.join("|"), true) if deep_strings&.any?
85
+
86
+ new regexps, deep_regexps, blocks, mask: mask
87
+ end
88
+
89
+ attr_reader :regexps, :deep_regexps, :blocks
90
+
91
+ def initialize(regexps, deep_regexps, blocks, mask:)
92
+ @regexps = regexps
93
+ @deep_regexps = deep_regexps&.any? ? deep_regexps : nil
94
+ @blocks = blocks
95
+ @mask = mask
96
+ end
97
+
98
+ def call(params, parents = [], original_params = params)
99
+ filtered_params = params.class.new
100
+
101
+ params.each do |key, value|
102
+ filtered_params[key] = value_for_key(key, value, parents, original_params)
103
+ end
104
+
105
+ filtered_params
106
+ end
107
+
108
+ def value_for_key(key, value, parents = [], original_params = nil)
109
+ parents.push(key) if deep_regexps
110
+ if regexps.any? { |r| r.match?(key.to_s) }
111
+ value = @mask
112
+ elsif deep_regexps && (joined = parents.join(".")) && deep_regexps.any? { |r| r.match?(joined) }
113
+ value = @mask
114
+ elsif value.is_a?(Hash)
115
+ value = call(value, parents, original_params)
116
+ elsif value.is_a?(Array)
117
+ # If we don't pop the current parent it will be duplicated as we
118
+ # process each array value.
119
+ parents.pop if deep_regexps
120
+ value = value.map { |v| value_for_key(key, v, parents, original_params) }
121
+ # Restore the parent stack after processing the array.
122
+ parents.push(key) if deep_regexps
123
+ elsif blocks.any?
124
+ key = key.dup if key.duplicable?
125
+ value = value.dup if value.duplicable?
126
+ blocks.each { |b| b.arity == 2 ? b.call(key, value) : b.call(key, value, original_params) }
127
+ end
128
+ parents.pop if deep_regexps
129
+ value
130
+ end
131
+ end
132
+ end
133
+ end
@@ -40,7 +40,7 @@ module ActiveSupport
40
40
  # If the class has an initializer, it must accept no arguments.
41
41
  module PerThreadRegistry
42
42
  def self.extended(object)
43
- object.instance_variable_set "@per_thread_registry_key", object.name.freeze
43
+ object.instance_variable_set :@per_thread_registry_key, object.name.freeze
44
44
  end
45
45
 
46
46
  def instance
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # This is private interface.
3
+ # This is a private interface.
4
4
  #
5
5
  # Rails components cherry pick from Active Support as needed, but there are a
6
6
  # few features that are used for sure in some way or another and it is not worth
@@ -13,9 +13,6 @@
13
13
  # Defines Object#blank? and Object#present?.
14
14
  require "active_support/core_ext/object/blank"
15
15
 
16
- # Rails own autoload, eager_load, etc.
17
- require "active_support/dependencies/autoload"
18
-
19
16
  # Support for ClassMethods and the included macro.
20
17
  require "active_support/concern"
21
18
 
@@ -27,9 +24,3 @@ require "active_support/core_ext/module/delegation"
27
24
 
28
25
  # Defines ActiveSupport::Deprecation.
29
26
  require "active_support/deprecation"
30
-
31
- # Defines Regexp#match?.
32
- #
33
- # This should be removed when Rails needs Ruby 2.4 or later, and the require
34
- # added where other Regexp extensions are being used (easy to grep).
35
- require "active_support/core_ext/regexp"
@@ -22,12 +22,25 @@ module ActiveSupport
22
22
  app.reloader.before_class_unload { ActiveSupport::CurrentAttributes.clear_all }
23
23
  app.executor.to_run { ActiveSupport::CurrentAttributes.reset_all }
24
24
  app.executor.to_complete { ActiveSupport::CurrentAttributes.reset_all }
25
+
26
+ ActiveSupport.on_load(:active_support_test_case) do
27
+ require "active_support/current_attributes/test_helper"
28
+ include ActiveSupport::CurrentAttributes::TestHelper
29
+ end
25
30
  end
26
31
 
27
32
  initializer "active_support.deprecation_behavior" do |app|
28
33
  if deprecation = app.config.active_support.deprecation
29
34
  ActiveSupport::Deprecation.behavior = deprecation
30
35
  end
36
+
37
+ if disallowed_deprecation = app.config.active_support.disallowed_deprecation
38
+ ActiveSupport::Deprecation.disallowed_behavior = disallowed_deprecation
39
+ end
40
+
41
+ if disallowed_warnings = app.config.active_support.disallowed_deprecation_warnings
42
+ ActiveSupport::Deprecation.disallowed_warnings = disallowed_warnings
43
+ end
31
44
  end
32
45
 
33
46
  # Sets the default value for Time.zone
@@ -65,15 +78,24 @@ module ActiveSupport
65
78
  initializer "active_support.set_configs" do |app|
66
79
  app.config.active_support.each do |k, v|
67
80
  k = "#{k}="
68
- ActiveSupport.send(k, v) if ActiveSupport.respond_to? k
81
+ ActiveSupport.public_send(k, v) if ActiveSupport.respond_to? k
69
82
  end
70
83
  end
71
84
 
72
85
  initializer "active_support.set_hash_digest_class" do |app|
73
86
  config.after_initialize do
74
87
  if app.config.active_support.use_sha1_digests
88
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
89
+ config.active_support.use_sha1_digests is deprecated and will
90
+ be removed from Rails 6.2. Use
91
+ config.active_support.hash_digest_class = ::Digest::SHA1 instead.
92
+ MSG
75
93
  ActiveSupport::Digest.hash_digest_class = ::Digest::SHA1
76
94
  end
95
+
96
+ if klass = app.config.active_support.hash_digest_class
97
+ ActiveSupport::Digest.hash_digest_class = klass
98
+ end
77
99
  end
78
100
  end
79
101
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/execution_wrapper"
4
+ require "active_support/executor"
4
5
 
5
6
  module ActiveSupport
6
7
  #--
@@ -49,11 +50,9 @@ module ActiveSupport
49
50
  def self.reload!
50
51
  executor.wrap do
51
52
  new.tap do |instance|
52
- begin
53
- instance.run!
54
- ensure
55
- instance.complete!
56
- end
53
+ instance.run!
54
+ ensure
55
+ instance.complete!
57
56
  end
58
57
  end
59
58
  prepare!
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/security_utils"
4
+ require "active_support/messages/rotator"
5
+
6
+ module ActiveSupport
7
+ # The ActiveSupport::SecureCompareRotator is a wrapper around +ActiveSupport::SecurityUtils.secure_compare+
8
+ # and allows you to rotate a previously defined value to a new one.
9
+ #
10
+ # It can be used as follow:
11
+ #
12
+ # rotator = ActiveSupport::SecureCompareRotator.new('new_production_value')
13
+ # rotator.rotate('previous_production_value')
14
+ # rotator.secure_compare!('previous_production_value')
15
+ #
16
+ # One real use case example would be to rotate a basic auth credentials:
17
+ #
18
+ # class MyController < ApplicationController
19
+ # def authenticate_request
20
+ # rotator = ActiveSupport::SecureComparerotator.new('new_password')
21
+ # rotator.rotate('old_password')
22
+ #
23
+ # authenticate_or_request_with_http_basic do |username, password|
24
+ # rotator.secure_compare!(password)
25
+ # rescue ActiveSupport::SecureCompareRotator::InvalidMatch
26
+ # false
27
+ # end
28
+ # end
29
+ # end
30
+ class SecureCompareRotator
31
+ include SecurityUtils
32
+ prepend Messages::Rotator
33
+
34
+ InvalidMatch = Class.new(StandardError)
35
+
36
+ def initialize(value, **_options)
37
+ @value = value
38
+ end
39
+
40
+ def secure_compare!(other_value, on_rotation: @on_rotation)
41
+ secure_compare(@value, other_value) ||
42
+ run_rotations(on_rotation) { |wrapper| wrapper.secure_compare!(other_value) } ||
43
+ raise(InvalidMatch)
44
+ end
45
+
46
+ private
47
+ def build_rotation(previous_value, _options)
48
+ self.class.new(previous_value)
49
+ end
50
+ end
51
+ end
@@ -1,30 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "digest/sha2"
4
-
5
3
  module ActiveSupport
6
4
  module SecurityUtils
7
5
  # Constant time string comparison, for fixed length strings.
8
6
  #
9
7
  # The values compared should be of fixed length, such as strings
10
8
  # that have already been processed by HMAC. Raises in case of length mismatch.
11
- def fixed_length_secure_compare(a, b)
12
- raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize
13
9
 
14
- l = a.unpack "C#{a.bytesize}"
10
+ if defined?(OpenSSL.fixed_length_secure_compare)
11
+ def fixed_length_secure_compare(a, b)
12
+ OpenSSL.fixed_length_secure_compare(a, b)
13
+ end
14
+ else
15
+ def fixed_length_secure_compare(a, b)
16
+ raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize
17
+
18
+ l = a.unpack "C#{a.bytesize}"
15
19
 
16
- res = 0
17
- b.each_byte { |byte| res |= byte ^ l.shift }
18
- res == 0
20
+ res = 0
21
+ b.each_byte { |byte| res |= byte ^ l.shift }
22
+ res == 0
23
+ end
19
24
  end
20
25
  module_function :fixed_length_secure_compare
21
26
 
22
- # Constant time string comparison, for variable length strings.
27
+ # Secure string comparison for strings of variable length.
23
28
  #
24
- # The values are first processed by SHA256, so that we don't leak length info
25
- # via timing attacks.
29
+ # While a timing attack would not be able to discern the content of
30
+ # a secret compared via secure_compare, it is possible to determine
31
+ # the secret length. This should be considered when using secure_compare
32
+ # to compare weak, short secrets to user input.
26
33
  def secure_compare(a, b)
27
- fixed_length_secure_compare(::Digest::SHA256.hexdigest(a), ::Digest::SHA256.hexdigest(b)) && a == b
34
+ a.length == b.length && fixed_length_secure_compare(a, b)
28
35
  end
29
36
  module_function :secure_compare
30
37
  end