activesupport 6.0.6.1 → 7.1.3.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +865 -438
- data/MIT-LICENSE +1 -1
- data/README.rdoc +6 -6
- data/lib/active_support/actionable_error.rb +4 -2
- data/lib/active_support/array_inquirer.rb +4 -2
- data/lib/active_support/backtrace_cleaner.rb +30 -10
- data/lib/active_support/benchmarkable.rb +4 -3
- data/lib/active_support/broadcast_logger.rb +250 -0
- data/lib/active_support/builder.rb +1 -1
- data/lib/active_support/cache/coder.rb +153 -0
- data/lib/active_support/cache/entry.rb +134 -0
- data/lib/active_support/cache/file_store.rb +53 -20
- data/lib/active_support/cache/mem_cache_store.rb +208 -63
- data/lib/active_support/cache/memory_store.rb +120 -38
- data/lib/active_support/cache/null_store.rb +16 -2
- data/lib/active_support/cache/redis_cache_store.rb +201 -208
- data/lib/active_support/cache/serializer_with_fallback.rb +175 -0
- data/lib/active_support/cache/strategy/local_cache.rb +73 -66
- data/lib/active_support/cache.rb +539 -261
- data/lib/active_support/callbacks.rb +273 -142
- data/lib/active_support/code_generator.rb +65 -0
- data/lib/active_support/concern.rb +53 -7
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +44 -7
- data/lib/active_support/concurrency/null_lock.rb +13 -0
- data/lib/active_support/concurrency/share_lock.rb +2 -2
- data/lib/active_support/configurable.rb +19 -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 +15 -13
- 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/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 +19 -29
- data/lib/active_support/core_ext/date/blank.rb +1 -1
- data/lib/active_support/core_ext/date/calculations.rb +24 -9
- data/lib/active_support/core_ext/date/conversions.rb +18 -16
- data/lib/active_support/core_ext/date_and_time/calculations.rb +27 -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/calculations.rb +4 -0
- data/lib/active_support/core_ext/date_time/conversions.rb +19 -15
- data/lib/active_support/core_ext/digest/uuid.rb +30 -13
- data/lib/active_support/core_ext/enumerable.rb +146 -72
- data/lib/active_support/core_ext/erb/util.rb +196 -0
- data/lib/active_support/core_ext/file/atomic.rb +3 -1
- data/lib/active_support/core_ext/hash/conversions.rb +3 -4
- data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +4 -4
- data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
- data/lib/active_support/core_ext/hash/keys.rb +5 -5
- data/lib/active_support/core_ext/hash/slice.rb +3 -2
- data/lib/active_support/core_ext/integer/inflections.rb +12 -12
- 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 +31 -29
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +51 -20
- data/lib/active_support/core_ext/module/concerning.rb +14 -8
- data/lib/active_support/core_ext/module/delegation.rb +75 -42
- data/lib/active_support/core_ext/module/deprecation.rb +15 -12
- data/lib/active_support/core_ext/module/introspection.rb +1 -26
- data/lib/active_support/core_ext/name_error.rb +23 -2
- data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +82 -73
- 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 +17 -1
- data/lib/active_support/core_ext/object/duplicable.rb +15 -4
- data/lib/active_support/core_ext/object/inclusion.rb +13 -5
- data/lib/active_support/core_ext/object/instance_variables.rb +22 -12
- data/lib/active_support/core_ext/object/json.rb +52 -28
- data/lib/active_support/core_ext/object/to_query.rb +2 -4
- data/lib/active_support/core_ext/object/try.rb +20 -20
- data/lib/active_support/core_ext/object/with.rb +44 -0
- data/lib/active_support/core_ext/object/with_options.rb +25 -6
- data/lib/active_support/core_ext/object.rb +1 -0
- data/lib/active_support/core_ext/pathname/blank.rb +16 -0
- data/lib/active_support/core_ext/pathname/existence.rb +23 -0
- data/lib/active_support/core_ext/pathname.rb +4 -0
- data/lib/active_support/core_ext/range/compare_range.rb +6 -25
- data/lib/active_support/core_ext/range/conversions.rb +34 -13
- data/lib/active_support/core_ext/range/each.rb +1 -1
- data/lib/active_support/core_ext/range/overlap.rb +40 -0
- data/lib/active_support/core_ext/range.rb +1 -2
- data/lib/active_support/core_ext/regexp.rb +8 -1
- data/lib/active_support/core_ext/securerandom.rb +25 -13
- 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 +21 -15
- data/lib/active_support/core_ext/string/indent.rb +1 -1
- data/lib/active_support/core_ext/string/inflections.rb +51 -10
- 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 +85 -194
- 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/thread/backtrace/location.rb +12 -0
- data/lib/active_support/core_ext/time/calculations.rb +46 -8
- data/lib/active_support/core_ext/time/conversions.rb +16 -13
- data/lib/active_support/core_ext/time/zones.rb +12 -28
- 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 +54 -22
- data/lib/active_support/deep_mergeable.rb +53 -0
- data/lib/active_support/dependencies/autoload.rb +17 -12
- 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 +77 -38
- data/lib/active_support/deprecation/constant_accessor.rb +5 -4
- data/lib/active_support/deprecation/deprecators.rb +104 -0
- data/lib/active_support/deprecation/disallowed.rb +54 -0
- data/lib/active_support/deprecation/instance_delegator.rb +31 -5
- data/lib/active_support/deprecation/method_wrappers.rb +12 -28
- data/lib/active_support/deprecation/proxy_wrappers.rb +40 -25
- data/lib/active_support/deprecation/reporting.rb +76 -16
- data/lib/active_support/deprecation.rb +36 -4
- data/lib/active_support/deprecator.rb +7 -0
- data/lib/active_support/descendants_tracker.rb +150 -68
- 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 -12
- data/lib/active_support/duration.rb +136 -56
- data/lib/active_support/encrypted_configuration.rb +72 -9
- data/lib/active_support/encrypted_file.rb +46 -13
- data/lib/active_support/environment_inquirer.rb +40 -0
- data/lib/active_support/error_reporter/test_helper.rb +15 -0
- data/lib/active_support/error_reporter.rb +203 -0
- data/lib/active_support/evented_file_update_checker.rb +86 -137
- 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 +31 -12
- data/lib/active_support/executor/test_helper.rb +7 -0
- data/lib/active_support/file_update_checker.rb +4 -2
- data/lib/active_support/fork_tracker.rb +79 -0
- data/lib/active_support/gem_version.rb +5 -5
- data/lib/active_support/gzip.rb +2 -0
- data/lib/active_support/hash_with_indifferent_access.rb +86 -42
- data/lib/active_support/html_safe_translation.rb +53 -0
- data/lib/active_support/i18n.rb +2 -1
- data/lib/active_support/i18n_railtie.rb +29 -27
- data/lib/active_support/inflector/inflections.rb +26 -9
- data/lib/active_support/inflector/methods.rb +54 -64
- data/lib/active_support/inflector/transliterate.rb +7 -5
- data/lib/active_support/isolated_execution_state.rb +76 -0
- data/lib/active_support/json/decoding.rb +6 -5
- data/lib/active_support/json/encoding.rb +31 -45
- data/lib/active_support/key_generator.rb +32 -7
- data/lib/active_support/lazy_load_hooks.rb +33 -7
- data/lib/active_support/locale/en.yml +10 -4
- data/lib/active_support/log_subscriber/test_helper.rb +2 -2
- data/lib/active_support/log_subscriber.rb +101 -32
- data/lib/active_support/logger.rb +9 -60
- data/lib/active_support/logger_silence.rb +2 -26
- data/lib/active_support/logger_thread_safe_level.rb +24 -25
- data/lib/active_support/message_encryptor.rb +205 -58
- data/lib/active_support/message_encryptors.rb +141 -0
- data/lib/active_support/message_pack/cache_serializer.rb +23 -0
- data/lib/active_support/message_pack/extensions.rb +292 -0
- data/lib/active_support/message_pack/serializer.rb +63 -0
- data/lib/active_support/message_pack.rb +50 -0
- data/lib/active_support/message_verifier.rb +237 -86
- data/lib/active_support/message_verifiers.rb +135 -0
- data/lib/active_support/messages/codec.rb +65 -0
- data/lib/active_support/messages/metadata.rb +112 -46
- data/lib/active_support/messages/rotation_configuration.rb +2 -1
- data/lib/active_support/messages/rotation_coordinator.rb +93 -0
- data/lib/active_support/messages/rotator.rb +35 -32
- data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
- data/lib/active_support/multibyte/chars.rb +15 -52
- data/lib/active_support/multibyte/unicode.rb +8 -122
- data/lib/active_support/multibyte.rb +1 -1
- data/lib/active_support/notifications/fanout.rb +310 -105
- data/lib/active_support/notifications/instrumenter.rb +113 -48
- data/lib/active_support/notifications.rb +56 -29
- data/lib/active_support/number_helper/number_converter.rb +15 -8
- 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 +5 -5
- data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -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 +379 -304
- data/lib/active_support/option_merger.rb +11 -18
- data/lib/active_support/ordered_hash.rb +4 -4
- data/lib/active_support/ordered_options.rb +23 -3
- data/lib/active_support/parameter_filter.rb +104 -75
- data/lib/active_support/proxy_object.rb +2 -0
- data/lib/active_support/rails.rb +1 -4
- data/lib/active_support/railtie.rb +90 -6
- data/lib/active_support/reloader.rb +12 -4
- data/lib/active_support/rescuable.rb +18 -16
- data/lib/active_support/ruby_features.rb +7 -0
- data/lib/active_support/secure_compare_rotator.rb +58 -0
- data/lib/active_support/security_utils.rb +19 -12
- data/lib/active_support/string_inquirer.rb +5 -3
- data/lib/active_support/subscriber.rb +23 -47
- data/lib/active_support/syntax_error_proxy.rb +70 -0
- data/lib/active_support/tagged_logging.rb +84 -23
- data/lib/active_support/test_case.rb +166 -27
- data/lib/active_support/testing/assertions.rb +73 -20
- data/lib/active_support/testing/autorun.rb +0 -2
- data/lib/active_support/testing/constant_stubbing.rb +32 -0
- data/lib/active_support/testing/deprecation.rb +53 -2
- data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
- data/lib/active_support/testing/isolation.rb +30 -29
- data/lib/active_support/testing/method_call_assertions.rb +24 -11
- 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 +81 -0
- data/lib/active_support/testing/stream.rb +4 -6
- data/lib/active_support/testing/strict_warnings.rb +39 -0
- data/lib/active_support/testing/tagged_logging.rb +1 -1
- data/lib/active_support/testing/time_helpers.rb +89 -19
- data/lib/active_support/time_with_zone.rb +105 -70
- data/lib/active_support/values/time_zone.rb +59 -26
- data/lib/active_support/version.rb +1 -1
- data/lib/active_support/xml_mini/jdom.rb +4 -11
- 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 +5 -5
- data/lib/active_support/xml_mini/nokogirisax.rb +2 -2
- data/lib/active_support/xml_mini/rexml.rb +9 -2
- data/lib/active_support/xml_mini.rb +7 -6
- data/lib/active_support.rb +40 -1
- metadata +127 -40
- 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/core_ext/range/include_time_with_zone.rb +0 -23
- data/lib/active_support/core_ext/range/overlaps.rb +0 -10
- data/lib/active_support/core_ext/uri.rb +0 -25
- data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
- data/lib/active_support/per_thread_registry.rb +0 -60
@@ -3,15 +3,16 @@
|
|
3
3
|
require "active_support/core_ext/array/conversions"
|
4
4
|
require "active_support/core_ext/module/delegation"
|
5
5
|
require "active_support/core_ext/object/acts_like"
|
6
|
-
require "active_support/core_ext/string/filters"
|
7
6
|
|
8
7
|
module ActiveSupport
|
8
|
+
# = Active Support \Duration
|
9
|
+
#
|
9
10
|
# Provides accurate date and time measurements using Date#advance and
|
10
11
|
# Time#advance, respectively. It mainly supports the methods on Numeric.
|
11
12
|
#
|
12
13
|
# 1.month.ago # equivalent to Time.now.advance(months: -1)
|
13
14
|
class Duration
|
14
|
-
class Scalar < Numeric
|
15
|
+
class Scalar < Numeric # :nodoc:
|
15
16
|
attr_reader :value
|
16
17
|
delegate :to_i, :to_f, :to_s, to: :value
|
17
18
|
|
@@ -39,11 +40,11 @@ module ActiveSupport
|
|
39
40
|
|
40
41
|
def +(other)
|
41
42
|
if Duration === other
|
42
|
-
seconds = value + other.
|
43
|
-
new_parts = other.
|
43
|
+
seconds = value + other._parts.fetch(:seconds, 0)
|
44
|
+
new_parts = other._parts.merge(seconds: seconds)
|
44
45
|
new_value = value + other.value
|
45
46
|
|
46
|
-
Duration.new(new_value, new_parts)
|
47
|
+
Duration.new(new_value, new_parts, other.variable?)
|
47
48
|
else
|
48
49
|
calculate(:+, other)
|
49
50
|
end
|
@@ -51,12 +52,12 @@ module ActiveSupport
|
|
51
52
|
|
52
53
|
def -(other)
|
53
54
|
if Duration === other
|
54
|
-
seconds = value - other.
|
55
|
-
new_parts = other.
|
55
|
+
seconds = value - other._parts.fetch(:seconds, 0)
|
56
|
+
new_parts = other._parts.transform_values(&:-@)
|
56
57
|
new_parts = new_parts.merge(seconds: seconds)
|
57
58
|
new_value = value - other.value
|
58
59
|
|
59
|
-
Duration.new(new_value, new_parts)
|
60
|
+
Duration.new(new_value, new_parts, other.variable?)
|
60
61
|
else
|
61
62
|
calculate(:-, other)
|
62
63
|
end
|
@@ -64,10 +65,10 @@ module ActiveSupport
|
|
64
65
|
|
65
66
|
def *(other)
|
66
67
|
if Duration === other
|
67
|
-
new_parts = other.
|
68
|
+
new_parts = other._parts.transform_values { |other_value| value * other_value }
|
68
69
|
new_value = value * other.value
|
69
70
|
|
70
|
-
Duration.new(new_value, new_parts)
|
71
|
+
Duration.new(new_value, new_parts, other.variable?)
|
71
72
|
else
|
72
73
|
calculate(:*, other)
|
73
74
|
end
|
@@ -89,6 +90,10 @@ module ActiveSupport
|
|
89
90
|
end
|
90
91
|
end
|
91
92
|
|
93
|
+
def variable? # :nodoc:
|
94
|
+
false
|
95
|
+
end
|
96
|
+
|
92
97
|
private
|
93
98
|
def calculate(op, other)
|
94
99
|
if Scalar === other
|
@@ -123,8 +128,9 @@ module ActiveSupport
|
|
123
128
|
}.freeze
|
124
129
|
|
125
130
|
PARTS = [:years, :months, :weeks, :days, :hours, :minutes, :seconds].freeze
|
131
|
+
VARIABLE_PARTS = [:years, :months, :weeks, :days].freeze
|
126
132
|
|
127
|
-
|
133
|
+
attr_reader :value
|
128
134
|
|
129
135
|
autoload :ISO8601Parser, "active_support/duration/iso8601_parser"
|
130
136
|
autoload :ISO8601Serializer, "active_support/duration/iso8601_serializer"
|
@@ -140,38 +146,38 @@ module ActiveSupport
|
|
140
146
|
new(calculate_total_seconds(parts), parts)
|
141
147
|
end
|
142
148
|
|
143
|
-
def ===(other)
|
149
|
+
def ===(other) # :nodoc:
|
144
150
|
other.is_a?(Duration)
|
145
151
|
rescue ::NoMethodError
|
146
152
|
false
|
147
153
|
end
|
148
154
|
|
149
|
-
def seconds(value)
|
150
|
-
new(value,
|
155
|
+
def seconds(value) # :nodoc:
|
156
|
+
new(value, { seconds: value }, false)
|
151
157
|
end
|
152
158
|
|
153
|
-
def minutes(value)
|
154
|
-
new(value * SECONDS_PER_MINUTE,
|
159
|
+
def minutes(value) # :nodoc:
|
160
|
+
new(value * SECONDS_PER_MINUTE, { minutes: value }, false)
|
155
161
|
end
|
156
162
|
|
157
|
-
def hours(value)
|
158
|
-
new(value * SECONDS_PER_HOUR,
|
163
|
+
def hours(value) # :nodoc:
|
164
|
+
new(value * SECONDS_PER_HOUR, { hours: value }, false)
|
159
165
|
end
|
160
166
|
|
161
|
-
def days(value)
|
162
|
-
new(value * SECONDS_PER_DAY,
|
167
|
+
def days(value) # :nodoc:
|
168
|
+
new(value * SECONDS_PER_DAY, { days: value }, true)
|
163
169
|
end
|
164
170
|
|
165
|
-
def weeks(value)
|
166
|
-
new(value * SECONDS_PER_WEEK,
|
171
|
+
def weeks(value) # :nodoc:
|
172
|
+
new(value * SECONDS_PER_WEEK, { weeks: value }, true)
|
167
173
|
end
|
168
174
|
|
169
|
-
def months(value)
|
170
|
-
new(value * SECONDS_PER_MONTH,
|
175
|
+
def months(value) # :nodoc:
|
176
|
+
new(value * SECONDS_PER_MONTH, { months: value }, true)
|
171
177
|
end
|
172
178
|
|
173
|
-
def years(value)
|
174
|
-
new(value * SECONDS_PER_YEAR,
|
179
|
+
def years(value) # :nodoc:
|
180
|
+
new(value * SECONDS_PER_YEAR, { years: value }, true)
|
175
181
|
end
|
176
182
|
|
177
183
|
# Creates a new Duration from a seconds value that is converted
|
@@ -181,20 +187,30 @@ module ActiveSupport
|
|
181
187
|
# ActiveSupport::Duration.build(2716146).parts # => {:months=>1, :days=>1}
|
182
188
|
#
|
183
189
|
def build(value)
|
190
|
+
unless value.is_a?(::Numeric)
|
191
|
+
raise TypeError, "can't build an #{self.name} from a #{value.class.name}"
|
192
|
+
end
|
193
|
+
|
184
194
|
parts = {}
|
185
|
-
|
195
|
+
remainder_sign = value <=> 0
|
196
|
+
remainder = value.round(9).abs
|
197
|
+
variable = false
|
186
198
|
|
187
199
|
PARTS.each do |part|
|
188
200
|
unless part == :seconds
|
189
201
|
part_in_seconds = PARTS_IN_SECONDS[part]
|
190
|
-
parts[part] = remainder.div(part_in_seconds)
|
202
|
+
parts[part] = remainder.div(part_in_seconds) * remainder_sign
|
191
203
|
remainder %= part_in_seconds
|
204
|
+
|
205
|
+
unless parts[part].zero?
|
206
|
+
variable ||= VARIABLE_PARTS.include?(part)
|
207
|
+
end
|
192
208
|
end
|
193
209
|
end unless value == 0
|
194
210
|
|
195
|
-
parts[:seconds] = remainder
|
211
|
+
parts[:seconds] = remainder * remainder_sign
|
196
212
|
|
197
|
-
new(value, parts)
|
213
|
+
new(value, parts, variable)
|
198
214
|
end
|
199
215
|
|
200
216
|
private
|
@@ -205,13 +221,23 @@ module ActiveSupport
|
|
205
221
|
end
|
206
222
|
end
|
207
223
|
|
208
|
-
def initialize(value, parts)
|
209
|
-
@value, @parts = value, parts
|
210
|
-
@parts.default = 0
|
224
|
+
def initialize(value, parts, variable = nil) # :nodoc:
|
225
|
+
@value, @parts = value, parts
|
211
226
|
@parts.reject! { |k, v| v.zero? } unless value == 0
|
227
|
+
@parts.freeze
|
228
|
+
@variable = variable
|
229
|
+
|
230
|
+
if @variable.nil?
|
231
|
+
@variable = @parts.any? { |part, _| VARIABLE_PARTS.include?(part) }
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# Returns a copy of the parts hash that defines the duration
|
236
|
+
def parts
|
237
|
+
@parts.dup
|
212
238
|
end
|
213
239
|
|
214
|
-
def coerce(other)
|
240
|
+
def coerce(other) # :nodoc:
|
215
241
|
case other
|
216
242
|
when Scalar
|
217
243
|
[other, self]
|
@@ -236,14 +262,13 @@ module ActiveSupport
|
|
236
262
|
# are treated as seconds.
|
237
263
|
def +(other)
|
238
264
|
if Duration === other
|
239
|
-
parts = @parts.
|
240
|
-
|
241
|
-
parts[key] += value
|
265
|
+
parts = @parts.merge(other._parts) do |_key, value, other_value|
|
266
|
+
value + other_value
|
242
267
|
end
|
243
|
-
Duration.new(value + other.value, parts)
|
268
|
+
Duration.new(value + other.value, parts, @variable || other.variable?)
|
244
269
|
else
|
245
|
-
seconds = @parts
|
246
|
-
Duration.new(value + other, @parts.merge(seconds: seconds))
|
270
|
+
seconds = @parts.fetch(:seconds, 0) + other
|
271
|
+
Duration.new(value + other, @parts.merge(seconds: seconds), @variable)
|
247
272
|
end
|
248
273
|
end
|
249
274
|
|
@@ -256,9 +281,9 @@ module ActiveSupport
|
|
256
281
|
# Multiplies this Duration by a Numeric and returns a new Duration.
|
257
282
|
def *(other)
|
258
283
|
if Scalar === other || Duration === other
|
259
|
-
Duration.new(value * other.value, parts.
|
284
|
+
Duration.new(value * other.value, @parts.transform_values { |number| number * other.value }, @variable || other.variable?)
|
260
285
|
elsif Numeric === other
|
261
|
-
Duration.new(value * other, parts.
|
286
|
+
Duration.new(value * other, @parts.transform_values { |number| number * other }, @variable)
|
262
287
|
else
|
263
288
|
raise_type_error(other)
|
264
289
|
end
|
@@ -267,11 +292,11 @@ module ActiveSupport
|
|
267
292
|
# Divides this Duration by a Numeric and returns a new Duration.
|
268
293
|
def /(other)
|
269
294
|
if Scalar === other
|
270
|
-
Duration.new(value / other.value, parts.
|
295
|
+
Duration.new(value / other.value, @parts.transform_values { |number| number / other.value }, @variable)
|
271
296
|
elsif Duration === other
|
272
297
|
value / other.value
|
273
298
|
elsif Numeric === other
|
274
|
-
Duration.new(value / other, parts.
|
299
|
+
Duration.new(value / other, @parts.transform_values { |number| number / other }, @variable)
|
275
300
|
else
|
276
301
|
raise_type_error(other)
|
277
302
|
end
|
@@ -289,11 +314,15 @@ module ActiveSupport
|
|
289
314
|
end
|
290
315
|
end
|
291
316
|
|
292
|
-
def -@
|
293
|
-
Duration.new(-value, parts.
|
317
|
+
def -@ # :nodoc:
|
318
|
+
Duration.new(-value, @parts.transform_values(&:-@), @variable)
|
319
|
+
end
|
320
|
+
|
321
|
+
def +@ # :nodoc:
|
322
|
+
self
|
294
323
|
end
|
295
324
|
|
296
|
-
def is_a?(klass)
|
325
|
+
def is_a?(klass) # :nodoc:
|
297
326
|
Duration == klass || value.is_a?(klass)
|
298
327
|
end
|
299
328
|
alias :kind_of? :is_a?
|
@@ -343,6 +372,49 @@ module ActiveSupport
|
|
343
372
|
def to_i
|
344
373
|
@value.to_i
|
345
374
|
end
|
375
|
+
alias :in_seconds :to_i
|
376
|
+
|
377
|
+
# Returns the amount of minutes a duration covers as a float
|
378
|
+
#
|
379
|
+
# 1.day.in_minutes # => 1440.0
|
380
|
+
def in_minutes
|
381
|
+
in_seconds / SECONDS_PER_MINUTE.to_f
|
382
|
+
end
|
383
|
+
|
384
|
+
# Returns the amount of hours a duration covers as a float
|
385
|
+
#
|
386
|
+
# 1.day.in_hours # => 24.0
|
387
|
+
def in_hours
|
388
|
+
in_seconds / SECONDS_PER_HOUR.to_f
|
389
|
+
end
|
390
|
+
|
391
|
+
# Returns the amount of days a duration covers as a float
|
392
|
+
#
|
393
|
+
# 12.hours.in_days # => 0.5
|
394
|
+
def in_days
|
395
|
+
in_seconds / SECONDS_PER_DAY.to_f
|
396
|
+
end
|
397
|
+
|
398
|
+
# Returns the amount of weeks a duration covers as a float
|
399
|
+
#
|
400
|
+
# 2.months.in_weeks # => 8.696
|
401
|
+
def in_weeks
|
402
|
+
in_seconds / SECONDS_PER_WEEK.to_f
|
403
|
+
end
|
404
|
+
|
405
|
+
# Returns the amount of months a duration covers as a float
|
406
|
+
#
|
407
|
+
# 9.weeks.in_months # => 2.07
|
408
|
+
def in_months
|
409
|
+
in_seconds / SECONDS_PER_MONTH.to_f
|
410
|
+
end
|
411
|
+
|
412
|
+
# Returns the amount of years a duration covers as a float
|
413
|
+
#
|
414
|
+
# 30.days.in_years # => 0.082
|
415
|
+
def in_years
|
416
|
+
in_seconds / SECONDS_PER_YEAR.to_f
|
417
|
+
end
|
346
418
|
|
347
419
|
# Returns +true+ if +other+ is also a Duration instance, which has the
|
348
420
|
# same parts as this one.
|
@@ -370,24 +442,24 @@ module ActiveSupport
|
|
370
442
|
alias :until :ago
|
371
443
|
alias :before :ago
|
372
444
|
|
373
|
-
def inspect
|
374
|
-
return "#{value} seconds" if parts.empty?
|
445
|
+
def inspect # :nodoc:
|
446
|
+
return "#{value} seconds" if @parts.empty?
|
375
447
|
|
376
|
-
parts.
|
448
|
+
@parts.
|
377
449
|
sort_by { |unit, _ | PARTS.index(unit) }.
|
378
450
|
map { |unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}" }.
|
379
|
-
to_sentence(locale:
|
451
|
+
to_sentence(locale: false)
|
380
452
|
end
|
381
453
|
|
382
|
-
def as_json(options = nil)
|
454
|
+
def as_json(options = nil) # :nodoc:
|
383
455
|
to_i
|
384
456
|
end
|
385
457
|
|
386
|
-
def init_with(coder)
|
458
|
+
def init_with(coder) # :nodoc:
|
387
459
|
initialize(coder["value"], coder["parts"])
|
388
460
|
end
|
389
461
|
|
390
|
-
def encode_with(coder)
|
462
|
+
def encode_with(coder) # :nodoc:
|
391
463
|
coder.map = { "value" => @value, "parts" => @parts }
|
392
464
|
end
|
393
465
|
|
@@ -397,16 +469,24 @@ module ActiveSupport
|
|
397
469
|
ISO8601Serializer.new(self, precision: precision).serialize
|
398
470
|
end
|
399
471
|
|
472
|
+
def variable? # :nodoc:
|
473
|
+
@variable
|
474
|
+
end
|
475
|
+
|
476
|
+
def _parts # :nodoc:
|
477
|
+
@parts
|
478
|
+
end
|
479
|
+
|
400
480
|
private
|
401
481
|
def sum(sign, time = ::Time.current)
|
402
482
|
unless time.acts_like?(:time) || time.acts_like?(:date)
|
403
483
|
raise ::ArgumentError, "expected a time or date, got #{time.inspect}"
|
404
484
|
end
|
405
485
|
|
406
|
-
if parts.empty?
|
486
|
+
if @parts.empty?
|
407
487
|
time.since(sign * value)
|
408
488
|
else
|
409
|
-
parts.inject(time) do |t, (type, number)|
|
489
|
+
@parts.inject(time) do |t, (type, number)|
|
410
490
|
if type == :seconds
|
411
491
|
t.since(sign * number)
|
412
492
|
elsif type == :minutes
|
@@ -4,42 +4,105 @@ require "yaml"
|
|
4
4
|
require "active_support/encrypted_file"
|
5
5
|
require "active_support/ordered_options"
|
6
6
|
require "active_support/core_ext/object/inclusion"
|
7
|
+
require "active_support/core_ext/hash/keys"
|
7
8
|
require "active_support/core_ext/module/delegation"
|
8
9
|
|
9
10
|
module ActiveSupport
|
11
|
+
# = Encrypted Configuration
|
12
|
+
#
|
13
|
+
# Provides convenience methods on top of EncryptedFile to access values stored
|
14
|
+
# as encrypted YAML.
|
15
|
+
#
|
16
|
+
# Values can be accessed via +Hash+ methods, such as +fetch+ and +dig+, or via
|
17
|
+
# dynamic accessor methods, similar to OrderedOptions.
|
18
|
+
#
|
19
|
+
# my_config = ActiveSupport::EncryptedConfiguration.new(...)
|
20
|
+
# my_config.read # => "some_secret: 123\nsome_namespace:\n another_secret: 456"
|
21
|
+
#
|
22
|
+
# my_config[:some_secret]
|
23
|
+
# # => 123
|
24
|
+
# my_config.some_secret
|
25
|
+
# # => 123
|
26
|
+
# my_config.dig(:some_namespace, :another_secret)
|
27
|
+
# # => 456
|
28
|
+
# my_config.some_namespace.another_secret
|
29
|
+
# # => 456
|
30
|
+
# my_config.fetch(:foo)
|
31
|
+
# # => KeyError
|
32
|
+
# my_config.foo!
|
33
|
+
# # => KeyError
|
34
|
+
#
|
10
35
|
class EncryptedConfiguration < EncryptedFile
|
11
|
-
|
36
|
+
class InvalidContentError < RuntimeError
|
37
|
+
def initialize(content_path)
|
38
|
+
super "Invalid YAML in '#{content_path}'."
|
39
|
+
end
|
40
|
+
|
41
|
+
def message
|
42
|
+
cause.is_a?(Psych::SyntaxError) ? "#{super}\n\n #{cause.message}" : super
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
12
46
|
delegate_missing_to :options
|
13
47
|
|
14
48
|
def initialize(config_path:, key_path:, env_key:, raise_if_missing_key:)
|
15
49
|
super content_path: config_path, key_path: key_path,
|
16
50
|
env_key: env_key, raise_if_missing_key: raise_if_missing_key
|
51
|
+
@config = nil
|
52
|
+
@options = nil
|
17
53
|
end
|
18
54
|
|
19
|
-
#
|
55
|
+
# Reads the file and returns the decrypted content. See EncryptedFile#read.
|
20
56
|
def read
|
21
57
|
super
|
22
58
|
rescue ActiveSupport::EncryptedFile::MissingContentError
|
59
|
+
# Allow a config to be started without a file present
|
23
60
|
""
|
24
61
|
end
|
25
62
|
|
26
|
-
def
|
27
|
-
deserialize(
|
28
|
-
|
29
|
-
super
|
63
|
+
def validate! # :nodoc:
|
64
|
+
deserialize(read)
|
30
65
|
end
|
31
66
|
|
67
|
+
# Returns the decrypted content as a Hash with symbolized keys.
|
68
|
+
#
|
69
|
+
# my_config = ActiveSupport::EncryptedConfiguration.new(...)
|
70
|
+
# my_config.read # => "some_secret: 123\nsome_namespace:\n another_secret: 456"
|
71
|
+
#
|
72
|
+
# my_config.config
|
73
|
+
# # => { some_secret: 123, some_namespace: { another_secret: 789 } }
|
74
|
+
#
|
32
75
|
def config
|
33
76
|
@config ||= deserialize(read).deep_symbolize_keys
|
34
77
|
end
|
35
78
|
|
79
|
+
def inspect # :nodoc:
|
80
|
+
"#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>"
|
81
|
+
end
|
82
|
+
|
36
83
|
private
|
84
|
+
def deep_transform(hash)
|
85
|
+
return hash unless hash.is_a?(Hash)
|
86
|
+
|
87
|
+
h = ActiveSupport::OrderedOptions.new
|
88
|
+
hash.each do |k, v|
|
89
|
+
h[k] = deep_transform(v)
|
90
|
+
end
|
91
|
+
h
|
92
|
+
end
|
93
|
+
|
37
94
|
def options
|
38
|
-
@options ||=
|
95
|
+
@options ||= deep_transform(config)
|
39
96
|
end
|
40
97
|
|
41
|
-
def deserialize(
|
42
|
-
YAML.
|
98
|
+
def deserialize(content)
|
99
|
+
config = YAML.respond_to?(:unsafe_load) ?
|
100
|
+
YAML.unsafe_load(content, filename: content_path) :
|
101
|
+
YAML.load(content, filename: content_path)
|
102
|
+
|
103
|
+
config.presence || {}
|
104
|
+
rescue Psych::SyntaxError
|
105
|
+
raise InvalidContentError.new(content_path)
|
43
106
|
end
|
44
107
|
end
|
45
108
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "pathname"
|
4
|
-
require "
|
4
|
+
require "tempfile"
|
5
5
|
require "active_support/message_encryptor"
|
6
6
|
|
7
7
|
module ActiveSupport
|
@@ -20,24 +20,53 @@ 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
|
+
# Returns truthy if #key is truthy. Returns falsy otherwise. Unlike #key,
|
57
|
+
# does not raise MissingKeyError when +raise_if_missing_key+ is true.
|
58
|
+
def key?
|
59
|
+
read_env_key || read_key_file
|
60
|
+
end
|
61
|
+
|
62
|
+
# Reads the file and returns the decrypted content.
|
63
|
+
#
|
64
|
+
# Raises:
|
65
|
+
# - MissingKeyError if the key is missing and +raise_if_missing_key+ is true.
|
66
|
+
# - MissingContentError if the encrypted file does not exist or otherwise
|
67
|
+
# if the key is missing.
|
68
|
+
# - ActiveSupport::MessageEncryptor::InvalidMessage if the content cannot be
|
69
|
+
# decrypted or verified.
|
41
70
|
def read
|
42
71
|
if !key.nil? && content_path.exist?
|
43
72
|
decrypt content_path.binread
|
@@ -58,21 +87,21 @@ module ActiveSupport
|
|
58
87
|
|
59
88
|
private
|
60
89
|
def writing(contents)
|
61
|
-
|
62
|
-
|
63
|
-
|
90
|
+
Tempfile.create(["", "-" + content_path.basename.to_s.chomp(".enc")]) do |tmp_file|
|
91
|
+
tmp_path = Pathname.new(tmp_file)
|
92
|
+
tmp_path.binwrite contents
|
64
93
|
|
65
|
-
|
94
|
+
yield tmp_path
|
66
95
|
|
67
|
-
|
96
|
+
updated_contents = tmp_path.binread
|
68
97
|
|
69
|
-
|
70
|
-
|
71
|
-
FileUtils.rm(tmp_path) if tmp_path&.exist?
|
98
|
+
write(updated_contents) if updated_contents != contents
|
99
|
+
end
|
72
100
|
end
|
73
101
|
|
74
102
|
|
75
103
|
def encrypt(contents)
|
104
|
+
check_key_length
|
76
105
|
encryptor.encrypt_and_sign contents
|
77
106
|
end
|
78
107
|
|
@@ -81,20 +110,24 @@ module ActiveSupport
|
|
81
110
|
end
|
82
111
|
|
83
112
|
def encryptor
|
84
|
-
@encryptor ||= ActiveSupport::MessageEncryptor.new([ key ].pack("H*"), cipher: CIPHER)
|
113
|
+
@encryptor ||= ActiveSupport::MessageEncryptor.new([ key ].pack("H*"), cipher: CIPHER, serializer: Marshal)
|
85
114
|
end
|
86
115
|
|
87
116
|
|
88
117
|
def read_env_key
|
89
|
-
ENV[env_key]
|
118
|
+
ENV[env_key].presence
|
90
119
|
end
|
91
120
|
|
92
121
|
def read_key_file
|
93
|
-
key_path.binread.strip if key_path.exist?
|
122
|
+
@key_file_contents ||= (key_path.binread.strip if key_path.exist?)
|
94
123
|
end
|
95
124
|
|
96
125
|
def handle_missing_key
|
97
126
|
raise MissingKeyError.new(key_path: key_path, env_key: env_key) if raise_if_missing_key
|
98
127
|
end
|
128
|
+
|
129
|
+
def check_key_length
|
130
|
+
raise InvalidKeyLengthError if key&.length != self.class.expected_key_length
|
131
|
+
end
|
99
132
|
end
|
100
133
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/string_inquirer"
|
4
|
+
require "active_support/core_ext/object/inclusion"
|
5
|
+
|
6
|
+
module ActiveSupport
|
7
|
+
class EnvironmentInquirer < StringInquirer # :nodoc:
|
8
|
+
# Optimization for the three default environments, so this inquirer doesn't need to rely on
|
9
|
+
# the slower delegation through method_missing that StringInquirer would normally entail.
|
10
|
+
DEFAULT_ENVIRONMENTS = %w[ development test production ]
|
11
|
+
|
12
|
+
# Environments that'll respond true for #local?
|
13
|
+
LOCAL_ENVIRONMENTS = %w[ development test ]
|
14
|
+
|
15
|
+
def initialize(env)
|
16
|
+
raise(ArgumentError, "'local' is a reserved environment name") if env == "local"
|
17
|
+
|
18
|
+
super(env)
|
19
|
+
|
20
|
+
DEFAULT_ENVIRONMENTS.each do |default|
|
21
|
+
instance_variable_set :"@#{default}", env == default
|
22
|
+
end
|
23
|
+
|
24
|
+
@local = in? LOCAL_ENVIRONMENTS
|
25
|
+
end
|
26
|
+
|
27
|
+
DEFAULT_ENVIRONMENTS.each do |env|
|
28
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
29
|
+
def #{env}?
|
30
|
+
@#{env}
|
31
|
+
end
|
32
|
+
RUBY
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns true if we're in the development or test environment.
|
36
|
+
def local?
|
37
|
+
@local
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveSupport::ErrorReporter::TestHelper # :nodoc:
|
4
|
+
class ErrorSubscriber
|
5
|
+
attr_reader :events
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@events = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def report(error, handled:, severity:, source:, context:)
|
12
|
+
@events << [error, handled, severity, source, context]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|