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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +362 -333
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -3
- data/lib/active_support/actionable_error.rb +48 -0
- data/lib/active_support/array_inquirer.rb +4 -2
- data/lib/active_support/backtrace_cleaner.rb +29 -3
- data/lib/active_support/benchmarkable.rb +1 -1
- data/lib/active_support/cache/file_store.rb +33 -33
- data/lib/active_support/cache/mem_cache_store.rb +31 -29
- data/lib/active_support/cache/memory_store.rb +59 -33
- data/lib/active_support/cache/null_store.rb +8 -3
- data/lib/active_support/cache/redis_cache_store.rb +84 -45
- data/lib/active_support/cache/strategy/local_cache.rb +41 -26
- data/lib/active_support/cache.rb +174 -113
- data/lib/active_support/callbacks.rb +81 -64
- data/lib/active_support/concern.rb +76 -5
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +18 -0
- data/lib/active_support/concurrency/share_lock.rb +0 -1
- data/lib/active_support/configurable.rb +10 -14
- data/lib/active_support/configuration_file.rb +46 -0
- data/lib/active_support/core_ext/array/access.rb +18 -6
- data/lib/active_support/core_ext/array/conversions.rb +5 -5
- data/lib/active_support/core_ext/array/extract.rb +21 -0
- data/lib/active_support/core_ext/array.rb +1 -1
- data/lib/active_support/core_ext/benchmark.rb +2 -2
- data/lib/active_support/core_ext/class/attribute.rb +32 -47
- data/lib/active_support/core_ext/class/subclasses.rb +17 -38
- data/lib/active_support/core_ext/date/calculations.rb +6 -5
- data/lib/active_support/core_ext/date/conversions.rb +2 -1
- data/lib/active_support/core_ext/date_and_time/calculations.rb +37 -47
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
- data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
- data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
- data/lib/active_support/core_ext/date_time/conversions.rb +0 -1
- data/lib/active_support/core_ext/digest.rb +3 -0
- data/lib/active_support/core_ext/enumerable.rb +171 -70
- data/lib/active_support/core_ext/file/atomic.rb +1 -1
- data/lib/active_support/core_ext/hash/conversions.rb +3 -3
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
- data/lib/active_support/core_ext/hash/except.rb +2 -2
- data/lib/active_support/core_ext/hash/keys.rb +1 -30
- data/lib/active_support/core_ext/hash/slice.rb +6 -27
- data/lib/active_support/core_ext/hash.rb +1 -2
- data/lib/active_support/core_ext/integer/multiple.rb +1 -1
- data/lib/active_support/core_ext/kernel.rb +0 -1
- data/lib/active_support/core_ext/load_error.rb +1 -1
- data/lib/active_support/core_ext/marshal.rb +2 -0
- data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
- data/lib/active_support/core_ext/module/attribute_accessors.rb +30 -39
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +17 -19
- data/lib/active_support/core_ext/module/concerning.rb +8 -2
- data/lib/active_support/core_ext/module/delegation.rb +76 -33
- data/lib/active_support/core_ext/module/introspection.rb +16 -15
- data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
- data/lib/active_support/core_ext/module.rb +0 -1
- data/lib/active_support/core_ext/name_error.rb +29 -2
- data/lib/active_support/core_ext/numeric/conversions.rb +129 -129
- data/lib/active_support/core_ext/numeric.rb +0 -1
- data/lib/active_support/core_ext/object/blank.rb +1 -2
- data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
- data/lib/active_support/core_ext/object/duplicable.rb +7 -114
- data/lib/active_support/core_ext/object/json.rb +7 -2
- data/lib/active_support/core_ext/object/to_query.rb +5 -2
- data/lib/active_support/core_ext/object/try.rb +17 -7
- data/lib/active_support/core_ext/object/with_options.rb +1 -1
- data/lib/active_support/core_ext/range/compare_range.rb +82 -0
- data/lib/active_support/core_ext/range/conversions.rb +31 -29
- data/lib/active_support/core_ext/range/each.rb +0 -1
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +8 -3
- data/lib/active_support/core_ext/range.rb +1 -1
- data/lib/active_support/core_ext/regexp.rb +8 -5
- data/lib/active_support/core_ext/securerandom.rb +23 -3
- data/lib/active_support/core_ext/string/access.rb +5 -16
- data/lib/active_support/core_ext/string/conversions.rb +1 -0
- data/lib/active_support/core_ext/string/filters.rb +42 -1
- data/lib/active_support/core_ext/string/inflections.rb +45 -6
- data/lib/active_support/core_ext/string/inquiry.rb +1 -0
- data/lib/active_support/core_ext/string/multibyte.rb +6 -5
- data/lib/active_support/core_ext/string/output_safety.rb +69 -12
- data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
- data/lib/active_support/core_ext/string/strip.rb +3 -1
- data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
- data/lib/active_support/core_ext/symbol.rb +3 -0
- data/lib/active_support/core_ext/time/calculations.rb +50 -3
- data/lib/active_support/core_ext/time/conversions.rb +1 -0
- data/lib/active_support/core_ext/uri.rb +7 -5
- data/lib/active_support/current_attributes/test_helper.rb +13 -0
- data/lib/active_support/current_attributes.rb +15 -2
- data/lib/active_support/dependencies/zeitwerk_integration.rb +117 -0
- data/lib/active_support/dependencies.rb +118 -35
- data/lib/active_support/deprecation/behaviors.rb +20 -3
- data/lib/active_support/deprecation/disallowed.rb +56 -0
- data/lib/active_support/deprecation/instance_delegator.rb +0 -1
- data/lib/active_support/deprecation/method_wrappers.rb +21 -13
- data/lib/active_support/deprecation/proxy_wrappers.rb +29 -6
- data/lib/active_support/deprecation/reporting.rb +51 -8
- data/lib/active_support/deprecation.rb +6 -1
- data/lib/active_support/descendants_tracker.rb +59 -9
- data/lib/active_support/duration/iso8601_parser.rb +2 -4
- data/lib/active_support/duration/iso8601_serializer.rb +18 -14
- data/lib/active_support/duration.rb +90 -38
- data/lib/active_support/encrypted_configuration.rb +1 -5
- data/lib/active_support/encrypted_file.rb +23 -5
- data/lib/active_support/environment_inquirer.rb +20 -0
- data/lib/active_support/evented_file_update_checker.rb +82 -117
- data/lib/active_support/execution_wrapper.rb +1 -0
- data/lib/active_support/file_update_checker.rb +0 -1
- data/lib/active_support/fork_tracker.rb +62 -0
- data/lib/active_support/gem_version.rb +2 -2
- data/lib/active_support/hash_with_indifferent_access.rb +78 -41
- data/lib/active_support/i18n.rb +1 -0
- data/lib/active_support/i18n_railtie.rb +16 -5
- data/lib/active_support/inflector/inflections.rb +2 -7
- data/lib/active_support/inflector/methods.rb +50 -57
- data/lib/active_support/inflector/transliterate.rb +47 -18
- data/lib/active_support/json/decoding.rb +25 -26
- data/lib/active_support/json/encoding.rb +11 -3
- data/lib/active_support/key_generator.rb +1 -33
- data/lib/active_support/lazy_load_hooks.rb +5 -2
- data/lib/active_support/locale/en.rb +33 -0
- data/lib/active_support/locale/en.yml +7 -3
- data/lib/active_support/log_subscriber.rb +39 -9
- data/lib/active_support/logger.rb +2 -17
- data/lib/active_support/logger_silence.rb +11 -19
- data/lib/active_support/logger_thread_safe_level.rb +52 -7
- data/lib/active_support/message_encryptor.rb +8 -13
- data/lib/active_support/message_verifier.rb +10 -10
- data/lib/active_support/messages/metadata.rb +11 -2
- data/lib/active_support/messages/rotation_configuration.rb +2 -1
- data/lib/active_support/messages/rotator.rb +10 -9
- data/lib/active_support/multibyte/chars.rb +10 -68
- data/lib/active_support/multibyte/unicode.rb +15 -327
- data/lib/active_support/notifications/fanout.rb +116 -16
- data/lib/active_support/notifications/instrumenter.rb +71 -9
- data/lib/active_support/notifications.rb +72 -8
- data/lib/active_support/number_helper/number_converter.rb +5 -6
- data/lib/active_support/number_helper/number_to_currency_converter.rb +4 -9
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -2
- data/lib/active_support/number_helper/number_to_human_converter.rb +4 -3
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +4 -3
- data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
- data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +8 -7
- data/lib/active_support/number_helper/rounding_helper.rb +12 -28
- data/lib/active_support/number_helper.rb +38 -12
- data/lib/active_support/option_merger.rb +22 -3
- data/lib/active_support/ordered_hash.rb +1 -1
- data/lib/active_support/ordered_options.rb +13 -3
- data/lib/active_support/parameter_filter.rb +133 -0
- data/lib/active_support/per_thread_registry.rb +1 -1
- data/lib/active_support/rails.rb +1 -10
- data/lib/active_support/railtie.rb +23 -1
- data/lib/active_support/reloader.rb +4 -5
- data/lib/active_support/secure_compare_rotator.rb +51 -0
- data/lib/active_support/security_utils.rb +19 -12
- data/lib/active_support/string_inquirer.rb +4 -3
- data/lib/active_support/subscriber.rb +72 -24
- data/lib/active_support/tagged_logging.rb +42 -8
- data/lib/active_support/test_case.rb +92 -1
- data/lib/active_support/testing/assertions.rb +30 -9
- data/lib/active_support/testing/deprecation.rb +0 -1
- data/lib/active_support/testing/file_fixtures.rb +2 -0
- data/lib/active_support/testing/isolation.rb +2 -2
- data/lib/active_support/testing/method_call_assertions.rb +28 -1
- data/lib/active_support/testing/parallelization/server.rb +78 -0
- data/lib/active_support/testing/parallelization/worker.rb +100 -0
- data/lib/active_support/testing/parallelization.rb +51 -0
- data/lib/active_support/testing/setup_and_teardown.rb +5 -9
- data/lib/active_support/testing/stream.rb +1 -2
- data/lib/active_support/testing/time_helpers.rb +47 -12
- data/lib/active_support/time_with_zone.rb +81 -47
- data/lib/active_support/values/time_zone.rb +34 -18
- data/lib/active_support/xml_mini/jdom.rb +2 -3
- data/lib/active_support/xml_mini/libxml.rb +2 -2
- data/lib/active_support/xml_mini/libxmlsax.rb +4 -4
- data/lib/active_support/xml_mini/nokogiri.rb +2 -2
- data/lib/active_support/xml_mini/nokogirisax.rb +3 -3
- data/lib/active_support/xml_mini/rexml.rb +10 -3
- data/lib/active_support/xml_mini.rb +2 -10
- data/lib/active_support.rb +14 -1
- metadata +57 -30
- data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -9
- data/lib/active_support/core_ext/hash/compact.rb +0 -29
- data/lib/active_support/core_ext/hash/transform_values.rb +0 -32
- data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
- data/lib/active_support/core_ext/module/reachable.rb +0 -11
- data/lib/active_support/core_ext/numeric/inquiry.rb +0 -28
- data/lib/active_support/core_ext/range/include_range.rb +0 -25
- 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
|
-
|
15
|
-
|
16
|
-
|
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(
|
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
|
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: '£', separator: ',', delimiter: '')
|
100
109
|
# # => "£1234567890,50"
|
101
110
|
# number_to_currency(1234567890.50, unit: '£', separator: ',', delimiter: '', format: '%n %u')
|
102
111
|
# # => "1234567890,50 £"
|
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)
|
132
|
-
# number_to_percentage('98')
|
133
|
-
# number_to_percentage(100, precision: 0)
|
134
|
-
# number_to_percentage(1000, delimiter: '.', separator: ',')
|
135
|
-
# number_to_percentage(302.24398923423, precision: 5)
|
136
|
-
# number_to_percentage(1000, locale: :fr)
|
137
|
-
# number_to_percentage(1000, precision: nil)
|
138
|
-
# number_to_percentage('98a')
|
139
|
-
# number_to_percentage(100, format: '%n %')
|
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.
|
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 (
|
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 (
|
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)
|
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
|
-
|
25
|
+
options = @options
|
22
26
|
end
|
23
27
|
|
24
|
-
|
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
|
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
|
-
#
|
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
|
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
|
43
|
+
object.instance_variable_set :@per_thread_registry_key, object.name.freeze
|
44
44
|
end
|
45
45
|
|
46
46
|
def instance
|
data/lib/active_support/rails.rb
CHANGED
@@ -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.
|
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
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
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
|
-
|
17
|
-
|
18
|
-
|
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
|
-
#
|
27
|
+
# Secure string comparison for strings of variable length.
|
23
28
|
#
|
24
|
-
#
|
25
|
-
# via
|
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
|
-
|
34
|
+
a.length == b.length && fixed_length_secure_compare(a, b)
|
28
35
|
end
|
29
36
|
module_function :secure_compare
|
30
37
|
end
|