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.

Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +302 -1
  3. data/README.rdoc +2 -1
  4. data/lib/active_support.rb +1 -0
  5. data/lib/active_support/actionable_error.rb +48 -0
  6. data/lib/active_support/backtrace_cleaner.rb +5 -1
  7. data/lib/active_support/cache.rb +5 -5
  8. data/lib/active_support/cache/file_store.rb +3 -10
  9. data/lib/active_support/cache/memory_store.rb +4 -2
  10. data/lib/active_support/cache/redis_cache_store.rb +9 -6
  11. data/lib/active_support/concern.rb +24 -1
  12. data/lib/active_support/configurable.rb +3 -3
  13. data/lib/active_support/core_ext/array/access.rb +18 -6
  14. data/lib/active_support/core_ext/class/attribute.rb +10 -15
  15. data/lib/active_support/core_ext/date_and_time/calculations.rb +0 -30
  16. data/lib/active_support/core_ext/digest.rb +3 -0
  17. data/lib/active_support/core_ext/enumerable.rb +24 -4
  18. data/lib/active_support/core_ext/hash.rb +1 -0
  19. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  20. data/lib/active_support/core_ext/hash/except.rb +1 -1
  21. data/lib/active_support/core_ext/kernel.rb +0 -1
  22. data/lib/active_support/core_ext/module/attribute_accessors.rb +5 -5
  23. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +5 -5
  24. data/lib/active_support/core_ext/module/delegation.rb +6 -0
  25. data/lib/active_support/core_ext/object/duplicable.rb +7 -117
  26. data/lib/active_support/core_ext/range/compare_range.rb +27 -12
  27. data/lib/active_support/core_ext/range/include_time_with_zone.rb +2 -2
  28. data/lib/active_support/core_ext/string/filters.rb +1 -1
  29. data/lib/active_support/core_ext/string/inflections.rb +7 -2
  30. data/lib/active_support/core_ext/string/output_safety.rb +51 -4
  31. data/lib/active_support/core_ext/time/calculations.rb +31 -2
  32. data/lib/active_support/current_attributes.rb +6 -0
  33. data/lib/active_support/dependencies.rb +41 -5
  34. data/lib/active_support/dependencies/zeitwerk_integration.rb +118 -0
  35. data/lib/active_support/deprecation/method_wrappers.rb +7 -18
  36. data/lib/active_support/deprecation/proxy_wrappers.rb +24 -3
  37. data/lib/active_support/descendants_tracker.rb +52 -6
  38. data/lib/active_support/duration.rb +2 -3
  39. data/lib/active_support/encrypted_file.rb +2 -1
  40. data/lib/active_support/evented_file_update_checker.rb +14 -2
  41. data/lib/active_support/gem_version.rb +2 -2
  42. data/lib/active_support/hash_with_indifferent_access.rb +19 -3
  43. data/lib/active_support/i18n_railtie.rb +2 -1
  44. data/lib/active_support/inflector/transliterate.rb +43 -14
  45. data/lib/active_support/logger_thread_safe_level.rb +2 -1
  46. data/lib/active_support/message_encryptor.rb +1 -1
  47. data/lib/active_support/message_verifier.rb +1 -1
  48. data/lib/active_support/notifications.rb +9 -0
  49. data/lib/active_support/notifications/fanout.rb +60 -13
  50. data/lib/active_support/notifications/instrumenter.rb +11 -10
  51. data/lib/active_support/ordered_hash.rb +1 -1
  52. data/lib/active_support/ordered_options.rb +1 -1
  53. data/lib/active_support/parameter_filter.rb +6 -1
  54. data/lib/active_support/security_utils.rb +1 -1
  55. data/lib/active_support/subscriber.rb +55 -6
  56. data/lib/active_support/testing/parallelization.rb +21 -2
  57. metadata +27 -7
  58. 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.exist?
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
- Listen.to(*@dtw, &method(:changed)).start
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
- if @dirs.fetch(dir, []).include?(ext)
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
@@ -9,8 +9,8 @@ module ActiveSupport
9
9
  module VERSION
10
10
  MAJOR = 6
11
11
  MINOR = 0
12
- TINY = 0
13
- PRE = "beta1"
12
+ TINY = 1
13
+ PRE = "rc1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -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(*indices)
216
- indices.collect { |key| self[convert_key(key)] }
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.collect { |key| fetch(key, &block) }
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
- # I18n.locale = :en
55
- # transliterate('Jürgen')
54
+ # transliterate('Jürgen', locale: :en)
56
55
  # # => "Jurgen"
57
56
  #
58
- # I18n.locale = :de
59
- # transliterate('Jürgen')
57
+ # transliterate('Jürgen', locale: :de)
60
58
  # # => "Juergen"
61
- def transliterate(string, replacement = "?")
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
- I18n.transliterate(
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
- # parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth"
79
- # parameterize("^très|Jolie__ ", separator: '_') # => "tres_jolie"
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
- # parameterize("^très|Jolie__ ") # => "tres-jolie__"
89
- # parameterize("^très|Jolie-- ", separator: "_") # => "tres_jolie--"
90
- # parameterize("^très_Jolie-- ", separator: ".") # => "tres_jolie--"
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
- def parameterize(string, separator: "-", preserve_case: false)
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
- Thread.current.__id__
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 upto the expire time.
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 upto the expire time.
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
- @subscribers = []
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, block = Proc.new)
22
- subscriber = Subscribers.new pattern, block
23
+ def subscribe(pattern = nil, callable = nil, &block)
24
+ subscriber = Subscribers.new(pattern, callable || block)
23
25
  synchronize do
24
- @subscribers << subscriber
25
- @listeners_for.clear
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
- @subscribers.reject! { |s| s.matches?(subscriber_or_name) }
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
- @subscribers.delete(subscriber_or_name)
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] ||= @subscribers.select { |s| s.subscribed_to?(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
- @pattern === name
164
+ pattern === name
126
165
  end
127
166
 
128
167
  def matches?(name)
129
- @pattern && @pattern === name
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
- # Instrument the given block by measuring the time taken to execute it
17
- # and publish it. Notice that events get sent even if an error occurs
18
- # in the passed-in block.
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
- @duration = nil
71
- @cpu_time_start = nil
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
- @duration ||= 1000.0 * (self.end - time)
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
- Process.clock_gettime(Process::CLOCK_MONOTONIC)
141
+ Concurrent.monotonic_time
141
142
  end
142
143
 
143
144
  if clock_gettime_supported?