activesupport 6.0.4.4 → 7.0.4.1
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.
Potentially problematic release.
This version of activesupport might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +257 -532
- data/MIT-LICENSE +1 -1
- data/lib/active_support/actionable_error.rb +1 -1
- data/lib/active_support/array_inquirer.rb +2 -2
- data/lib/active_support/backtrace_cleaner.rb +5 -5
- data/lib/active_support/benchmarkable.rb +3 -3
- data/lib/active_support/cache/file_store.rb +16 -10
- data/lib/active_support/cache/mem_cache_store.rb +163 -42
- data/lib/active_support/cache/memory_store.rb +57 -29
- data/lib/active_support/cache/null_store.rb +10 -2
- data/lib/active_support/cache/redis_cache_store.rb +79 -98
- data/lib/active_support/cache/strategy/local_cache.rb +49 -57
- data/lib/active_support/cache.rb +378 -179
- data/lib/active_support/callbacks.rb +230 -122
- data/lib/active_support/code_generator.rb +65 -0
- data/lib/active_support/concern.rb +49 -5
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +2 -4
- data/lib/active_support/concurrency/share_lock.rb +2 -2
- data/lib/active_support/configurable.rb +9 -6
- data/lib/active_support/configuration_file.rb +51 -0
- data/lib/active_support/core_ext/array/access.rb +1 -5
- data/lib/active_support/core_ext/array/conversions.rb +13 -12
- data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
- data/lib/active_support/core_ext/array/grouping.rb +6 -6
- data/lib/active_support/core_ext/array/inquiry.rb +2 -2
- data/lib/active_support/core_ext/array.rb +1 -0
- data/lib/active_support/core_ext/benchmark.rb +2 -2
- data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
- data/lib/active_support/core_ext/class/attribute.rb +34 -44
- data/lib/active_support/core_ext/class/subclasses.rb +9 -22
- data/lib/active_support/core_ext/date/blank.rb +1 -1
- data/lib/active_support/core_ext/date/calculations.rb +9 -9
- data/lib/active_support/core_ext/date/conversions.rb +16 -15
- data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
- data/lib/active_support/core_ext/date.rb +1 -0
- data/lib/active_support/core_ext/date_and_time/calculations.rb +17 -4
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
- data/lib/active_support/core_ext/date_time/blank.rb +1 -1
- data/lib/active_support/core_ext/date_time/conversions.rb +13 -13
- data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
- data/lib/active_support/core_ext/date_time.rb +1 -0
- data/lib/active_support/core_ext/digest/uuid.rb +39 -13
- data/lib/active_support/core_ext/enumerable.rb +164 -23
- data/lib/active_support/core_ext/file/atomic.rb +3 -1
- data/lib/active_support/core_ext/hash/conversions.rb +2 -3
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +1 -1
- data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
- data/lib/active_support/core_ext/hash/keys.rb +2 -2
- data/lib/active_support/core_ext/hash/slice.rb +3 -2
- data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
- data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
- data/lib/active_support/core_ext/load_error.rb +1 -1
- data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
- data/lib/active_support/core_ext/module/attribute_accessors.rb +25 -29
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +26 -13
- data/lib/active_support/core_ext/module/concerning.rb +8 -2
- data/lib/active_support/core_ext/module/delegation.rb +40 -36
- data/lib/active_support/core_ext/module/introspection.rb +1 -25
- data/lib/active_support/core_ext/name_error.rb +23 -2
- data/lib/active_support/core_ext/numeric/conversions.rb +80 -73
- data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
- data/lib/active_support/core_ext/numeric.rb +1 -0
- data/lib/active_support/core_ext/object/acts_like.rb +29 -5
- data/lib/active_support/core_ext/object/blank.rb +2 -2
- data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
- data/lib/active_support/core_ext/object/duplicable.rb +11 -0
- data/lib/active_support/core_ext/object/json.rb +42 -26
- data/lib/active_support/core_ext/object/to_query.rb +2 -2
- data/lib/active_support/core_ext/object/try.rb +20 -20
- data/lib/active_support/core_ext/object/with_options.rb +20 -1
- data/lib/active_support/core_ext/pathname/existence.rb +21 -0
- data/lib/active_support/core_ext/pathname.rb +3 -0
- data/lib/active_support/core_ext/range/compare_range.rb +6 -25
- data/lib/active_support/core_ext/range/conversions.rb +8 -8
- data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
- data/lib/active_support/core_ext/range/each.rb +1 -1
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +4 -20
- data/lib/active_support/core_ext/range/overlaps.rb +1 -1
- data/lib/active_support/core_ext/range.rb +1 -1
- data/lib/active_support/core_ext/regexp.rb +8 -1
- data/lib/active_support/core_ext/securerandom.rb +1 -1
- data/lib/active_support/core_ext/string/access.rb +5 -24
- data/lib/active_support/core_ext/string/conversions.rb +3 -2
- data/lib/active_support/core_ext/string/filters.rb +1 -1
- data/lib/active_support/core_ext/string/inflections.rb +39 -5
- data/lib/active_support/core_ext/string/inquiry.rb +2 -1
- data/lib/active_support/core_ext/string/multibyte.rb +2 -2
- data/lib/active_support/core_ext/string/output_safety.rb +92 -41
- data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
- data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
- data/lib/active_support/core_ext/symbol.rb +3 -0
- data/lib/active_support/core_ext/time/calculations.rb +25 -7
- data/lib/active_support/core_ext/time/conversions.rb +15 -12
- data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
- data/lib/active_support/core_ext/time/zones.rb +7 -22
- data/lib/active_support/core_ext/time.rb +1 -0
- data/lib/active_support/core_ext/uri.rb +3 -23
- data/lib/active_support/core_ext.rb +2 -1
- data/lib/active_support/current_attributes/test_helper.rb +13 -0
- data/lib/active_support/current_attributes.rb +39 -16
- data/lib/active_support/dependencies/interlock.rb +10 -18
- data/lib/active_support/dependencies/require_dependency.rb +28 -0
- data/lib/active_support/dependencies.rb +58 -769
- data/lib/active_support/deprecation/behaviors.rb +23 -7
- 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 +6 -5
- data/lib/active_support/deprecation/proxy_wrappers.rb +4 -4
- data/lib/active_support/deprecation/reporting.rb +50 -7
- data/lib/active_support/deprecation.rb +7 -2
- data/lib/active_support/descendants_tracker.rb +174 -64
- data/lib/active_support/digest.rb +5 -3
- data/lib/active_support/duration/iso8601_parser.rb +3 -3
- data/lib/active_support/duration/iso8601_serializer.rb +24 -10
- data/lib/active_support/duration.rb +134 -55
- data/lib/active_support/encrypted_configuration.rb +13 -2
- data/lib/active_support/encrypted_file.rb +32 -3
- data/lib/active_support/environment_inquirer.rb +20 -0
- data/lib/active_support/error_reporter.rb +117 -0
- data/lib/active_support/evented_file_update_checker.rb +72 -138
- data/lib/active_support/execution_context/test_helper.rb +13 -0
- data/lib/active_support/execution_context.rb +53 -0
- data/lib/active_support/execution_wrapper.rb +43 -21
- data/lib/active_support/executor/test_helper.rb +7 -0
- data/lib/active_support/fork_tracker.rb +71 -0
- data/lib/active_support/gem_version.rb +3 -3
- data/lib/active_support/hash_with_indifferent_access.rb +51 -25
- data/lib/active_support/html_safe_translation.rb +43 -0
- data/lib/active_support/i18n.rb +1 -0
- data/lib/active_support/i18n_railtie.rb +14 -19
- data/lib/active_support/inflector/inflections.rb +24 -9
- data/lib/active_support/inflector/methods.rb +29 -49
- data/lib/active_support/inflector/transliterate.rb +5 -5
- data/lib/active_support/isolated_execution_state.rb +72 -0
- data/lib/active_support/json/decoding.rb +4 -4
- data/lib/active_support/json/encoding.rb +8 -4
- data/lib/active_support/key_generator.rb +23 -6
- data/lib/active_support/lazy_load_hooks.rb +28 -4
- data/lib/active_support/locale/en.yml +8 -4
- data/lib/active_support/log_subscriber/test_helper.rb +2 -2
- data/lib/active_support/log_subscriber.rb +23 -5
- data/lib/active_support/logger.rb +1 -1
- data/lib/active_support/logger_silence.rb +2 -26
- data/lib/active_support/logger_thread_safe_level.rb +34 -21
- data/lib/active_support/message_encryptor.rb +16 -13
- data/lib/active_support/message_verifier.rb +50 -18
- data/lib/active_support/messages/metadata.rb +2 -2
- data/lib/active_support/messages/rotation_configuration.rb +2 -1
- data/lib/active_support/messages/rotator.rb +6 -5
- data/lib/active_support/multibyte/chars.rb +13 -52
- data/lib/active_support/multibyte/unicode.rb +1 -87
- data/lib/active_support/multibyte.rb +1 -1
- data/lib/active_support/notifications/fanout.rb +110 -69
- data/lib/active_support/notifications/instrumenter.rb +37 -29
- data/lib/active_support/notifications.rb +55 -28
- data/lib/active_support/number_helper/number_converter.rb +2 -4
- data/lib/active_support/number_helper/number_to_currency_converter.rb +11 -6
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
- data/lib/active_support/number_helper/number_to_human_converter.rb +1 -1
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +2 -2
- data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -1
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +9 -5
- data/lib/active_support/number_helper/rounding_helper.rb +12 -32
- data/lib/active_support/number_helper.rb +29 -16
- data/lib/active_support/option_merger.rb +11 -18
- data/lib/active_support/ordered_hash.rb +1 -1
- data/lib/active_support/ordered_options.rb +9 -3
- data/lib/active_support/parameter_filter.rb +21 -11
- data/lib/active_support/per_thread_registry.rb +6 -1
- data/lib/active_support/rails.rb +1 -4
- data/lib/active_support/railtie.rb +77 -5
- data/lib/active_support/reloader.rb +1 -1
- data/lib/active_support/rescuable.rb +16 -16
- data/lib/active_support/ruby_features.rb +7 -0
- 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 +2 -2
- data/lib/active_support/subscriber.rb +19 -25
- data/lib/active_support/tagged_logging.rb +31 -6
- data/lib/active_support/test_case.rb +13 -21
- data/lib/active_support/testing/assertions.rb +50 -13
- data/lib/active_support/testing/deprecation.rb +52 -1
- data/lib/active_support/testing/isolation.rb +2 -2
- data/lib/active_support/testing/method_call_assertions.rb +5 -5
- data/lib/active_support/testing/parallelization/server.rb +82 -0
- data/lib/active_support/testing/parallelization/worker.rb +103 -0
- data/lib/active_support/testing/parallelization.rb +16 -95
- data/lib/active_support/testing/parallelize_executor.rb +76 -0
- data/lib/active_support/testing/stream.rb +3 -5
- data/lib/active_support/testing/tagged_logging.rb +1 -1
- data/lib/active_support/testing/time_helpers.rb +53 -5
- data/lib/active_support/time_with_zone.rb +126 -62
- data/lib/active_support/values/time_zone.rb +54 -23
- data/lib/active_support/version.rb +1 -1
- data/lib/active_support/xml_mini/jdom.rb +1 -1
- data/lib/active_support/xml_mini/libxml.rb +5 -5
- data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
- data/lib/active_support/xml_mini/nokogiri.rb +4 -4
- data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
- data/lib/active_support/xml_mini/rexml.rb +9 -2
- data/lib/active_support/xml_mini.rb +5 -4
- data/lib/active_support.rb +29 -1
- metadata +46 -45
- data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
- data/lib/active_support/core_ext/hash/compact.rb +0 -5
- data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
- data/lib/active_support/core_ext/marshal.rb +0 -24
- data/lib/active_support/core_ext/module/reachable.rb +0 -6
- data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
- data/lib/active_support/core_ext/range/include_range.rb +0 -9
- data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
@@ -6,6 +6,8 @@ module ActiveSupport
|
|
6
6
|
class Duration
|
7
7
|
# Serializes duration to string according to ISO 8601 Duration format.
|
8
8
|
class ISO8601Serializer # :nodoc:
|
9
|
+
DATE_COMPONENTS = %i(years months days)
|
10
|
+
|
9
11
|
def initialize(duration, precision: nil)
|
10
12
|
@duration = duration
|
11
13
|
@precision = precision
|
@@ -13,22 +15,22 @@ module ActiveSupport
|
|
13
15
|
|
14
16
|
# Builds and returns output string.
|
15
17
|
def serialize
|
16
|
-
parts
|
18
|
+
parts = normalize
|
17
19
|
return "PT0S" if parts.empty?
|
18
20
|
|
19
21
|
output = +"P"
|
20
22
|
output << "#{parts[:years]}Y" if parts.key?(:years)
|
21
23
|
output << "#{parts[:months]}M" if parts.key?(:months)
|
22
|
-
output << "#{parts[:weeks]}W" if parts.key?(:weeks)
|
23
24
|
output << "#{parts[:days]}D" if parts.key?(:days)
|
25
|
+
output << "#{parts[:weeks]}W" if parts.key?(:weeks)
|
24
26
|
time = +""
|
25
27
|
time << "#{parts[:hours]}H" if parts.key?(:hours)
|
26
28
|
time << "#{parts[:minutes]}M" if parts.key?(:minutes)
|
27
29
|
if parts.key?(:seconds)
|
28
|
-
time << "#{
|
30
|
+
time << "#{format_seconds(parts[:seconds])}S"
|
29
31
|
end
|
30
32
|
output << "T#{time}" unless time.empty?
|
31
|
-
|
33
|
+
output
|
32
34
|
end
|
33
35
|
|
34
36
|
private
|
@@ -40,13 +42,25 @@ module ActiveSupport
|
|
40
42
|
parts = @duration.parts.each_with_object(Hash.new(0)) do |(k, v), p|
|
41
43
|
p[k] += v unless v.zero?
|
42
44
|
end
|
43
|
-
|
44
|
-
|
45
|
-
if parts
|
46
|
-
|
47
|
-
|
45
|
+
|
46
|
+
# Convert weeks to days and remove weeks if mixed with date parts
|
47
|
+
if week_mixed_with_date?(parts)
|
48
|
+
parts[:days] += parts.delete(:weeks) * SECONDS_PER_WEEK / SECONDS_PER_DAY
|
49
|
+
end
|
50
|
+
|
51
|
+
parts
|
52
|
+
end
|
53
|
+
|
54
|
+
def week_mixed_with_date?(parts)
|
55
|
+
parts.key?(:weeks) && (parts.keys & DATE_COMPONENTS).any?
|
56
|
+
end
|
57
|
+
|
58
|
+
def format_seconds(seconds)
|
59
|
+
if @precision
|
60
|
+
sprintf("%0.0#{@precision}f", seconds)
|
61
|
+
else
|
62
|
+
seconds.to_s
|
48
63
|
end
|
49
|
-
[parts, sign]
|
50
64
|
end
|
51
65
|
end
|
52
66
|
end
|
@@ -11,7 +11,7 @@ module ActiveSupport
|
|
11
11
|
#
|
12
12
|
# 1.month.ago # equivalent to Time.now.advance(months: -1)
|
13
13
|
class Duration
|
14
|
-
class Scalar < Numeric
|
14
|
+
class Scalar < Numeric # :nodoc:
|
15
15
|
attr_reader :value
|
16
16
|
delegate :to_i, :to_f, :to_s, to: :value
|
17
17
|
|
@@ -39,11 +39,11 @@ module ActiveSupport
|
|
39
39
|
|
40
40
|
def +(other)
|
41
41
|
if Duration === other
|
42
|
-
seconds = value + other.
|
43
|
-
new_parts = other.
|
42
|
+
seconds = value + other._parts.fetch(:seconds, 0)
|
43
|
+
new_parts = other._parts.merge(seconds: seconds)
|
44
44
|
new_value = value + other.value
|
45
45
|
|
46
|
-
Duration.new(new_value, new_parts)
|
46
|
+
Duration.new(new_value, new_parts, other.variable?)
|
47
47
|
else
|
48
48
|
calculate(:+, other)
|
49
49
|
end
|
@@ -51,12 +51,12 @@ module ActiveSupport
|
|
51
51
|
|
52
52
|
def -(other)
|
53
53
|
if Duration === other
|
54
|
-
seconds = value - other.
|
55
|
-
new_parts = other.
|
54
|
+
seconds = value - other._parts.fetch(:seconds, 0)
|
55
|
+
new_parts = other._parts.transform_values(&:-@)
|
56
56
|
new_parts = new_parts.merge(seconds: seconds)
|
57
57
|
new_value = value - other.value
|
58
58
|
|
59
|
-
Duration.new(new_value, new_parts)
|
59
|
+
Duration.new(new_value, new_parts, other.variable?)
|
60
60
|
else
|
61
61
|
calculate(:-, other)
|
62
62
|
end
|
@@ -64,10 +64,10 @@ module ActiveSupport
|
|
64
64
|
|
65
65
|
def *(other)
|
66
66
|
if Duration === other
|
67
|
-
new_parts = other.
|
67
|
+
new_parts = other._parts.transform_values { |other_value| value * other_value }
|
68
68
|
new_value = value * other.value
|
69
69
|
|
70
|
-
Duration.new(new_value, new_parts)
|
70
|
+
Duration.new(new_value, new_parts, other.variable?)
|
71
71
|
else
|
72
72
|
calculate(:*, other)
|
73
73
|
end
|
@@ -89,6 +89,10 @@ module ActiveSupport
|
|
89
89
|
end
|
90
90
|
end
|
91
91
|
|
92
|
+
def variable? # :nodoc:
|
93
|
+
false
|
94
|
+
end
|
95
|
+
|
92
96
|
private
|
93
97
|
def calculate(op, other)
|
94
98
|
if Scalar === other
|
@@ -123,8 +127,9 @@ module ActiveSupport
|
|
123
127
|
}.freeze
|
124
128
|
|
125
129
|
PARTS = [:years, :months, :weeks, :days, :hours, :minutes, :seconds].freeze
|
130
|
+
VARIABLE_PARTS = [:years, :months, :weeks, :days].freeze
|
126
131
|
|
127
|
-
|
132
|
+
attr_reader :value
|
128
133
|
|
129
134
|
autoload :ISO8601Parser, "active_support/duration/iso8601_parser"
|
130
135
|
autoload :ISO8601Serializer, "active_support/duration/iso8601_serializer"
|
@@ -140,38 +145,38 @@ module ActiveSupport
|
|
140
145
|
new(calculate_total_seconds(parts), parts)
|
141
146
|
end
|
142
147
|
|
143
|
-
def ===(other)
|
148
|
+
def ===(other) # :nodoc:
|
144
149
|
other.is_a?(Duration)
|
145
150
|
rescue ::NoMethodError
|
146
151
|
false
|
147
152
|
end
|
148
153
|
|
149
|
-
def seconds(value)
|
150
|
-
new(value,
|
154
|
+
def seconds(value) # :nodoc:
|
155
|
+
new(value, { seconds: value }, false)
|
151
156
|
end
|
152
157
|
|
153
|
-
def minutes(value)
|
154
|
-
new(value * SECONDS_PER_MINUTE,
|
158
|
+
def minutes(value) # :nodoc:
|
159
|
+
new(value * SECONDS_PER_MINUTE, { minutes: value }, false)
|
155
160
|
end
|
156
161
|
|
157
|
-
def hours(value)
|
158
|
-
new(value * SECONDS_PER_HOUR,
|
162
|
+
def hours(value) # :nodoc:
|
163
|
+
new(value * SECONDS_PER_HOUR, { hours: value }, false)
|
159
164
|
end
|
160
165
|
|
161
|
-
def days(value)
|
162
|
-
new(value * SECONDS_PER_DAY,
|
166
|
+
def days(value) # :nodoc:
|
167
|
+
new(value * SECONDS_PER_DAY, { days: value }, true)
|
163
168
|
end
|
164
169
|
|
165
|
-
def weeks(value)
|
166
|
-
new(value * SECONDS_PER_WEEK,
|
170
|
+
def weeks(value) # :nodoc:
|
171
|
+
new(value * SECONDS_PER_WEEK, { weeks: value }, true)
|
167
172
|
end
|
168
173
|
|
169
|
-
def months(value)
|
170
|
-
new(value * SECONDS_PER_MONTH,
|
174
|
+
def months(value) # :nodoc:
|
175
|
+
new(value * SECONDS_PER_MONTH, { months: value }, true)
|
171
176
|
end
|
172
177
|
|
173
|
-
def years(value)
|
174
|
-
new(value * SECONDS_PER_YEAR,
|
178
|
+
def years(value) # :nodoc:
|
179
|
+
new(value * SECONDS_PER_YEAR, { years: value }, true)
|
175
180
|
end
|
176
181
|
|
177
182
|
# Creates a new Duration from a seconds value that is converted
|
@@ -181,20 +186,30 @@ module ActiveSupport
|
|
181
186
|
# ActiveSupport::Duration.build(2716146).parts # => {:months=>1, :days=>1}
|
182
187
|
#
|
183
188
|
def build(value)
|
189
|
+
unless value.is_a?(::Numeric)
|
190
|
+
raise TypeError, "can't build an #{self.name} from a #{value.class.name}"
|
191
|
+
end
|
192
|
+
|
184
193
|
parts = {}
|
185
|
-
|
194
|
+
remainder_sign = value <=> 0
|
195
|
+
remainder = value.round(9).abs
|
196
|
+
variable = false
|
186
197
|
|
187
198
|
PARTS.each do |part|
|
188
199
|
unless part == :seconds
|
189
200
|
part_in_seconds = PARTS_IN_SECONDS[part]
|
190
|
-
parts[part] = remainder.div(part_in_seconds)
|
201
|
+
parts[part] = remainder.div(part_in_seconds) * remainder_sign
|
191
202
|
remainder %= part_in_seconds
|
203
|
+
|
204
|
+
unless parts[part].zero?
|
205
|
+
variable ||= VARIABLE_PARTS.include?(part)
|
206
|
+
end
|
192
207
|
end
|
193
208
|
end unless value == 0
|
194
209
|
|
195
|
-
parts[:seconds] = remainder
|
210
|
+
parts[:seconds] = remainder * remainder_sign
|
196
211
|
|
197
|
-
new(value, parts)
|
212
|
+
new(value, parts, variable)
|
198
213
|
end
|
199
214
|
|
200
215
|
private
|
@@ -205,13 +220,23 @@ module ActiveSupport
|
|
205
220
|
end
|
206
221
|
end
|
207
222
|
|
208
|
-
def initialize(value, parts)
|
209
|
-
@value, @parts = value, parts
|
210
|
-
@parts.default = 0
|
223
|
+
def initialize(value, parts, variable = nil) # :nodoc:
|
224
|
+
@value, @parts = value, parts
|
211
225
|
@parts.reject! { |k, v| v.zero? } unless value == 0
|
226
|
+
@parts.freeze
|
227
|
+
@variable = variable
|
228
|
+
|
229
|
+
if @variable.nil?
|
230
|
+
@variable = @parts.any? { |part, _| VARIABLE_PARTS.include?(part) }
|
231
|
+
end
|
212
232
|
end
|
213
233
|
|
214
|
-
|
234
|
+
# Returns a copy of the parts hash that defines the duration
|
235
|
+
def parts
|
236
|
+
@parts.dup
|
237
|
+
end
|
238
|
+
|
239
|
+
def coerce(other) # :nodoc:
|
215
240
|
case other
|
216
241
|
when Scalar
|
217
242
|
[other, self]
|
@@ -236,14 +261,13 @@ module ActiveSupport
|
|
236
261
|
# are treated as seconds.
|
237
262
|
def +(other)
|
238
263
|
if Duration === other
|
239
|
-
parts = @parts.
|
240
|
-
|
241
|
-
parts[key] += value
|
264
|
+
parts = @parts.merge(other._parts) do |_key, value, other_value|
|
265
|
+
value + other_value
|
242
266
|
end
|
243
|
-
Duration.new(value + other.value, parts)
|
267
|
+
Duration.new(value + other.value, parts, @variable || other.variable?)
|
244
268
|
else
|
245
|
-
seconds = @parts
|
246
|
-
Duration.new(value + other, @parts.merge(seconds: seconds))
|
269
|
+
seconds = @parts.fetch(:seconds, 0) + other
|
270
|
+
Duration.new(value + other, @parts.merge(seconds: seconds), @variable)
|
247
271
|
end
|
248
272
|
end
|
249
273
|
|
@@ -256,9 +280,9 @@ module ActiveSupport
|
|
256
280
|
# Multiplies this Duration by a Numeric and returns a new Duration.
|
257
281
|
def *(other)
|
258
282
|
if Scalar === other || Duration === other
|
259
|
-
Duration.new(value * other.value, parts.
|
283
|
+
Duration.new(value * other.value, @parts.transform_values { |number| number * other.value }, @variable || other.variable?)
|
260
284
|
elsif Numeric === other
|
261
|
-
Duration.new(value * other, parts.
|
285
|
+
Duration.new(value * other, @parts.transform_values { |number| number * other }, @variable)
|
262
286
|
else
|
263
287
|
raise_type_error(other)
|
264
288
|
end
|
@@ -267,11 +291,11 @@ module ActiveSupport
|
|
267
291
|
# Divides this Duration by a Numeric and returns a new Duration.
|
268
292
|
def /(other)
|
269
293
|
if Scalar === other
|
270
|
-
Duration.new(value / other.value, parts.
|
294
|
+
Duration.new(value / other.value, @parts.transform_values { |number| number / other.value }, @variable)
|
271
295
|
elsif Duration === other
|
272
296
|
value / other.value
|
273
297
|
elsif Numeric === other
|
274
|
-
Duration.new(value / other, parts.
|
298
|
+
Duration.new(value / other, @parts.transform_values { |number| number / other }, @variable)
|
275
299
|
else
|
276
300
|
raise_type_error(other)
|
277
301
|
end
|
@@ -289,11 +313,15 @@ module ActiveSupport
|
|
289
313
|
end
|
290
314
|
end
|
291
315
|
|
292
|
-
def -@
|
293
|
-
Duration.new(-value, parts.
|
316
|
+
def -@ # :nodoc:
|
317
|
+
Duration.new(-value, @parts.transform_values(&:-@), @variable)
|
294
318
|
end
|
295
319
|
|
296
|
-
def
|
320
|
+
def +@ # :nodoc:
|
321
|
+
self
|
322
|
+
end
|
323
|
+
|
324
|
+
def is_a?(klass) # :nodoc:
|
297
325
|
Duration == klass || value.is_a?(klass)
|
298
326
|
end
|
299
327
|
alias :kind_of? :is_a?
|
@@ -343,6 +371,49 @@ module ActiveSupport
|
|
343
371
|
def to_i
|
344
372
|
@value.to_i
|
345
373
|
end
|
374
|
+
alias :in_seconds :to_i
|
375
|
+
|
376
|
+
# Returns the amount of minutes a duration covers as a float
|
377
|
+
#
|
378
|
+
# 1.day.in_minutes # => 1440.0
|
379
|
+
def in_minutes
|
380
|
+
in_seconds / SECONDS_PER_MINUTE.to_f
|
381
|
+
end
|
382
|
+
|
383
|
+
# Returns the amount of hours a duration covers as a float
|
384
|
+
#
|
385
|
+
# 1.day.in_hours # => 24.0
|
386
|
+
def in_hours
|
387
|
+
in_seconds / SECONDS_PER_HOUR.to_f
|
388
|
+
end
|
389
|
+
|
390
|
+
# Returns the amount of days a duration covers as a float
|
391
|
+
#
|
392
|
+
# 12.hours.in_days # => 0.5
|
393
|
+
def in_days
|
394
|
+
in_seconds / SECONDS_PER_DAY.to_f
|
395
|
+
end
|
396
|
+
|
397
|
+
# Returns the amount of weeks a duration covers as a float
|
398
|
+
#
|
399
|
+
# 2.months.in_weeks # => 8.696
|
400
|
+
def in_weeks
|
401
|
+
in_seconds / SECONDS_PER_WEEK.to_f
|
402
|
+
end
|
403
|
+
|
404
|
+
# Returns the amount of months a duration covers as a float
|
405
|
+
#
|
406
|
+
# 9.weeks.in_months # => 2.07
|
407
|
+
def in_months
|
408
|
+
in_seconds / SECONDS_PER_MONTH.to_f
|
409
|
+
end
|
410
|
+
|
411
|
+
# Returns the amount of years a duration covers as a float
|
412
|
+
#
|
413
|
+
# 30.days.in_years # => 0.082
|
414
|
+
def in_years
|
415
|
+
in_seconds / SECONDS_PER_YEAR.to_f
|
416
|
+
end
|
346
417
|
|
347
418
|
# Returns +true+ if +other+ is also a Duration instance, which has the
|
348
419
|
# same parts as this one.
|
@@ -370,24 +441,24 @@ module ActiveSupport
|
|
370
441
|
alias :until :ago
|
371
442
|
alias :before :ago
|
372
443
|
|
373
|
-
def inspect
|
374
|
-
return "#{value} seconds" if parts.empty?
|
444
|
+
def inspect # :nodoc:
|
445
|
+
return "#{value} seconds" if @parts.empty?
|
375
446
|
|
376
|
-
parts.
|
447
|
+
@parts.
|
377
448
|
sort_by { |unit, _ | PARTS.index(unit) }.
|
378
449
|
map { |unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}" }.
|
379
|
-
to_sentence(locale:
|
450
|
+
to_sentence(locale: false)
|
380
451
|
end
|
381
452
|
|
382
|
-
def as_json(options = nil)
|
453
|
+
def as_json(options = nil) # :nodoc:
|
383
454
|
to_i
|
384
455
|
end
|
385
456
|
|
386
|
-
def init_with(coder)
|
457
|
+
def init_with(coder) # :nodoc:
|
387
458
|
initialize(coder["value"], coder["parts"])
|
388
459
|
end
|
389
460
|
|
390
|
-
def encode_with(coder)
|
461
|
+
def encode_with(coder) # :nodoc:
|
391
462
|
coder.map = { "value" => @value, "parts" => @parts }
|
392
463
|
end
|
393
464
|
|
@@ -397,16 +468,24 @@ module ActiveSupport
|
|
397
468
|
ISO8601Serializer.new(self, precision: precision).serialize
|
398
469
|
end
|
399
470
|
|
471
|
+
def variable? # :nodoc:
|
472
|
+
@variable
|
473
|
+
end
|
474
|
+
|
475
|
+
def _parts # :nodoc:
|
476
|
+
@parts
|
477
|
+
end
|
478
|
+
|
400
479
|
private
|
401
480
|
def sum(sign, time = ::Time.current)
|
402
481
|
unless time.acts_like?(:time) || time.acts_like?(:date)
|
403
482
|
raise ::ArgumentError, "expected a time or date, got #{time.inspect}"
|
404
483
|
end
|
405
484
|
|
406
|
-
if parts.empty?
|
485
|
+
if @parts.empty?
|
407
486
|
time.since(sign * value)
|
408
487
|
else
|
409
|
-
parts.inject(time) do |t, (type, number)|
|
488
|
+
@parts.inject(time) do |t, (type, number)|
|
410
489
|
if type == :seconds
|
411
490
|
t.since(sign * number)
|
412
491
|
elsif type == :minutes
|
@@ -34,12 +34,23 @@ module ActiveSupport
|
|
34
34
|
end
|
35
35
|
|
36
36
|
private
|
37
|
+
def deep_transform(hash)
|
38
|
+
return hash unless hash.is_a?(Hash)
|
39
|
+
|
40
|
+
h = ActiveSupport::InheritableOptions.new
|
41
|
+
hash.each do |k, v|
|
42
|
+
h[k] = deep_transform(v)
|
43
|
+
end
|
44
|
+
h
|
45
|
+
end
|
46
|
+
|
37
47
|
def options
|
38
|
-
@options ||= ActiveSupport::InheritableOptions.new(config)
|
48
|
+
@options ||= ActiveSupport::InheritableOptions.new(deep_transform(config))
|
39
49
|
end
|
40
50
|
|
41
51
|
def deserialize(config)
|
42
|
-
YAML.
|
52
|
+
doc = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(config) : YAML.load(config)
|
53
|
+
doc.presence || {}
|
43
54
|
end
|
44
55
|
end
|
45
56
|
end
|
@@ -20,24 +20,47 @@ module ActiveSupport
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
+
class InvalidKeyLengthError < RuntimeError
|
24
|
+
def initialize
|
25
|
+
super "Encryption key must be exactly #{EncryptedFile.expected_key_length} characters."
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
23
29
|
CIPHER = "aes-128-gcm"
|
24
30
|
|
25
31
|
def self.generate_key
|
26
32
|
SecureRandom.hex(ActiveSupport::MessageEncryptor.key_len(CIPHER))
|
27
33
|
end
|
28
34
|
|
35
|
+
def self.expected_key_length # :nodoc:
|
36
|
+
@expected_key_length ||= generate_key.length
|
37
|
+
end
|
38
|
+
|
29
39
|
|
30
40
|
attr_reader :content_path, :key_path, :env_key, :raise_if_missing_key
|
31
41
|
|
32
42
|
def initialize(content_path:, key_path:, env_key:, raise_if_missing_key:)
|
33
|
-
@content_path
|
43
|
+
@content_path = Pathname.new(content_path).yield_self { |path| path.symlink? ? path.realpath : path }
|
44
|
+
@key_path = Pathname.new(key_path)
|
34
45
|
@env_key, @raise_if_missing_key = env_key, raise_if_missing_key
|
35
46
|
end
|
36
47
|
|
48
|
+
# Returns the encryption key, first trying the environment variable
|
49
|
+
# specified by +env_key+, then trying the key file specified by +key_path+.
|
50
|
+
# If +raise_if_missing_key+ is true, raises MissingKeyError if the
|
51
|
+
# environment variable is not set and the key file does not exist.
|
37
52
|
def key
|
38
53
|
read_env_key || read_key_file || handle_missing_key
|
39
54
|
end
|
40
55
|
|
56
|
+
# Reads the file and returns the decrypted content.
|
57
|
+
#
|
58
|
+
# Raises:
|
59
|
+
# - MissingKeyError if the key is missing and +raise_if_missing_key+ is true.
|
60
|
+
# - MissingContentError if the encrypted file does not exist or otherwise
|
61
|
+
# if the key is missing.
|
62
|
+
# - ActiveSupport::MessageEncryptor::InvalidMessage if the content cannot be
|
63
|
+
# decrypted or verified.
|
41
64
|
def read
|
42
65
|
if !key.nil? && content_path.exist?
|
43
66
|
decrypt content_path.binread
|
@@ -73,6 +96,7 @@ module ActiveSupport
|
|
73
96
|
|
74
97
|
|
75
98
|
def encrypt(contents)
|
99
|
+
check_key_length
|
76
100
|
encryptor.encrypt_and_sign contents
|
77
101
|
end
|
78
102
|
|
@@ -86,15 +110,20 @@ module ActiveSupport
|
|
86
110
|
|
87
111
|
|
88
112
|
def read_env_key
|
89
|
-
ENV[env_key]
|
113
|
+
ENV[env_key].presence
|
90
114
|
end
|
91
115
|
|
92
116
|
def read_key_file
|
93
|
-
|
117
|
+
return @key_file_contents if defined?(@key_file_contents)
|
118
|
+
@key_file_contents = (key_path.binread.strip if key_path.exist?)
|
94
119
|
end
|
95
120
|
|
96
121
|
def handle_missing_key
|
97
122
|
raise MissingKeyError.new(key_path: key_path, env_key: env_key) if raise_if_missing_key
|
98
123
|
end
|
124
|
+
|
125
|
+
def check_key_length
|
126
|
+
raise InvalidKeyLengthError if key&.length != self.class.expected_key_length
|
127
|
+
end
|
99
128
|
end
|
100
129
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/string_inquirer"
|
4
|
+
|
5
|
+
module ActiveSupport
|
6
|
+
class EnvironmentInquirer < StringInquirer # :nodoc:
|
7
|
+
DEFAULT_ENVIRONMENTS = ["development", "test", "production"]
|
8
|
+
def initialize(env)
|
9
|
+
super(env)
|
10
|
+
|
11
|
+
DEFAULT_ENVIRONMENTS.each do |default|
|
12
|
+
instance_variable_set :"@#{default}", env == default
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
DEFAULT_ENVIRONMENTS.each do |env|
|
17
|
+
class_eval "def #{env}?; @#{env}; end"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveSupport
|
4
|
+
# +ActiveSupport::ErrorReporter+ is a common interface for error reporting services.
|
5
|
+
#
|
6
|
+
# To rescue and report any unhandled error, you can use the +handle+ method:
|
7
|
+
#
|
8
|
+
# Rails.error.handle do
|
9
|
+
# do_something!
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# If an error is raised, it will be reported and swallowed.
|
13
|
+
#
|
14
|
+
# Alternatively if you want to report the error but not swallow it, you can use +record+
|
15
|
+
#
|
16
|
+
# Rails.error.record do
|
17
|
+
# do_something!
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# Both methods can be restricted to only handle a specific exception class
|
21
|
+
#
|
22
|
+
# maybe_tags = Rails.error.handle(Redis::BaseError) { redis.get("tags") }
|
23
|
+
#
|
24
|
+
# You can also pass some extra context information that may be used by the error subscribers:
|
25
|
+
#
|
26
|
+
# Rails.error.handle(context: { section: "admin" }) do
|
27
|
+
# # ...
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# Additionally a +severity+ can be passed along to communicate how important the error report is.
|
31
|
+
# +severity+ can be one of +:error+, +:warning+, or +:info+. Handled errors default to the +:warning+
|
32
|
+
# severity, and unhandled ones to +:error+.
|
33
|
+
#
|
34
|
+
# Both +handle+ and +record+ pass through the return value from the block. In the case of +handle+
|
35
|
+
# rescuing an error, a fallback can be provided. The fallback must be a callable whose result will
|
36
|
+
# be returned when the block raises and is handled:
|
37
|
+
#
|
38
|
+
# user = Rails.error.handle(fallback: -> { User.anonymous }) do
|
39
|
+
# User.find_by(params)
|
40
|
+
# end
|
41
|
+
class ErrorReporter
|
42
|
+
SEVERITIES = %i(error warning info)
|
43
|
+
|
44
|
+
attr_accessor :logger
|
45
|
+
|
46
|
+
def initialize(*subscribers, logger: nil)
|
47
|
+
@subscribers = subscribers.flatten
|
48
|
+
@logger = logger
|
49
|
+
end
|
50
|
+
|
51
|
+
# Report any unhandled exception, and swallow it.
|
52
|
+
#
|
53
|
+
# Rails.error.handle do
|
54
|
+
# 1 + '1'
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
def handle(error_class = StandardError, severity: :warning, context: {}, fallback: nil)
|
58
|
+
yield
|
59
|
+
rescue error_class => error
|
60
|
+
report(error, handled: true, severity: severity, context: context)
|
61
|
+
fallback.call if fallback
|
62
|
+
end
|
63
|
+
|
64
|
+
def record(error_class = StandardError, severity: :error, context: {})
|
65
|
+
yield
|
66
|
+
rescue error_class => error
|
67
|
+
report(error, handled: false, severity: severity, context: context)
|
68
|
+
raise
|
69
|
+
end
|
70
|
+
|
71
|
+
# Register a new error subscriber. The subscriber must respond to
|
72
|
+
#
|
73
|
+
# report(Exception, handled: Boolean, context: Hash)
|
74
|
+
#
|
75
|
+
# The +report+ method +should+ never raise an error.
|
76
|
+
def subscribe(subscriber)
|
77
|
+
unless subscriber.respond_to?(:report)
|
78
|
+
raise ArgumentError, "Error subscribers must respond to #report"
|
79
|
+
end
|
80
|
+
@subscribers << subscriber
|
81
|
+
end
|
82
|
+
|
83
|
+
# Update the execution context that is accessible to error subscribers
|
84
|
+
#
|
85
|
+
# Rails.error.set_context(section: "checkout", user_id: @user.id)
|
86
|
+
#
|
87
|
+
# See +ActiveSupport::ExecutionContext.set+
|
88
|
+
def set_context(...)
|
89
|
+
ActiveSupport::ExecutionContext.set(...)
|
90
|
+
end
|
91
|
+
|
92
|
+
# When the block based +handle+ and +record+ methods are not suitable, you can directly use +report+
|
93
|
+
#
|
94
|
+
# Rails.error.report(error, handled: true)
|
95
|
+
def report(error, handled:, severity: handled ? :warning : :error, context: {})
|
96
|
+
unless SEVERITIES.include?(severity)
|
97
|
+
raise ArgumentError, "severity must be one of #{SEVERITIES.map(&:inspect).join(", ")}, got: #{severity.inspect}"
|
98
|
+
end
|
99
|
+
|
100
|
+
full_context = ActiveSupport::ExecutionContext.to_h.merge(context)
|
101
|
+
@subscribers.each do |subscriber|
|
102
|
+
subscriber.report(error, handled: handled, severity: severity, context: full_context)
|
103
|
+
rescue => subscriber_error
|
104
|
+
if logger
|
105
|
+
logger.fatal(
|
106
|
+
"Error subscriber raised an error: #{subscriber_error.message} (#{subscriber_error.class})\n" +
|
107
|
+
subscriber_error.backtrace.join("\n")
|
108
|
+
)
|
109
|
+
else
|
110
|
+
raise
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
nil
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|