activesupport 7.1.6 → 8.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +256 -1133
- data/README.rdoc +1 -1
- data/lib/active_support/array_inquirer.rb +1 -1
- data/lib/active_support/backtrace_cleaner.rb +81 -3
- data/lib/active_support/benchmark.rb +21 -0
- data/lib/active_support/benchmarkable.rb +3 -2
- data/lib/active_support/broadcast_logger.rb +65 -78
- data/lib/active_support/cache/file_store.rb +29 -14
- data/lib/active_support/cache/mem_cache_store.rb +42 -102
- data/lib/active_support/cache/memory_store.rb +11 -6
- data/lib/active_support/cache/null_store.rb +2 -2
- data/lib/active_support/cache/redis_cache_store.rb +58 -46
- data/lib/active_support/cache/serializer_with_fallback.rb +0 -23
- data/lib/active_support/cache/strategy/local_cache.rb +72 -27
- data/lib/active_support/cache/strategy/local_cache_middleware.rb +7 -7
- data/lib/active_support/cache.rb +146 -86
- data/lib/active_support/callbacks.rb +102 -126
- data/lib/active_support/class_attribute.rb +33 -0
- data/lib/active_support/code_generator.rb +9 -0
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +8 -62
- data/lib/active_support/concurrency/share_lock.rb +0 -1
- data/lib/active_support/concurrency/thread_monitor.rb +55 -0
- data/lib/active_support/configurable.rb +34 -0
- data/lib/active_support/configuration_file.rb +15 -6
- data/lib/active_support/continuous_integration.rb +145 -0
- data/lib/active_support/core_ext/array/conversions.rb +3 -5
- data/lib/active_support/core_ext/array.rb +7 -7
- data/lib/active_support/core_ext/benchmark.rb +4 -14
- data/lib/active_support/core_ext/big_decimal.rb +1 -1
- data/lib/active_support/core_ext/class/attribute.rb +26 -19
- data/lib/active_support/core_ext/class/subclasses.rb +15 -35
- data/lib/active_support/core_ext/class.rb +2 -2
- data/lib/active_support/core_ext/date/blank.rb +4 -0
- data/lib/active_support/core_ext/date/conversions.rb +2 -2
- data/lib/active_support/core_ext/date.rb +5 -5
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +1 -9
- data/lib/active_support/core_ext/date_time/blank.rb +4 -0
- data/lib/active_support/core_ext/date_time/compatibility.rb +3 -5
- data/lib/active_support/core_ext/date_time/conversions.rb +4 -6
- data/lib/active_support/core_ext/date_time.rb +5 -5
- data/lib/active_support/core_ext/digest/uuid.rb +6 -0
- data/lib/active_support/core_ext/digest.rb +1 -1
- data/lib/active_support/core_ext/enumerable.rb +25 -8
- data/lib/active_support/core_ext/erb/util.rb +10 -5
- data/lib/active_support/core_ext/file.rb +1 -1
- data/lib/active_support/core_ext/hash/deep_merge.rb +1 -0
- data/lib/active_support/core_ext/hash/except.rb +0 -12
- data/lib/active_support/core_ext/hash/keys.rb +4 -4
- data/lib/active_support/core_ext/hash.rb +8 -8
- data/lib/active_support/core_ext/integer.rb +3 -3
- data/lib/active_support/core_ext/kernel.rb +3 -3
- data/lib/active_support/core_ext/module/attr_internal.rb +16 -6
- data/lib/active_support/core_ext/module/delegation.rb +20 -163
- data/lib/active_support/core_ext/module/deprecation.rb +1 -4
- data/lib/active_support/core_ext/module/introspection.rb +3 -0
- data/lib/active_support/core_ext/module.rb +11 -11
- data/lib/active_support/core_ext/numeric/conversions.rb +3 -3
- data/lib/active_support/core_ext/numeric.rb +3 -3
- data/lib/active_support/core_ext/object/blank.rb +45 -1
- data/lib/active_support/core_ext/object/instance_variables.rb +11 -19
- data/lib/active_support/core_ext/object/json.rb +24 -11
- data/lib/active_support/core_ext/object/to_query.rb +7 -1
- data/lib/active_support/core_ext/object/try.rb +2 -2
- data/lib/active_support/core_ext/object/with.rb +5 -3
- data/lib/active_support/core_ext/object.rb +13 -13
- data/lib/active_support/core_ext/pathname/blank.rb +4 -0
- data/lib/active_support/core_ext/pathname.rb +2 -2
- data/lib/active_support/core_ext/range/overlap.rb +4 -4
- data/lib/active_support/core_ext/range/sole.rb +17 -0
- data/lib/active_support/core_ext/range.rb +4 -4
- data/lib/active_support/core_ext/securerandom.rb +4 -4
- data/lib/active_support/core_ext/string/conversions.rb +1 -1
- data/lib/active_support/core_ext/string/filters.rb +4 -4
- data/lib/active_support/core_ext/string/multibyte.rb +13 -4
- data/lib/active_support/core_ext/string/output_safety.rb +19 -19
- data/lib/active_support/core_ext/string.rb +13 -13
- data/lib/active_support/core_ext/symbol.rb +1 -1
- data/lib/active_support/core_ext/thread/backtrace/location.rb +2 -7
- data/lib/active_support/core_ext/time/calculations.rb +25 -30
- data/lib/active_support/core_ext/time/compatibility.rb +2 -3
- data/lib/active_support/core_ext/time/conversions.rb +2 -2
- data/lib/active_support/core_ext/time/zones.rb +1 -1
- data/lib/active_support/core_ext/time.rb +5 -5
- data/lib/active_support/core_ext.rb +1 -2
- data/lib/active_support/current_attributes/test_helper.rb +2 -2
- data/lib/active_support/current_attributes.rb +58 -50
- data/lib/active_support/delegation.rb +200 -0
- data/lib/active_support/dependencies/autoload.rb +0 -12
- data/lib/active_support/dependencies/interlock.rb +11 -5
- data/lib/active_support/dependencies.rb +6 -2
- data/lib/active_support/deprecation/constant_accessor.rb +47 -26
- data/lib/active_support/deprecation/proxy_wrappers.rb +9 -12
- data/lib/active_support/deprecation/reporting.rb +5 -17
- data/lib/active_support/deprecation.rb +8 -5
- data/lib/active_support/descendants_tracker.rb +9 -87
- data/lib/active_support/duration/iso8601_parser.rb +2 -2
- data/lib/active_support/duration/iso8601_serializer.rb +1 -2
- data/lib/active_support/duration.rb +25 -16
- data/lib/active_support/editor.rb +70 -0
- data/lib/active_support/encrypted_configuration.rb +20 -2
- data/lib/active_support/encrypted_file.rb +1 -1
- data/lib/active_support/error_reporter.rb +121 -6
- data/lib/active_support/event_reporter/test_helper.rb +32 -0
- data/lib/active_support/event_reporter.rb +592 -0
- data/lib/active_support/evented_file_update_checker.rb +5 -3
- data/lib/active_support/execution_context.rb +64 -7
- data/lib/active_support/execution_wrapper.rb +1 -2
- data/lib/active_support/file_update_checker.rb +9 -7
- data/lib/active_support/fork_tracker.rb +2 -38
- data/lib/active_support/gem_version.rb +2 -2
- data/lib/active_support/gzip.rb +1 -0
- data/lib/active_support/hash_with_indifferent_access.rb +66 -45
- data/lib/active_support/html_safe_translation.rb +3 -0
- data/lib/active_support/i18n_railtie.rb +19 -11
- data/lib/active_support/inflector/inflections.rb +31 -15
- data/lib/active_support/inflector/transliterate.rb +6 -8
- data/lib/active_support/isolated_execution_state.rb +12 -17
- data/lib/active_support/json/decoding.rb +6 -4
- data/lib/active_support/json/encoding.rb +157 -21
- data/lib/active_support/lazy_load_hooks.rb +1 -1
- data/lib/active_support/log_subscriber.rb +2 -18
- data/lib/active_support/logger.rb +15 -2
- data/lib/active_support/logger_thread_safe_level.rb +4 -9
- data/lib/active_support/message_encryptors.rb +54 -2
- data/lib/active_support/message_pack/extensions.rb +20 -2
- data/lib/active_support/message_verifier.rb +21 -0
- data/lib/active_support/message_verifiers.rb +57 -3
- data/lib/active_support/messages/rotation_coordinator.rb +9 -0
- data/lib/active_support/messages/rotator.rb +10 -0
- data/lib/active_support/multibyte/chars.rb +14 -4
- data/lib/active_support/multibyte.rb +4 -0
- data/lib/active_support/notifications/fanout.rb +68 -50
- data/lib/active_support/notifications/instrumenter.rb +22 -19
- data/lib/active_support/notifications.rb +28 -27
- data/lib/active_support/number_helper/number_converter.rb +2 -2
- data/lib/active_support/number_helper.rb +22 -0
- data/lib/active_support/option_merger.rb +2 -2
- data/lib/active_support/ordered_options.rb +53 -15
- data/lib/active_support/railtie.rb +36 -20
- data/lib/active_support/string_inquirer.rb +1 -1
- data/lib/active_support/structured_event_subscriber.rb +99 -0
- data/lib/active_support/subscriber.rb +1 -5
- data/lib/active_support/syntax_error_proxy.rb +3 -0
- data/lib/active_support/tagged_logging.rb +5 -1
- data/lib/active_support/test_case.rb +63 -6
- data/lib/active_support/testing/assertions.rb +113 -27
- data/lib/active_support/testing/constant_stubbing.rb +30 -8
- data/lib/active_support/testing/deprecation.rb +5 -12
- data/lib/active_support/testing/error_reporter_assertions.rb +18 -1
- data/lib/active_support/testing/event_reporter_assertions.rb +227 -0
- data/lib/active_support/testing/isolation.rb +19 -9
- data/lib/active_support/testing/method_call_assertions.rb +2 -16
- data/lib/active_support/testing/notification_assertions.rb +92 -0
- data/lib/active_support/testing/parallelization/server.rb +18 -2
- data/lib/active_support/testing/parallelization/worker.rb +4 -2
- data/lib/active_support/testing/parallelization.rb +25 -1
- data/lib/active_support/testing/tests_without_assertions.rb +19 -0
- data/lib/active_support/testing/time_helpers.rb +11 -6
- data/lib/active_support/time_with_zone.rb +39 -26
- data/lib/active_support/values/time_zone.rb +26 -17
- data/lib/active_support/xml_mini.rb +14 -4
- data/lib/active_support.rb +22 -9
- metadata +31 -17
- data/lib/active_support/core_ext/range/each.rb +0 -24
- data/lib/active_support/deprecation/instance_delegator.rb +0 -65
- data/lib/active_support/proxy_object.rb +0 -17
- data/lib/active_support/ruby_features.rb +0 -7
- data/lib/active_support/testing/strict_warnings.rb +0 -39
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "singleton"
|
|
4
|
-
|
|
5
3
|
module ActiveSupport
|
|
6
4
|
# = Active Support \Deprecation
|
|
7
5
|
#
|
|
@@ -41,7 +39,6 @@ module ActiveSupport
|
|
|
41
39
|
# a circular require warning for active_support/deprecation.rb.
|
|
42
40
|
#
|
|
43
41
|
# So, we define the constant first, and load dependencies later.
|
|
44
|
-
require "active_support/deprecation/instance_delegator"
|
|
45
42
|
require "active_support/deprecation/behaviors"
|
|
46
43
|
require "active_support/deprecation/reporting"
|
|
47
44
|
require "active_support/deprecation/disallowed"
|
|
@@ -52,12 +49,18 @@ module ActiveSupport
|
|
|
52
49
|
require "active_support/core_ext/module/deprecation"
|
|
53
50
|
require "concurrent/atomic/thread_local_var"
|
|
54
51
|
|
|
55
|
-
include InstanceDelegator
|
|
56
52
|
include Behavior
|
|
57
53
|
include Reporting
|
|
58
54
|
include Disallowed
|
|
59
55
|
include MethodWrapper
|
|
60
56
|
|
|
57
|
+
MUTEX = Mutex.new # :nodoc:
|
|
58
|
+
private_constant :MUTEX
|
|
59
|
+
|
|
60
|
+
def self._instance # :nodoc:
|
|
61
|
+
@_instance ||= MUTEX.synchronize { @_instance ||= new }
|
|
62
|
+
end
|
|
63
|
+
|
|
61
64
|
# The version number in which the deprecated behavior will be removed, by default.
|
|
62
65
|
attr_accessor :deprecation_horizon
|
|
63
66
|
|
|
@@ -65,7 +68,7 @@ module ActiveSupport
|
|
|
65
68
|
# and the second is a library name.
|
|
66
69
|
#
|
|
67
70
|
# ActiveSupport::Deprecation.new('2.0', 'MyLibrary')
|
|
68
|
-
def initialize(deprecation_horizon = "
|
|
71
|
+
def initialize(deprecation_horizon = "8.2", gem_name = "Rails")
|
|
69
72
|
self.gem_name = gem_name
|
|
70
73
|
self.deprecation_horizon = deprecation_horizon
|
|
71
74
|
# By default, warnings are not silenced and debugging is off.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "weakref"
|
|
4
|
-
require "active_support/ruby_features"
|
|
5
4
|
|
|
6
5
|
module ActiveSupport
|
|
7
6
|
# = Active Support Descendants Tracker
|
|
@@ -95,96 +94,19 @@ module ActiveSupport
|
|
|
95
94
|
end
|
|
96
95
|
end
|
|
97
96
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
klass.subclasses
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def descendants(klass)
|
|
105
|
-
klass.descendants
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def descendants
|
|
110
|
-
subclasses = DescendantsTracker.reject!(self.subclasses)
|
|
111
|
-
subclasses.concat(subclasses.flat_map(&:descendants))
|
|
112
|
-
end
|
|
113
|
-
else
|
|
114
|
-
# DescendantsArray is an array that contains weak references to classes.
|
|
115
|
-
# Note: DescendantsArray is redundant with WeakSet, however WeakSet when used
|
|
116
|
-
# on Ruby 2.7 or 3.0 can trigger a Ruby crash: https://bugs.ruby-lang.org/issues/18928
|
|
117
|
-
class DescendantsArray # :nodoc:
|
|
118
|
-
include Enumerable
|
|
119
|
-
|
|
120
|
-
def initialize
|
|
121
|
-
@refs = []
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def <<(klass)
|
|
125
|
-
@refs << WeakRef.new(klass)
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
def each
|
|
129
|
-
@refs.reject! do |ref|
|
|
130
|
-
yield ref.__getobj__
|
|
131
|
-
false
|
|
132
|
-
rescue WeakRef::RefError
|
|
133
|
-
true
|
|
134
|
-
end
|
|
135
|
-
self
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
def refs_size
|
|
139
|
-
@refs.size
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def cleanup!
|
|
143
|
-
@refs.delete_if { |ref| !ref.weakref_alive? }
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def reject!
|
|
147
|
-
@refs.reject! do |ref|
|
|
148
|
-
yield ref.__getobj__
|
|
149
|
-
rescue WeakRef::RefError
|
|
150
|
-
true
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
@direct_descendants = {}
|
|
156
|
-
|
|
157
|
-
class << self
|
|
158
|
-
def subclasses(klass)
|
|
159
|
-
descendants = @direct_descendants[klass]
|
|
160
|
-
descendants ? DescendantsTracker.reject!(descendants.to_a) : []
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
def descendants(klass)
|
|
164
|
-
subclasses = self.subclasses(klass)
|
|
165
|
-
subclasses.concat(subclasses.flat_map { |k| descendants(k) })
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
# This is the only method that is not thread safe, but is only ever called
|
|
169
|
-
# during the eager loading phase.
|
|
170
|
-
def store_inherited(klass, descendant) # :nodoc:
|
|
171
|
-
(@direct_descendants[klass] ||= DescendantsArray.new) << descendant
|
|
172
|
-
end
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
def subclasses
|
|
176
|
-
DescendantsTracker.subclasses(self)
|
|
97
|
+
class << self
|
|
98
|
+
def subclasses(klass)
|
|
99
|
+
klass.subclasses
|
|
177
100
|
end
|
|
178
101
|
|
|
179
|
-
def descendants
|
|
180
|
-
|
|
102
|
+
def descendants(klass)
|
|
103
|
+
klass.descendants
|
|
181
104
|
end
|
|
105
|
+
end
|
|
182
106
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
super
|
|
187
|
-
end
|
|
107
|
+
def descendants
|
|
108
|
+
subclasses = DescendantsTracker.reject!(self.subclasses)
|
|
109
|
+
subclasses.concat(subclasses.flat_map(&:descendants))
|
|
188
110
|
end
|
|
189
111
|
end
|
|
190
112
|
end
|
|
@@ -102,12 +102,12 @@ module ActiveSupport
|
|
|
102
102
|
raise_parsing_error("is empty duration") if parts.empty?
|
|
103
103
|
|
|
104
104
|
# Mixing any of Y, M, D with W is invalid.
|
|
105
|
-
if parts.key?(:weeks) &&
|
|
105
|
+
if parts.key?(:weeks) && parts.keys.intersect?(DATE_COMPONENTS)
|
|
106
106
|
raise_parsing_error("mixing weeks with other date parts not allowed")
|
|
107
107
|
end
|
|
108
108
|
|
|
109
109
|
# Specifying an empty T part is invalid.
|
|
110
|
-
if mode == :time &&
|
|
110
|
+
if mode == :time && !parts.keys.intersect?(TIME_COMPONENTS)
|
|
111
111
|
raise_parsing_error("time part marker is present but time part is empty")
|
|
112
112
|
end
|
|
113
113
|
|
|
@@ -35,7 +35,6 @@ module ActiveSupport
|
|
|
35
35
|
# Return pair of duration's parts and whole duration sign.
|
|
36
36
|
# Parts are summarized (as they can become repetitive due to addition, etc).
|
|
37
37
|
# Zero parts are removed as not significant.
|
|
38
|
-
# If all parts are negative it will negate all of them and return minus as a sign.
|
|
39
38
|
def normalize
|
|
40
39
|
parts = @duration.parts.each_with_object(Hash.new(0)) do |(k, v), p|
|
|
41
40
|
p[k] += v unless v.zero?
|
|
@@ -50,7 +49,7 @@ module ActiveSupport
|
|
|
50
49
|
end
|
|
51
50
|
|
|
52
51
|
def week_mixed_with_date?(parts)
|
|
53
|
-
parts.key?(:weeks) &&
|
|
52
|
+
parts.key?(:weeks) && parts.keys.intersect?(DATE_COMPONENTS)
|
|
54
53
|
end
|
|
55
54
|
|
|
56
55
|
def format_seconds(seconds)
|
|
@@ -14,7 +14,7 @@ module ActiveSupport
|
|
|
14
14
|
class Duration
|
|
15
15
|
class Scalar < Numeric # :nodoc:
|
|
16
16
|
attr_reader :value
|
|
17
|
-
delegate :to_i, :to_f, :to_s, to:
|
|
17
|
+
delegate :to_i, :to_f, :to_s, to: :@value
|
|
18
18
|
|
|
19
19
|
def initialize(value)
|
|
20
20
|
@value = value
|
|
@@ -221,6 +221,8 @@ module ActiveSupport
|
|
|
221
221
|
end
|
|
222
222
|
end
|
|
223
223
|
|
|
224
|
+
Delegation.generate(self, [:to_f, :positive?, :negative?, :zero?, :abs], to: :@value, as: Integer, nilable: false)
|
|
225
|
+
|
|
224
226
|
def initialize(value, parts, variable = nil) # :nodoc:
|
|
225
227
|
@value, @parts = value, parts
|
|
226
228
|
@parts.reject! { |k, v| v.zero? } unless value == 0
|
|
@@ -232,7 +234,10 @@ module ActiveSupport
|
|
|
232
234
|
end
|
|
233
235
|
end
|
|
234
236
|
|
|
235
|
-
# Returns a copy of the parts hash that defines the duration
|
|
237
|
+
# Returns a copy of the parts hash that defines the duration.
|
|
238
|
+
#
|
|
239
|
+
# 5.minutes.parts # => {:minutes=>5}
|
|
240
|
+
# 3.years.parts # => {:years=>3}
|
|
236
241
|
def parts
|
|
237
242
|
@parts.dup
|
|
238
243
|
end
|
|
@@ -366,8 +371,8 @@ module ActiveSupport
|
|
|
366
371
|
# 1.year.to_i # => 31556952
|
|
367
372
|
#
|
|
368
373
|
# In such cases, Ruby's core
|
|
369
|
-
# Date[https://ruby-
|
|
370
|
-
# Time[https://ruby-
|
|
374
|
+
# Date[https://docs.ruby-lang.org/en/master/Date.html] and
|
|
375
|
+
# Time[https://docs.ruby-lang.org/en/master/Time.html] should be used for precision
|
|
371
376
|
# date and time arithmetic.
|
|
372
377
|
def to_i
|
|
373
378
|
@value.to_i
|
|
@@ -486,17 +491,21 @@ module ActiveSupport
|
|
|
486
491
|
if @parts.empty?
|
|
487
492
|
time.since(sign * value)
|
|
488
493
|
else
|
|
489
|
-
@parts.
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
494
|
+
@parts.each do |type, number|
|
|
495
|
+
t = time
|
|
496
|
+
time =
|
|
497
|
+
if type == :seconds
|
|
498
|
+
t.since(sign * number)
|
|
499
|
+
elsif type == :minutes
|
|
500
|
+
t.since(sign * number * 60)
|
|
501
|
+
elsif type == :hours
|
|
502
|
+
t.since(sign * number * 3600)
|
|
503
|
+
else
|
|
504
|
+
t.advance(type => sign * number)
|
|
505
|
+
end
|
|
499
506
|
end
|
|
507
|
+
|
|
508
|
+
time
|
|
500
509
|
end
|
|
501
510
|
end
|
|
502
511
|
|
|
@@ -504,8 +513,8 @@ module ActiveSupport
|
|
|
504
513
|
value.respond_to?(method)
|
|
505
514
|
end
|
|
506
515
|
|
|
507
|
-
def method_missing(
|
|
508
|
-
value.public_send(
|
|
516
|
+
def method_missing(...)
|
|
517
|
+
value.public_send(...)
|
|
509
518
|
end
|
|
510
519
|
|
|
511
520
|
def raise_type_error(other)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# :markup: markdown
|
|
4
|
+
|
|
5
|
+
module ActiveSupport
|
|
6
|
+
class Editor
|
|
7
|
+
@editors = {}
|
|
8
|
+
@current = false
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
# Registers a URL pattern for opening file in a given editor.
|
|
12
|
+
# This allows Rails to generate clickable links to control known editors.
|
|
13
|
+
#
|
|
14
|
+
# Example:
|
|
15
|
+
#
|
|
16
|
+
# ActiveSupport::Editor.register("myeditor", "myeditor://%s:%d")
|
|
17
|
+
def register(name, url_pattern, aliases: [])
|
|
18
|
+
editor = new(url_pattern)
|
|
19
|
+
@editors[name] = editor
|
|
20
|
+
aliases.each do |a|
|
|
21
|
+
@editors[a] = editor
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Returns the current editor pattern if it is known.
|
|
26
|
+
# First check for the `RAILS_EDITOR` environment variable, and if it's
|
|
27
|
+
# missing, check for the `EDITOR` environment variable.
|
|
28
|
+
def current
|
|
29
|
+
if @current == false
|
|
30
|
+
@current = if editor_name = ENV["RAILS_EDITOR"] || ENV["EDITOR"]
|
|
31
|
+
@editors[editor_name]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
@current
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# :nodoc:
|
|
38
|
+
|
|
39
|
+
def find(name)
|
|
40
|
+
@editors[name]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def reset
|
|
44
|
+
@current = false
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def initialize(url_pattern)
|
|
49
|
+
@url_pattern = url_pattern
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def url_for(path, line)
|
|
53
|
+
sprintf(@url_pattern, path, line)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
register "atom", "atom://core/open/file?filename=%s&line=%d"
|
|
57
|
+
register "cursor", "cursor://file/%s:%f"
|
|
58
|
+
register "emacs", "emacs://open?url=file://%s&line=%d", aliases: ["emacsclient"]
|
|
59
|
+
register "idea", "idea://open?file=%s&line=%d"
|
|
60
|
+
register "macvim", "mvim://open?url=file://%s&line=%d", aliases: ["mvim"]
|
|
61
|
+
register "nova", "nova://open?path=%s&line=%d"
|
|
62
|
+
register "rubymine", "x-mine://open?file=%s&line=%d"
|
|
63
|
+
register "sublime", "subl://open?url=file://%s&line=%d", aliases: ["subl"]
|
|
64
|
+
register "textmate", "txmt://open?url=file://%s&line=%d", aliases: ["mate"]
|
|
65
|
+
register "vscode", "vscode://file/%s:%d", aliases: ["code"]
|
|
66
|
+
register "vscodium", "vscodium://file/%s:%d", aliases: ["codium"]
|
|
67
|
+
register "windsurf", "windsurf://file/%s:%d"
|
|
68
|
+
register "zed", "zed://file/%s:%d"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -43,6 +43,12 @@ module ActiveSupport
|
|
|
43
43
|
end
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
+
class InvalidKeyError < RuntimeError
|
|
47
|
+
def initialize(content_path, key)
|
|
48
|
+
super "Key '#{key}' is invalid, it must respond to '#to_sym' from configuration in '#{content_path}'."
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
46
52
|
delegate_missing_to :options
|
|
47
53
|
|
|
48
54
|
def initialize(config_path:, key_path:, env_key:, raise_if_missing_key:)
|
|
@@ -61,7 +67,11 @@ module ActiveSupport
|
|
|
61
67
|
end
|
|
62
68
|
|
|
63
69
|
def validate! # :nodoc:
|
|
64
|
-
deserialize(read)
|
|
70
|
+
deserialize(read).each_key do |key|
|
|
71
|
+
key.to_sym
|
|
72
|
+
rescue NoMethodError
|
|
73
|
+
raise InvalidKeyError.new(content_path, key)
|
|
74
|
+
end
|
|
65
75
|
end
|
|
66
76
|
|
|
67
77
|
# Returns the decrypted content as a Hash with symbolized keys.
|
|
@@ -73,7 +83,7 @@ module ActiveSupport
|
|
|
73
83
|
# # => { some_secret: 123, some_namespace: { another_secret: 789 } }
|
|
74
84
|
#
|
|
75
85
|
def config
|
|
76
|
-
@config ||= deserialize(read)
|
|
86
|
+
@config ||= deep_symbolize_keys(deserialize(read))
|
|
77
87
|
end
|
|
78
88
|
|
|
79
89
|
def inspect # :nodoc:
|
|
@@ -81,6 +91,14 @@ module ActiveSupport
|
|
|
81
91
|
end
|
|
82
92
|
|
|
83
93
|
private
|
|
94
|
+
def deep_symbolize_keys(hash)
|
|
95
|
+
hash.deep_transform_keys do |key|
|
|
96
|
+
key.to_sym
|
|
97
|
+
rescue NoMethodError
|
|
98
|
+
raise InvalidKeyError.new(content_path, key)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
84
102
|
def deep_transform(hash)
|
|
85
103
|
return hash unless hash.is_a?(Hash)
|
|
86
104
|
|
|
@@ -26,12 +26,17 @@ module ActiveSupport
|
|
|
26
26
|
class ErrorReporter
|
|
27
27
|
SEVERITIES = %i(error warning info)
|
|
28
28
|
DEFAULT_SOURCE = "application"
|
|
29
|
+
DEFAULT_RESCUE = [StandardError].freeze
|
|
29
30
|
|
|
30
|
-
attr_accessor :logger
|
|
31
|
+
attr_accessor :logger, :debug_mode
|
|
32
|
+
|
|
33
|
+
UnexpectedError = Class.new(Exception)
|
|
31
34
|
|
|
32
35
|
def initialize(*subscribers, logger: nil)
|
|
33
36
|
@subscribers = subscribers.flatten
|
|
34
37
|
@logger = logger
|
|
38
|
+
@debug_mode = false
|
|
39
|
+
@context_middlewares = ErrorContextMiddlewareStack.new
|
|
35
40
|
end
|
|
36
41
|
|
|
37
42
|
# Evaluates the given block, reporting and swallowing any unhandled error.
|
|
@@ -72,7 +77,7 @@ module ActiveSupport
|
|
|
72
77
|
# source of the error. Subscribers can use this value to ignore certain
|
|
73
78
|
# errors. Defaults to <tt>"application"</tt>.
|
|
74
79
|
def handle(*error_classes, severity: :warning, context: {}, fallback: nil, source: DEFAULT_SOURCE)
|
|
75
|
-
error_classes =
|
|
80
|
+
error_classes = DEFAULT_RESCUE if error_classes.empty?
|
|
76
81
|
yield
|
|
77
82
|
rescue *error_classes => error
|
|
78
83
|
report(error, handled: true, severity: severity, context: context, source: source)
|
|
@@ -108,13 +113,47 @@ module ActiveSupport
|
|
|
108
113
|
# source of the error. Subscribers can use this value to ignore certain
|
|
109
114
|
# errors. Defaults to <tt>"application"</tt>.
|
|
110
115
|
def record(*error_classes, severity: :error, context: {}, source: DEFAULT_SOURCE)
|
|
111
|
-
error_classes =
|
|
116
|
+
error_classes = DEFAULT_RESCUE if error_classes.empty?
|
|
112
117
|
yield
|
|
113
118
|
rescue *error_classes => error
|
|
114
119
|
report(error, handled: false, severity: severity, context: context, source: source)
|
|
115
120
|
raise
|
|
116
121
|
end
|
|
117
122
|
|
|
123
|
+
# Either report the given error when in production, or raise it when in development or test.
|
|
124
|
+
#
|
|
125
|
+
# When called in production, after the error is reported, this method will return
|
|
126
|
+
# nil and execution will continue.
|
|
127
|
+
#
|
|
128
|
+
# When called in development, the original error is wrapped in a different error class to ensure
|
|
129
|
+
# it's not being rescued higher in the stack and will be surfaced to the developer.
|
|
130
|
+
#
|
|
131
|
+
# This method is intended for reporting violated assertions about preconditions, or similar
|
|
132
|
+
# cases that can and should be gracefully handled in production, but that aren't supposed to happen.
|
|
133
|
+
#
|
|
134
|
+
# The error can be either an exception instance or a String.
|
|
135
|
+
#
|
|
136
|
+
# example:
|
|
137
|
+
#
|
|
138
|
+
# def edit
|
|
139
|
+
# if published?
|
|
140
|
+
# Rails.error.unexpected("[BUG] Attempting to edit a published article, that shouldn't be possible")
|
|
141
|
+
# return false
|
|
142
|
+
# end
|
|
143
|
+
# # ...
|
|
144
|
+
# end
|
|
145
|
+
#
|
|
146
|
+
def unexpected(error, severity: :warning, context: {}, source: DEFAULT_SOURCE)
|
|
147
|
+
error = RuntimeError.new(error) if error.is_a?(String)
|
|
148
|
+
|
|
149
|
+
if @debug_mode
|
|
150
|
+
ensure_backtrace(error)
|
|
151
|
+
raise UnexpectedError, "#{error.class.name}: #{error.message}", error.backtrace, cause: error
|
|
152
|
+
else
|
|
153
|
+
report(error, handled: true, severity: severity, context: context, source: source)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
118
157
|
# Register a new error subscriber. The subscriber must respond to
|
|
119
158
|
#
|
|
120
159
|
# report(Exception, handled: Boolean, severity: (:error OR :warning OR :info), context: Hash, source: String)
|
|
@@ -164,19 +203,51 @@ module ActiveSupport
|
|
|
164
203
|
ActiveSupport::ExecutionContext.set(...)
|
|
165
204
|
end
|
|
166
205
|
|
|
206
|
+
# Add a middleware to modify the error context before it is sent to subscribers.
|
|
207
|
+
#
|
|
208
|
+
# Middleware is added to a stack of callables run on an error's execution context
|
|
209
|
+
# before passing to subscribers. Allows creation of entries in error context that
|
|
210
|
+
# are shared by all subscribers.
|
|
211
|
+
#
|
|
212
|
+
# A context middleware receives the same parameters as #report.
|
|
213
|
+
# It must return a hash - the middleware stack returns the hash after it has
|
|
214
|
+
# run through all middlewares. A middleware can mutate or replace the hash.
|
|
215
|
+
#
|
|
216
|
+
# Rails.error.add_middleware(-> (error, context) { context.merge({ foo: :bar }) })
|
|
217
|
+
#
|
|
218
|
+
def add_middleware(middleware)
|
|
219
|
+
@context_middlewares.use(middleware)
|
|
220
|
+
end
|
|
221
|
+
|
|
167
222
|
# Report an error directly to subscribers. You can use this method when the
|
|
168
223
|
# block-based #handle and #record methods are not suitable.
|
|
169
224
|
#
|
|
170
225
|
# Rails.error.report(error)
|
|
171
226
|
#
|
|
227
|
+
# The +error+ argument must be an instance of Exception.
|
|
228
|
+
#
|
|
229
|
+
# Rails.error.report(Exception.new("Something went wrong"))
|
|
230
|
+
#
|
|
231
|
+
# Otherwise you can use #unexpected to report an error which does accept a
|
|
232
|
+
# string argument.
|
|
172
233
|
def report(error, handled: true, severity: handled ? :warning : :error, context: {}, source: DEFAULT_SOURCE)
|
|
173
234
|
return if error.instance_variable_defined?(:@__rails_error_reported)
|
|
235
|
+
raise ArgumentError, "Reported error must be an Exception, got: #{error.inspect}" unless error.is_a?(Exception)
|
|
236
|
+
|
|
237
|
+
ensure_backtrace(error)
|
|
174
238
|
|
|
175
239
|
unless SEVERITIES.include?(severity)
|
|
176
240
|
raise ArgumentError, "severity must be one of #{SEVERITIES.map(&:inspect).join(", ")}, got: #{severity.inspect}"
|
|
177
241
|
end
|
|
178
242
|
|
|
179
|
-
full_context =
|
|
243
|
+
full_context = @context_middlewares.execute(
|
|
244
|
+
error,
|
|
245
|
+
context: ActiveSupport::ExecutionContext.to_h.merge(context || {}),
|
|
246
|
+
handled:,
|
|
247
|
+
severity:,
|
|
248
|
+
source:
|
|
249
|
+
)
|
|
250
|
+
|
|
180
251
|
disabled_subscribers = ActiveSupport::IsolatedExecutionState[self]
|
|
181
252
|
@subscribers.each do |subscriber|
|
|
182
253
|
unless disabled_subscribers&.any? { |s| s === subscriber }
|
|
@@ -193,11 +264,55 @@ module ActiveSupport
|
|
|
193
264
|
end
|
|
194
265
|
end
|
|
195
266
|
|
|
196
|
-
|
|
197
|
-
error.
|
|
267
|
+
while error
|
|
268
|
+
unless error.frozen?
|
|
269
|
+
error.instance_variable_set(:@__rails_error_reported, true)
|
|
270
|
+
end
|
|
271
|
+
error = error.cause
|
|
198
272
|
end
|
|
199
273
|
|
|
200
274
|
nil
|
|
201
275
|
end
|
|
276
|
+
|
|
277
|
+
private
|
|
278
|
+
def ensure_backtrace(error)
|
|
279
|
+
return if error.frozen? # re-raising won't add a backtrace or set the cause
|
|
280
|
+
return unless error.backtrace.nil?
|
|
281
|
+
|
|
282
|
+
begin
|
|
283
|
+
# As of Ruby 3.4, we could use Exception#set_backtrace to set the backtrace,
|
|
284
|
+
# but there's nothing like Exception#set_cause. Raising+rescuing is the only way to set the cause.
|
|
285
|
+
raise error
|
|
286
|
+
rescue error.class => error
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
count = 0
|
|
290
|
+
while error.backtrace_locations.first&.path == __FILE__
|
|
291
|
+
count += 1
|
|
292
|
+
error.backtrace_locations.shift
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
error.backtrace.shift(count)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
class ErrorContextMiddlewareStack # :nodoc:
|
|
299
|
+
def initialize
|
|
300
|
+
@stack = []
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Add a middleware to the error context stack.
|
|
304
|
+
def use(middleware)
|
|
305
|
+
unless middleware.respond_to?(:call)
|
|
306
|
+
raise ArgumentError, "Error context middleware must respond to #call"
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
@stack << middleware
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Run all middlewares in the stack
|
|
313
|
+
def execute(error, handled:, severity:, context:, source:)
|
|
314
|
+
@stack.inject(context) { |c, middleware| middleware.call(error, context: c, handled:, severity:, source:) }
|
|
315
|
+
end
|
|
316
|
+
end
|
|
202
317
|
end
|
|
203
318
|
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveSupport::EventReporter::TestHelper # :nodoc:
|
|
4
|
+
class EventSubscriber # :nodoc:
|
|
5
|
+
attr_reader :events
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@events = []
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def emit(event)
|
|
12
|
+
@events << event
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def event_matcher(name:, payload: nil, tags: {}, context: {}, source_location: nil)
|
|
17
|
+
->(event) {
|
|
18
|
+
return false unless event[:name] == name
|
|
19
|
+
return false unless event[:payload] == payload
|
|
20
|
+
return false unless event[:tags] == tags
|
|
21
|
+
return false unless event[:context] == context
|
|
22
|
+
|
|
23
|
+
[:filepath, :lineno, :label].each do |key|
|
|
24
|
+
if source_location && source_location[key]
|
|
25
|
+
return false unless event[:source_location][key] == source_location[key]
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
true
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
end
|