activesupport 6.0.0.beta2 → 6.0.2.rc1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activesupport might be problematic. Click here for more details.

Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +287 -3
  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/memory_store.rb +2 -0
  9. data/lib/active_support/cache/redis_cache_store.rb +9 -6
  10. data/lib/active_support/concern.rb +24 -1
  11. data/lib/active_support/configurable.rb +3 -3
  12. data/lib/active_support/core_ext/array/access.rb +18 -6
  13. data/lib/active_support/core_ext/class/attribute.rb +10 -15
  14. data/lib/active_support/core_ext/date_and_time/calculations.rb +0 -30
  15. data/lib/active_support/core_ext/digest.rb +3 -0
  16. data/lib/active_support/core_ext/enumerable.rb +24 -4
  17. data/lib/active_support/core_ext/hash/deep_transform_values.rb +2 -2
  18. data/lib/active_support/core_ext/hash/except.rb +1 -1
  19. data/lib/active_support/core_ext/module/attribute_accessors.rb +5 -5
  20. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +5 -5
  21. data/lib/active_support/core_ext/module/delegation.rb +6 -0
  22. data/lib/active_support/core_ext/object/duplicable.rb +7 -117
  23. data/lib/active_support/core_ext/range/compare_range.rb +27 -12
  24. data/lib/active_support/core_ext/range/include_time_with_zone.rb +2 -2
  25. data/lib/active_support/core_ext/string/filters.rb +1 -1
  26. data/lib/active_support/core_ext/string/inflections.rb +7 -2
  27. data/lib/active_support/core_ext/string/output_safety.rb +51 -4
  28. data/lib/active_support/core_ext/time/calculations.rb +31 -2
  29. data/lib/active_support/dependencies.rb +41 -5
  30. data/lib/active_support/dependencies/zeitwerk_integration.rb +44 -21
  31. data/lib/active_support/deprecation/method_wrappers.rb +7 -18
  32. data/lib/active_support/deprecation/proxy_wrappers.rb +24 -3
  33. data/lib/active_support/descendants_tracker.rb +52 -6
  34. data/lib/active_support/duration.rb +2 -3
  35. data/lib/active_support/evented_file_update_checker.rb +14 -2
  36. data/lib/active_support/gem_version.rb +2 -2
  37. data/lib/active_support/hash_with_indifferent_access.rb +6 -3
  38. data/lib/active_support/i18n_railtie.rb +6 -1
  39. data/lib/active_support/inflector/transliterate.rb +43 -14
  40. data/lib/active_support/logger_thread_safe_level.rb +2 -1
  41. data/lib/active_support/notifications/fanout.rb +2 -2
  42. data/lib/active_support/notifications/instrumenter.rb +12 -11
  43. data/lib/active_support/ordered_hash.rb +1 -1
  44. data/lib/active_support/ordered_options.rb +1 -1
  45. data/lib/active_support/parameter_filter.rb +6 -1
  46. data/lib/active_support/security_utils.rb +1 -1
  47. data/lib/active_support/subscriber.rb +55 -6
  48. data/lib/active_support/testing/parallelization.rb +21 -2
  49. metadata +13 -14
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/array/extract_options"
4
+ require "active_support/core_ext/module/redefine_method"
4
5
 
5
6
  module ActiveSupport
6
7
  class Deprecation
@@ -52,29 +53,17 @@ module ActiveSupport
52
53
  options = method_names.extract_options!
53
54
  deprecator = options.delete(:deprecator) || self
54
55
  method_names += options.keys
55
- mod = Module.new
56
+ mod = nil
56
57
 
57
58
  method_names.each do |method_name|
58
59
  if target_module.method_defined?(method_name) || target_module.private_method_defined?(method_name)
59
- aliased_method, punctuation = method_name.to_s.sub(/([?!=])$/, ""), $1
60
- with_method = "#{aliased_method}_with_deprecation#{punctuation}"
61
- without_method = "#{aliased_method}_without_deprecation#{punctuation}"
62
-
63
- target_module.define_method(with_method) do |*args, &block|
60
+ method = target_module.instance_method(method_name)
61
+ target_module.redefine_method(method_name) do |*args, &block|
64
62
  deprecator.deprecation_warning(method_name, options[method_name])
65
- send(without_method, *args, &block)
66
- end
67
-
68
- target_module.alias_method(without_method, method_name)
69
- target_module.alias_method(method_name, with_method)
70
-
71
- case
72
- when target_module.protected_method_defined?(without_method)
73
- target_module.send(:protected, method_name)
74
- when target_module.private_method_defined?(without_method)
75
- target_module.send(:private, method_name)
63
+ method.bind(self).call(*args, &block)
76
64
  end
77
65
  else
66
+ mod ||= Module.new
78
67
  mod.define_method(method_name) do |*args, &block|
79
68
  deprecator.deprecation_warning(method_name, options[method_name])
80
69
  super(*args, &block)
@@ -82,7 +71,7 @@ module ActiveSupport
82
71
  end
83
72
  end
84
73
 
85
- target_module.prepend(mod) unless mod.instance_methods(false).empty?
74
+ target_module.prepend(mod) if mod
86
75
  end
87
76
  end
88
77
  end
@@ -120,7 +120,14 @@ module ActiveSupport
120
120
  # # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead.
121
121
  # (Backtrace information…)
122
122
  # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
123
- class DeprecatedConstantProxy < DeprecationProxy
123
+ class DeprecatedConstantProxy < Module
124
+ def self.new(*args, &block)
125
+ object = args.first
126
+
127
+ return object unless object
128
+ super
129
+ end
130
+
124
131
  def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance, message: "#{old_const} is deprecated! Use #{new_const} instead.")
125
132
  require "active_support/inflector/methods"
126
133
 
@@ -130,6 +137,14 @@ module ActiveSupport
130
137
  @message = message
131
138
  end
132
139
 
140
+ instance_methods.each { |m| undef_method m unless /^__|^object_id$/.match?(m) }
141
+
142
+ # Don't give a deprecation warning on inspect since test/unit and error
143
+ # logs rely on it for diagnostics.
144
+ def inspect
145
+ target.inspect
146
+ end
147
+
133
148
  # Returns the class of the new constant.
134
149
  #
135
150
  # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
@@ -144,8 +159,14 @@ module ActiveSupport
144
159
  ActiveSupport::Inflector.constantize(@new_const.to_s)
145
160
  end
146
161
 
147
- def warn(callstack, called, args)
148
- @deprecator.warn(@message, callstack)
162
+ def const_missing(name)
163
+ @deprecator.warn(@message, caller_locations)
164
+ target.const_get(name)
165
+ end
166
+
167
+ def method_missing(called, *args, &block)
168
+ @deprecator.warn(@message, caller_locations)
169
+ target.__send__(called, *args, &block)
149
170
  end
150
171
  end
151
172
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "weakref"
4
+
3
5
  module ActiveSupport
4
6
  # This module provides an internal implementation to track descendants
5
7
  # which is faster than iterating through ObjectSpace.
@@ -8,7 +10,8 @@ module ActiveSupport
8
10
 
9
11
  class << self
10
12
  def direct_descendants(klass)
11
- @@direct_descendants[klass] || []
13
+ descendants = @@direct_descendants[klass]
14
+ descendants ? descendants.to_a : []
12
15
  end
13
16
 
14
17
  def descendants(klass)
@@ -20,10 +23,10 @@ module ActiveSupport
20
23
  def clear
21
24
  if defined? ActiveSupport::Dependencies
22
25
  @@direct_descendants.each do |klass, descendants|
23
- if ActiveSupport::Dependencies.autoloaded?(klass)
26
+ if Dependencies.autoloaded?(klass)
24
27
  @@direct_descendants.delete(klass)
25
28
  else
26
- descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) }
29
+ descendants.reject! { |v| Dependencies.autoloaded?(v) }
27
30
  end
28
31
  end
29
32
  else
@@ -34,15 +37,17 @@ module ActiveSupport
34
37
  # This is the only method that is not thread safe, but is only ever called
35
38
  # during the eager loading phase.
36
39
  def store_inherited(klass, descendant)
37
- (@@direct_descendants[klass] ||= []) << descendant
40
+ (@@direct_descendants[klass] ||= DescendantsArray.new) << descendant
38
41
  end
39
42
 
40
43
  private
41
44
 
42
45
  def accumulate_descendants(klass, acc)
43
46
  if direct_descendants = @@direct_descendants[klass]
44
- acc.concat(direct_descendants)
45
- direct_descendants.each { |direct_descendant| accumulate_descendants(direct_descendant, acc) }
47
+ direct_descendants.each do |direct_descendant|
48
+ acc << direct_descendant
49
+ accumulate_descendants(direct_descendant, acc)
50
+ end
46
51
  end
47
52
  end
48
53
  end
@@ -59,5 +64,46 @@ module ActiveSupport
59
64
  def descendants
60
65
  DescendantsTracker.descendants(self)
61
66
  end
67
+
68
+ # DescendantsArray is an array that contains weak references to classes.
69
+ class DescendantsArray # :nodoc:
70
+ include Enumerable
71
+
72
+ def initialize
73
+ @refs = []
74
+ end
75
+
76
+ def initialize_copy(orig)
77
+ @refs = @refs.dup
78
+ end
79
+
80
+ def <<(klass)
81
+ cleanup!
82
+ @refs << WeakRef.new(klass)
83
+ end
84
+
85
+ def each
86
+ @refs.each do |ref|
87
+ yield ref.__getobj__
88
+ rescue WeakRef::RefError
89
+ end
90
+ end
91
+
92
+ def refs_size
93
+ @refs.size
94
+ end
95
+
96
+ def cleanup!
97
+ @refs.delete_if { |ref| !ref.weakref_alive? }
98
+ end
99
+
100
+ def reject!
101
+ @refs.reject! do |ref|
102
+ yield ref.__getobj__
103
+ rescue WeakRef::RefError
104
+ true
105
+ end
106
+ end
107
+ end
62
108
  end
63
109
  end
@@ -4,7 +4,6 @@ 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
6
  require "active_support/core_ext/string/filters"
7
- require "active_support/deprecation"
8
7
 
9
8
  module ActiveSupport
10
9
  # Provides accurate date and time measurements using Date#advance and
@@ -339,8 +338,8 @@ module ActiveSupport
339
338
  # 1.year.to_i # => 31556952
340
339
  #
341
340
  # In such cases, Ruby's core
342
- # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
343
- # Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
341
+ # Date[https://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
342
+ # Time[https://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
344
343
  # date and time arithmetic.
345
344
  def to_i
346
345
  @value.to_i
@@ -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 = "beta2"
12
+ TINY = 2
13
+ PRE = "rc1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -225,8 +225,8 @@ module ActiveSupport
225
225
  # hash[:a] = 'x'
226
226
  # hash[:b] = 'y'
227
227
  # hash.values_at('a', 'b') # => ["x", "y"]
228
- def values_at(*indices)
229
- indices.collect { |key| self[convert_key(key)] }
228
+ def values_at(*keys)
229
+ super(*keys.map { |key| convert_key(key) })
230
230
  end
231
231
 
232
232
  # Returns an array of the values at the specified indices, but also
@@ -239,7 +239,7 @@ module ActiveSupport
239
239
  # hash.fetch_values('a', 'c') { |key| 'z' } # => ["x", "z"]
240
240
  # hash.fetch_values('a', 'c') # => KeyError: key not found: "c"
241
241
  def fetch_values(*indices, &block)
242
- indices.collect { |key| fetch(key, &block) }
242
+ super(*indices.map { |key| convert_key(key) }, &block)
243
243
  end
244
244
 
245
245
  # Returns a shallow copy of the hash.
@@ -293,6 +293,9 @@ module ActiveSupport
293
293
  super(convert_key(key))
294
294
  end
295
295
 
296
+ def except(*keys)
297
+ slice(*self.keys - keys.map { |key| convert_key(key) })
298
+ end
296
299
  alias_method :without, :except
297
300
 
298
301
  def stringify_keys!; self end
@@ -12,6 +12,10 @@ module I18n
12
12
  config.i18n.load_path = []
13
13
  config.i18n.fallbacks = ActiveSupport::OrderedOptions.new
14
14
 
15
+ if I18n.respond_to?(:eager_load!)
16
+ config.eager_load_namespaces << I18n
17
+ end
18
+
15
19
  # Set the i18n configuration after initialization since a lot of
16
20
  # configuration is still usually done in application initializers.
17
21
  config.after_initialize do |app|
@@ -97,7 +101,8 @@ module I18n
97
101
  If you desire the default locale to be included in the defaults, please
98
102
  explicitly configure it with `config.i18n.fallbacks.defaults =
99
103
  [I18n.default_locale]` or `config.i18n.fallbacks = [I18n.default_locale,
100
- {...}]`
104
+ {...}]`. If you want to opt-in to the new behavior, use
105
+ `config.i18n.fallbacks.defaults = [nil, {...}]`.
101
106
  MSG
102
107
  args.unshift I18n.default_locale
103
108
  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
@@ -20,8 +20,8 @@ module ActiveSupport
20
20
  super
21
21
  end
22
22
 
23
- def subscribe(pattern = nil, block = Proc.new)
24
- subscriber = Subscribers.new pattern, block
23
+ def subscribe(pattern = nil, callable = nil, &block)
24
+ subscriber = Subscribers.new(pattern, callable || block)
25
25
  synchronize do
26
26
  if String === pattern
27
27
  @string_subscribers[pattern] << subscriber
@@ -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
@@ -55,8 +56,9 @@ module ActiveSupport
55
56
  attr_reader :name, :time, :end, :transaction_id, :payload, :children
56
57
 
57
58
  def self.clock_gettime_supported? # :nodoc:
58
- defined?(Process::CLOCK_PROCESS_CPUTIME_ID) &&
59
- !Gem.win_platform?
59
+ defined?(Process::CLOCK_THREAD_CPUTIME_ID) &&
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)
@@ -142,7 +143,7 @@ module ActiveSupport
142
143
 
143
144
  if clock_gettime_supported?
144
145
  def now_cpu
145
- Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID)
146
+ Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID)
146
147
  end
147
148
  else
148
149
  def now_cpu