activesupport 6.0.0.beta1 → 6.0.1.rc1
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 +302 -1
- data/README.rdoc +2 -1
- data/lib/active_support.rb +1 -0
- data/lib/active_support/actionable_error.rb +48 -0
- data/lib/active_support/backtrace_cleaner.rb +5 -1
- data/lib/active_support/cache.rb +5 -5
- data/lib/active_support/cache/file_store.rb +3 -10
- data/lib/active_support/cache/memory_store.rb +4 -2
- data/lib/active_support/cache/redis_cache_store.rb +9 -6
- data/lib/active_support/concern.rb +24 -1
- data/lib/active_support/configurable.rb +3 -3
- data/lib/active_support/core_ext/array/access.rb +18 -6
- data/lib/active_support/core_ext/class/attribute.rb +10 -15
- data/lib/active_support/core_ext/date_and_time/calculations.rb +0 -30
- data/lib/active_support/core_ext/digest.rb +3 -0
- data/lib/active_support/core_ext/enumerable.rb +24 -4
- data/lib/active_support/core_ext/hash.rb +1 -0
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
- data/lib/active_support/core_ext/hash/except.rb +1 -1
- data/lib/active_support/core_ext/kernel.rb +0 -1
- data/lib/active_support/core_ext/module/attribute_accessors.rb +5 -5
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +5 -5
- data/lib/active_support/core_ext/module/delegation.rb +6 -0
- data/lib/active_support/core_ext/object/duplicable.rb +7 -117
- data/lib/active_support/core_ext/range/compare_range.rb +27 -12
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +2 -2
- data/lib/active_support/core_ext/string/filters.rb +1 -1
- data/lib/active_support/core_ext/string/inflections.rb +7 -2
- data/lib/active_support/core_ext/string/output_safety.rb +51 -4
- data/lib/active_support/core_ext/time/calculations.rb +31 -2
- data/lib/active_support/current_attributes.rb +6 -0
- data/lib/active_support/dependencies.rb +41 -5
- data/lib/active_support/dependencies/zeitwerk_integration.rb +118 -0
- data/lib/active_support/deprecation/method_wrappers.rb +7 -18
- data/lib/active_support/deprecation/proxy_wrappers.rb +24 -3
- data/lib/active_support/descendants_tracker.rb +52 -6
- data/lib/active_support/duration.rb +2 -3
- data/lib/active_support/encrypted_file.rb +2 -1
- data/lib/active_support/evented_file_update_checker.rb +14 -2
- data/lib/active_support/gem_version.rb +2 -2
- data/lib/active_support/hash_with_indifferent_access.rb +19 -3
- data/lib/active_support/i18n_railtie.rb +2 -1
- data/lib/active_support/inflector/transliterate.rb +43 -14
- data/lib/active_support/logger_thread_safe_level.rb +2 -1
- data/lib/active_support/message_encryptor.rb +1 -1
- data/lib/active_support/message_verifier.rb +1 -1
- data/lib/active_support/notifications.rb +9 -0
- data/lib/active_support/notifications/fanout.rb +60 -13
- data/lib/active_support/notifications/instrumenter.rb +11 -10
- data/lib/active_support/ordered_hash.rb +1 -1
- data/lib/active_support/ordered_options.rb +1 -1
- data/lib/active_support/parameter_filter.rb +6 -1
- data/lib/active_support/security_utils.rb +1 -1
- data/lib/active_support/subscriber.rb +55 -6
- data/lib/active_support/testing/parallelization.rb +21 -2
- metadata +27 -7
- data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "pathname"
|
4
|
+
require "tmpdir"
|
4
5
|
require "active_support/message_encryptor"
|
5
6
|
|
6
7
|
module ActiveSupport
|
@@ -67,7 +68,7 @@ module ActiveSupport
|
|
67
68
|
|
68
69
|
write(updated_contents) if updated_contents != contents
|
69
70
|
ensure
|
70
|
-
FileUtils.rm(tmp_path) if tmp_path
|
71
|
+
FileUtils.rm(tmp_path) if tmp_path&.exist?
|
71
72
|
end
|
72
73
|
|
73
74
|
|
@@ -107,13 +107,23 @@ module ActiveSupport
|
|
107
107
|
|
108
108
|
private
|
109
109
|
def boot!
|
110
|
-
|
110
|
+
normalize_dirs!
|
111
|
+
|
112
|
+
unless @dtw.empty?
|
113
|
+
Listen.to(*@dtw, &method(:changed)).start
|
114
|
+
end
|
111
115
|
end
|
112
116
|
|
113
117
|
def shutdown!
|
114
118
|
Listen.stop
|
115
119
|
end
|
116
120
|
|
121
|
+
def normalize_dirs!
|
122
|
+
@dirs.transform_keys! do |dir|
|
123
|
+
dir.exist? ? dir.realpath : dir
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
117
127
|
def changed(modified, added, removed)
|
118
128
|
unless updated?
|
119
129
|
@updated.make_true if (modified + added + removed).any? { |f| watching?(f) }
|
@@ -131,7 +141,9 @@ module ActiveSupport
|
|
131
141
|
ext = @ph.normalize_extension(file.extname)
|
132
142
|
|
133
143
|
file.dirname.ascend do |dir|
|
134
|
-
|
144
|
+
matching = @dirs[dir]
|
145
|
+
|
146
|
+
if matching && (matching.empty? || matching.include?(ext))
|
135
147
|
break true
|
136
148
|
elsif dir == @lcsp || dir.root?
|
137
149
|
break false
|
@@ -164,6 +164,19 @@ module ActiveSupport
|
|
164
164
|
super(convert_key(key))
|
165
165
|
end
|
166
166
|
|
167
|
+
# Same as <tt>Hash#assoc</tt> where the key passed as argument can be
|
168
|
+
# either a string or a symbol:
|
169
|
+
#
|
170
|
+
# counters = ActiveSupport::HashWithIndifferentAccess.new
|
171
|
+
# counters[:foo] = 1
|
172
|
+
#
|
173
|
+
# counters.assoc('foo') # => ["foo", 1]
|
174
|
+
# counters.assoc(:foo) # => ["foo", 1]
|
175
|
+
# counters.assoc(:zoo) # => nil
|
176
|
+
def assoc(key)
|
177
|
+
super(convert_key(key))
|
178
|
+
end
|
179
|
+
|
167
180
|
# Same as <tt>Hash#fetch</tt> where the key passed as argument can be
|
168
181
|
# either a string or a symbol:
|
169
182
|
#
|
@@ -212,8 +225,8 @@ module ActiveSupport
|
|
212
225
|
# hash[:a] = 'x'
|
213
226
|
# hash[:b] = 'y'
|
214
227
|
# hash.values_at('a', 'b') # => ["x", "y"]
|
215
|
-
def values_at(*
|
216
|
-
|
228
|
+
def values_at(*keys)
|
229
|
+
super(*keys.map { |key| convert_key(key) })
|
217
230
|
end
|
218
231
|
|
219
232
|
# Returns an array of the values at the specified indices, but also
|
@@ -226,7 +239,7 @@ module ActiveSupport
|
|
226
239
|
# hash.fetch_values('a', 'c') { |key| 'z' } # => ["x", "z"]
|
227
240
|
# hash.fetch_values('a', 'c') # => KeyError: key not found: "c"
|
228
241
|
def fetch_values(*indices, &block)
|
229
|
-
indices.
|
242
|
+
super(*indices.map { |key| convert_key(key) }, &block)
|
230
243
|
end
|
231
244
|
|
232
245
|
# Returns a shallow copy of the hash.
|
@@ -280,6 +293,9 @@ module ActiveSupport
|
|
280
293
|
super(convert_key(key))
|
281
294
|
end
|
282
295
|
|
296
|
+
def except(*keys)
|
297
|
+
slice(*self.keys - keys.map { |key| convert_key(key) })
|
298
|
+
end
|
283
299
|
alias_method :without, :except
|
284
300
|
|
285
301
|
def stringify_keys!; self end
|
@@ -97,7 +97,8 @@ module I18n
|
|
97
97
|
If you desire the default locale to be included in the defaults, please
|
98
98
|
explicitly configure it with `config.i18n.fallbacks.defaults =
|
99
99
|
[I18n.default_locale]` or `config.i18n.fallbacks = [I18n.default_locale,
|
100
|
-
{...}]
|
100
|
+
{...}]`. If you want to opt-in to the new behavior, use
|
101
|
+
`config.i18n.fallbacks.defaults = [nil, {...}]`.
|
101
102
|
MSG
|
102
103
|
args.unshift I18n.default_locale
|
103
104
|
end
|
@@ -51,20 +51,45 @@ module ActiveSupport
|
|
51
51
|
#
|
52
52
|
# Now you can have different transliterations for each locale:
|
53
53
|
#
|
54
|
-
#
|
55
|
-
# transliterate('Jürgen')
|
54
|
+
# transliterate('Jürgen', locale: :en)
|
56
55
|
# # => "Jurgen"
|
57
56
|
#
|
58
|
-
#
|
59
|
-
# transliterate('Jürgen')
|
57
|
+
# transliterate('Jürgen', locale: :de)
|
60
58
|
# # => "Juergen"
|
61
|
-
|
59
|
+
#
|
60
|
+
# Transliteration is restricted to UTF-8, US-ASCII and GB18030 strings
|
61
|
+
# Other encodings will raise an ArgumentError.
|
62
|
+
def transliterate(string, replacement = "?", locale: nil)
|
63
|
+
string = string.dup if string.frozen?
|
62
64
|
raise ArgumentError, "Can only transliterate strings. Received #{string.class.name}" unless string.is_a?(String)
|
63
65
|
|
64
|
-
|
66
|
+
allowed_encodings = [Encoding::UTF_8, Encoding::US_ASCII, Encoding::GB18030]
|
67
|
+
raise ArgumentError, "Can not transliterate strings with #{string.encoding} encoding" unless allowed_encodings.include?(string.encoding)
|
68
|
+
|
69
|
+
input_encoding = string.encoding
|
70
|
+
|
71
|
+
# US-ASCII is a subset of UTF-8 so we'll force encoding as UTF-8 if
|
72
|
+
# US-ASCII is given. This way we can let tidy_bytes handle the string
|
73
|
+
# in the same way as we do for UTF-8
|
74
|
+
string.force_encoding(Encoding::UTF_8) if string.encoding == Encoding::US_ASCII
|
75
|
+
|
76
|
+
# GB18030 is Unicode compatible but is not a direct mapping so needs to be
|
77
|
+
# transcoded. Using invalid/undef :replace will result in loss of data in
|
78
|
+
# the event of invalid characters, but since tidy_bytes will replace
|
79
|
+
# invalid/undef with a "?" we're safe to do the same beforehand
|
80
|
+
string.encode!(Encoding::UTF_8, invalid: :replace, undef: :replace) if string.encoding == Encoding::GB18030
|
81
|
+
|
82
|
+
transliterated = I18n.transliterate(
|
65
83
|
ActiveSupport::Multibyte::Unicode.tidy_bytes(string).unicode_normalize(:nfc),
|
66
|
-
replacement: replacement
|
84
|
+
replacement: replacement,
|
85
|
+
locale: locale
|
67
86
|
)
|
87
|
+
|
88
|
+
# Restore the string encoding of the input if it was not UTF-8.
|
89
|
+
# Apply invalid/undef :replace as tidy_bytes does
|
90
|
+
transliterated.encode!(input_encoding, invalid: :replace, undef: :replace) if input_encoding != transliterated.encoding
|
91
|
+
|
92
|
+
transliterated
|
68
93
|
end
|
69
94
|
|
70
95
|
# Replaces special characters in a string so that it may be used as part of
|
@@ -75,8 +100,8 @@ module ActiveSupport
|
|
75
100
|
#
|
76
101
|
# To use a custom separator, override the +separator+ argument.
|
77
102
|
#
|
78
|
-
#
|
79
|
-
#
|
103
|
+
# parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth"
|
104
|
+
# parameterize("^très|Jolie__ ", separator: '_') # => "tres_jolie"
|
80
105
|
#
|
81
106
|
# To preserve the case of the characters in a string, use the +preserve_case+ argument.
|
82
107
|
#
|
@@ -85,13 +110,17 @@ module ActiveSupport
|
|
85
110
|
#
|
86
111
|
# It preserves dashes and underscores unless they are used as separators:
|
87
112
|
#
|
88
|
-
#
|
89
|
-
#
|
90
|
-
#
|
113
|
+
# parameterize("^très|Jolie__ ") # => "tres-jolie__"
|
114
|
+
# parameterize("^très|Jolie-- ", separator: "_") # => "tres_jolie--"
|
115
|
+
# parameterize("^très_Jolie-- ", separator: ".") # => "tres_jolie--"
|
91
116
|
#
|
92
|
-
|
117
|
+
# If the optional parameter +locale+ is specified,
|
118
|
+
# the word will be parameterized as a word of that language.
|
119
|
+
# By default, this parameter is set to <tt>nil</tt> and it will use
|
120
|
+
# the configured <tt>I18n.locale<tt>.
|
121
|
+
def parameterize(string, separator: "-", preserve_case: false, locale: nil)
|
93
122
|
# Replace accented chars with their ASCII equivalents.
|
94
|
-
parameterized_string = transliterate(string)
|
123
|
+
parameterized_string = transliterate(string, locale: locale)
|
95
124
|
|
96
125
|
# Turn unwanted chars into the separator.
|
97
126
|
parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator)
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require "active_support/concern"
|
4
4
|
require "active_support/core_ext/module/attribute_accessors"
|
5
5
|
require "concurrent"
|
6
|
+
require "fiber"
|
6
7
|
|
7
8
|
module ActiveSupport
|
8
9
|
module LoggerThreadSafeLevel # :nodoc:
|
@@ -28,7 +29,7 @@ module ActiveSupport
|
|
28
29
|
end
|
29
30
|
|
30
31
|
def local_log_id
|
31
|
-
|
32
|
+
Fiber.current.__id__
|
32
33
|
end
|
33
34
|
|
34
35
|
def local_level
|
@@ -53,7 +53,7 @@ module ActiveSupport
|
|
53
53
|
# crypt.encrypt_and_sign(parcel, expires_in: 1.month)
|
54
54
|
# crypt.encrypt_and_sign(doowad, expires_at: Time.now.end_of_year)
|
55
55
|
#
|
56
|
-
# Then the messages can be verified and returned
|
56
|
+
# Then the messages can be verified and returned up to the expire time.
|
57
57
|
# Thereafter, verifying returns +nil+.
|
58
58
|
#
|
59
59
|
# === Rotating keys
|
@@ -71,7 +71,7 @@ module ActiveSupport
|
|
71
71
|
# @verifier.generate(parcel, expires_in: 1.month)
|
72
72
|
# @verifier.generate(doowad, expires_at: Time.now.end_of_year)
|
73
73
|
#
|
74
|
-
# Then the messages can be verified and returned
|
74
|
+
# Then the messages can be verified and returned up to the expire time.
|
75
75
|
# Thereafter, the +verified+ method returns +nil+ while +verify+ raises
|
76
76
|
# <tt>ActiveSupport::MessageVerifier::InvalidSignature</tt>.
|
77
77
|
#
|
@@ -153,6 +153,15 @@ module ActiveSupport
|
|
153
153
|
#
|
154
154
|
# ActiveSupport::Notifications.unsubscribe("render")
|
155
155
|
#
|
156
|
+
# Subscribers using a regexp or other pattern-matching object will remain subscribed
|
157
|
+
# to all events that match their original pattern, unless those events match a string
|
158
|
+
# passed to `unsubscribe`:
|
159
|
+
#
|
160
|
+
# subscriber = ActiveSupport::Notifications.subscribe(/render/) { }
|
161
|
+
# ActiveSupport::Notifications.unsubscribe('render_template.action_view')
|
162
|
+
# subscriber.matches?('render_template.action_view') # => false
|
163
|
+
# subscriber.matches?('render_partial.action_view') # => true
|
164
|
+
#
|
156
165
|
# == Default Queue
|
157
166
|
#
|
158
167
|
# Notifications ships with a queue implementation that consumes and publishes events
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "mutex_m"
|
4
4
|
require "concurrent/map"
|
5
|
+
require "set"
|
5
6
|
|
6
7
|
module ActiveSupport
|
7
8
|
module Notifications
|
@@ -13,16 +14,22 @@ module ActiveSupport
|
|
13
14
|
include Mutex_m
|
14
15
|
|
15
16
|
def initialize
|
16
|
-
@
|
17
|
+
@string_subscribers = Hash.new { |h, k| h[k] = [] }
|
18
|
+
@other_subscribers = []
|
17
19
|
@listeners_for = Concurrent::Map.new
|
18
20
|
super
|
19
21
|
end
|
20
22
|
|
21
|
-
def subscribe(pattern = nil,
|
22
|
-
subscriber = Subscribers.new
|
23
|
+
def subscribe(pattern = nil, callable = nil, &block)
|
24
|
+
subscriber = Subscribers.new(pattern, callable || block)
|
23
25
|
synchronize do
|
24
|
-
|
25
|
-
|
26
|
+
if String === pattern
|
27
|
+
@string_subscribers[pattern] << subscriber
|
28
|
+
@listeners_for.delete(pattern)
|
29
|
+
else
|
30
|
+
@other_subscribers << subscriber
|
31
|
+
@listeners_for.clear
|
32
|
+
end
|
26
33
|
end
|
27
34
|
subscriber
|
28
35
|
end
|
@@ -31,12 +38,19 @@ module ActiveSupport
|
|
31
38
|
synchronize do
|
32
39
|
case subscriber_or_name
|
33
40
|
when String
|
34
|
-
@
|
41
|
+
@string_subscribers[subscriber_or_name].clear
|
42
|
+
@listeners_for.delete(subscriber_or_name)
|
43
|
+
@other_subscribers.each { |sub| sub.unsubscribe!(subscriber_or_name) }
|
35
44
|
else
|
36
|
-
|
45
|
+
pattern = subscriber_or_name.try(:pattern)
|
46
|
+
if String === pattern
|
47
|
+
@string_subscribers[pattern].delete(subscriber_or_name)
|
48
|
+
@listeners_for.delete(pattern)
|
49
|
+
else
|
50
|
+
@other_subscribers.delete(subscriber_or_name)
|
51
|
+
@listeners_for.clear
|
52
|
+
end
|
37
53
|
end
|
38
|
-
|
39
|
-
@listeners_for.clear
|
40
54
|
end
|
41
55
|
end
|
42
56
|
|
@@ -56,7 +70,8 @@ module ActiveSupport
|
|
56
70
|
# this is correctly done double-checked locking (Concurrent::Map's lookups have volatile semantics)
|
57
71
|
@listeners_for[name] || synchronize do
|
58
72
|
# use synchronisation when accessing @subscribers
|
59
|
-
@listeners_for[name] ||=
|
73
|
+
@listeners_for[name] ||=
|
74
|
+
@string_subscribers[name] + @other_subscribers.select { |s| s.subscribed_to?(name) }
|
60
75
|
end
|
61
76
|
end
|
62
77
|
|
@@ -100,9 +115,33 @@ module ActiveSupport
|
|
100
115
|
end
|
101
116
|
end
|
102
117
|
|
118
|
+
class Matcher #:nodoc:
|
119
|
+
attr_reader :pattern, :exclusions
|
120
|
+
|
121
|
+
def self.wrap(pattern)
|
122
|
+
return pattern if String === pattern
|
123
|
+
new(pattern)
|
124
|
+
end
|
125
|
+
|
126
|
+
def initialize(pattern)
|
127
|
+
@pattern = pattern
|
128
|
+
@exclusions = Set.new
|
129
|
+
end
|
130
|
+
|
131
|
+
def unsubscribe!(name)
|
132
|
+
exclusions << -name if pattern === name
|
133
|
+
end
|
134
|
+
|
135
|
+
def ===(name)
|
136
|
+
pattern === name && !exclusions.include?(name)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
103
140
|
class Evented #:nodoc:
|
141
|
+
attr_reader :pattern
|
142
|
+
|
104
143
|
def initialize(pattern, delegate)
|
105
|
-
@pattern = pattern
|
144
|
+
@pattern = Matcher.wrap(pattern)
|
106
145
|
@delegate = delegate
|
107
146
|
@can_publish = delegate.respond_to?(:publish)
|
108
147
|
end
|
@@ -122,11 +161,15 @@ module ActiveSupport
|
|
122
161
|
end
|
123
162
|
|
124
163
|
def subscribed_to?(name)
|
125
|
-
|
164
|
+
pattern === name
|
126
165
|
end
|
127
166
|
|
128
167
|
def matches?(name)
|
129
|
-
|
168
|
+
pattern && pattern === name
|
169
|
+
end
|
170
|
+
|
171
|
+
def unsubscribe!(name)
|
172
|
+
pattern.unsubscribe!(name)
|
130
173
|
end
|
131
174
|
end
|
132
175
|
|
@@ -189,6 +232,10 @@ module ActiveSupport
|
|
189
232
|
true
|
190
233
|
end
|
191
234
|
|
235
|
+
def unsubscribe!(*)
|
236
|
+
false
|
237
|
+
end
|
238
|
+
|
192
239
|
alias :matches? :===
|
193
240
|
end
|
194
241
|
end
|
@@ -13,14 +13,15 @@ module ActiveSupport
|
|
13
13
|
@notifier = notifier
|
14
14
|
end
|
15
15
|
|
16
|
-
#
|
17
|
-
# and publish it.
|
18
|
-
#
|
16
|
+
# Given a block, instrument it by measuring the time taken to execute
|
17
|
+
# and publish it. Without a block, simply send a message via the
|
18
|
+
# notifier. Notice that events get sent even if an error occurs in the
|
19
|
+
# passed-in block.
|
19
20
|
def instrument(name, payload = {})
|
20
21
|
# some of the listeners might have state
|
21
22
|
listeners_state = start name, payload
|
22
23
|
begin
|
23
|
-
yield payload
|
24
|
+
yield payload if block_given?
|
24
25
|
rescue Exception => e
|
25
26
|
payload[:exception] = [e.class.name, e.message]
|
26
27
|
payload[:exception_object] = e
|
@@ -56,7 +57,8 @@ module ActiveSupport
|
|
56
57
|
|
57
58
|
def self.clock_gettime_supported? # :nodoc:
|
58
59
|
defined?(Process::CLOCK_PROCESS_CPUTIME_ID) &&
|
59
|
-
!Gem.win_platform?
|
60
|
+
!Gem.win_platform? &&
|
61
|
+
!RUBY_PLATFORM.match?(/solaris/i)
|
60
62
|
end
|
61
63
|
private_class_method :clock_gettime_supported?
|
62
64
|
|
@@ -67,9 +69,8 @@ module ActiveSupport
|
|
67
69
|
@transaction_id = transaction_id
|
68
70
|
@end = ending
|
69
71
|
@children = []
|
70
|
-
@
|
71
|
-
@
|
72
|
-
@cpu_time_finish = nil
|
72
|
+
@cpu_time_start = 0
|
73
|
+
@cpu_time_finish = 0
|
73
74
|
@allocation_count_start = 0
|
74
75
|
@allocation_count_finish = 0
|
75
76
|
end
|
@@ -124,7 +125,7 @@ module ActiveSupport
|
|
124
125
|
#
|
125
126
|
# @event.duration # => 1000.138
|
126
127
|
def duration
|
127
|
-
|
128
|
+
1000.0 * (self.end - time)
|
128
129
|
end
|
129
130
|
|
130
131
|
def <<(event)
|
@@ -137,7 +138,7 @@ module ActiveSupport
|
|
137
138
|
|
138
139
|
private
|
139
140
|
def now
|
140
|
-
|
141
|
+
Concurrent.monotonic_time
|
141
142
|
end
|
142
143
|
|
143
144
|
if clock_gettime_supported?
|