diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 04e45a01e2a..a9c06b71445 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,8 @@ +* Fix `Class#descendants` and `DescendantsTracker#descendants` compatibility with Ruby 3.1. + [The native `Class#descendants` was reverted prior to Ruby 3.1 release](https://bugs.ruby-lang.org/issues/14394#note-33), + but `Class#subclasses` was kept, breaking the feature detection. + + *Jean Boussier* Please check [7-0-stable](https://github.com/rails/rails/blob/7-0-stable/activesupport/CHANGELOG.md) for previous changes. diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb index b2d4dc7220e..11c59685726 100644 --- a/activesupport/lib/active_support/core_ext/class/subclasses.rb +++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb @@ -3,24 +3,32 @@ require "active_support/ruby_features" class Class - # Returns an array with all classes that are < than its receiver. - # - # class C; end - # C.descendants # => [] - # - # class B < C; end - # C.descendants # => [B] - # - # class A < B; end - # C.descendants # => [B, A] - # - # class D < C; end - # C.descendants # => [B, A, D] - def descendants - ObjectSpace.each_object(singleton_class).reject do |k| - k.singleton_class? || k == self + unless ActiveSupport::RubyFeatures::CLASS_DESCENDANTS + if ActiveSupport::RubyFeatures::CLASS_SUBCLASSES + def descendants + subclasses.concat(subclasses.flat_map(&:descendants)) + end + else + # Returns an array with all classes that are < than its receiver. + # + # class C; end + # C.descendants # => [] + # + # class B < C; end + # C.descendants # => [B] + # + # class A < B; end + # C.descendants # => [B, A] + # + # class D < C; end + # C.descendants # => [B, A, D] + def descendants + ObjectSpace.each_object(singleton_class).reject do |k| + k.singleton_class? || k == self + end + end end - end unless ActiveSupport::RubyFeatures::CLASS_SUBCLASSES + end # Returns an array with the direct children of +self+. # diff --git a/activesupport/lib/active_support/descendants_tracker.rb b/activesupport/lib/active_support/descendants_tracker.rb index 425d9ef8201..0c761d4f117 100644 --- a/activesupport/lib/active_support/descendants_tracker.rb +++ b/activesupport/lib/active_support/descendants_tracker.rb @@ -51,7 +51,7 @@ module ActiveSupport unless @clear_disabled @clear_disabled = true remove_method(:subclasses) - remove_method(:descendants) + remove_method(:descendants) if RubyFeatures::CLASS_DESCENDANTS @@excluded_descendants = nil end end @@ -86,10 +86,16 @@ module ActiveSupport subclasses end - def descendants - descendants = super - descendants.reject! { |d| @@excluded_descendants[d] } - descendants + if RubyFeatures::CLASS_DESCENDANTS + def descendants + descendants = super + descendants.reject! { |d| @@excluded_descendants[d] } + descendants + end + else + def descendants + children.concat(children.flat_map(&:descendants)) + end end def direct_descendants diff --git a/activesupport/lib/active_support/ruby_features.rb b/activesupport/lib/active_support/ruby_features.rb index 8cdb89c20e0..30b8b13440a 100644 --- a/activesupport/lib/active_support/ruby_features.rb +++ b/activesupport/lib/active_support/ruby_features.rb @@ -3,5 +3,6 @@ module ActiveSupport module RubyFeatures # :nodoc: CLASS_SUBCLASSES = Class.method_defined?(:subclasses) # RUBY_VERSION >= "3.1" + CLASS_DESCENDANTS = Class.method_defined?(:descendants) end end