Skip to content
Browse files

Reinstate Object.subclasses_of and Class#descendents for plugin compat.

This reverts commits 7d312e5, 5f981ff, f85f5df, 245bfaf, and ec7c642
  • Loading branch information...
1 parent fa2ff95 commit d1938953f416ac945b591251567d51e30ee2a6a4 @jeremy jeremy committed Feb 9, 2010
View
1 activesupport/lib/active_support/core_ext/class.rb
@@ -1,3 +1,4 @@
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/class/inheritable_attributes'
require 'active_support/core_ext/class/delegating_attributes'
+require 'active_support/core_ext/class/subclasses'
View
58 activesupport/lib/active_support/core_ext/class/subclasses.rb
@@ -0,0 +1,58 @@
+require 'active_support/core_ext/object/blank'
+
+class Class #:nodoc:
+ # Returns an array with the names of the subclasses of +self+ as strings.
+ #
+ # Integer.subclasses # => ["Bignum", "Fixnum"]
+ def subclasses
+ Class.subclasses_of(self).map { |o| o.to_s }
+ end
+
+ def reachable? #:nodoc:
+ eval("defined?(::#{self}) && ::#{self}.equal?(self)")
+ end
+
+ # Rubinius
+ if defined?(Class.__subclasses__)
+ def descendents
+ subclasses = []
+ __subclasses__.each {|k| subclasses << k; subclasses.concat k.descendents }
+ subclasses
+ end
+ else
+ # MRI
+ begin
+ ObjectSpace.each_object(Class.new) {}
+
+ def descendents
+ subclasses = []
+ ObjectSpace.each_object(class << self; self; end) do |k|
+ subclasses << k unless k == self
+ end
+ subclasses
+ end
+ # JRuby
+ rescue StandardError
+ def descendents
+ subclasses = []
+ ObjectSpace.each_object(Class) do |k|
+ subclasses << k if k < self
+ end
+ subclasses.uniq!
+ subclasses
+ end
+ end
+ end
+
+ # Exclude this class unless it's a subclass of our supers and is defined.
+ # We check defined? in case we find a removed class that has yet to be
+ # garbage collected. This also fails for anonymous classes -- please
+ # submit a patch if you have a workaround.
+ def self.subclasses_of(*superclasses) #:nodoc:
+ subclasses = []
+ superclasses.each do |klass|
+ subclasses.concat klass.descendents.select {|k| k.name.blank? || k.reachable?}
+ end
+ subclasses
+ end
+end
View
11 activesupport/lib/active_support/core_ext/object/extending.rb
@@ -0,0 +1,11 @@
+require 'active_support/core_ext/class/subclasses'
+
+class Object
+ # Exclude this class unless it's a subclass of our supers and is defined.
+ # We check defined? in case we find a removed class that has yet to be
+ # garbage collected. This also fails for anonymous classes -- please
+ # submit a patch if you have a workaround.
+ def subclasses_of(*superclasses) #:nodoc:
+ Class.subclasses_of(*superclasses)
+ end
+end
View
29 activesupport/test/core_ext/class_test.rb
@@ -0,0 +1,29 @@
+require 'abstract_unit'
+require 'active_support/core_ext/class'
+
+class A
+end
+
+module X
+ class B
+ end
+end
+
+module Y
+ module Z
+ class C
+ end
+ end
+end
+
+class ClassTest < Test::Unit::TestCase
+ def test_retrieving_subclasses
+ @parent = eval("class D; end; D")
+ @sub = eval("class E < D; end; E")
+ @subofsub = eval("class F < E; end; F")
+ assert_equal 2, @parent.subclasses.size
+ assert_equal [@subofsub.to_s], @sub.subclasses
+ assert_equal [], @subofsub.subclasses
+ assert_equal [@sub.to_s, @subofsub.to_s].sort, @parent.subclasses.sort
+ end
+end
View
50 activesupport/test/core_ext/object_and_class_ext_test.rb
@@ -1,6 +1,7 @@
require 'abstract_unit'
require 'active_support/time'
require 'active_support/core_ext/object'
+require 'active_support/core_ext/class/subclasses'
class ClassA; end
class ClassB < ClassA; end
@@ -39,6 +40,55 @@ class Foo
include Bar
end
+class ClassExtTest < Test::Unit::TestCase
+ def test_subclasses_of_should_find_nested_classes
+ assert Class.subclasses_of(ClassK).include?(Nested::ClassL)
+ end
+
+ def test_subclasses_of_should_not_return_removed_classes
+ # First create the removed class
+ old_class = Nested.class_eval { remove_const :ClassL }
+ new_class = Class.new(ClassK)
+ Nested.const_set :ClassL, new_class
+ assert_equal "Nested::ClassL", new_class.name # Sanity check
+
+ subclasses = Class.subclasses_of(ClassK)
+ assert subclasses.include?(new_class)
+ assert ! subclasses.include?(old_class)
+ ensure
+ Nested.const_set :ClassL, old_class unless defined?(Nested::ClassL)
+ end
+
+ def test_subclasses_of_should_not_trigger_const_missing
+ const_missing = false
+ Nested.on_const_missing { const_missing = true }
+
+ subclasses = Class.subclasses_of ClassK
+ assert !const_missing
+ assert_equal [ Nested::ClassL ], subclasses
+
+ removed = Nested.class_eval { remove_const :ClassL } # keep it in memory
+ subclasses = Class.subclasses_of ClassK
+ assert !const_missing
+ assert subclasses.empty?
+ ensure
+ Nested.const_set :ClassL, removed unless defined?(Nested::ClassL)
+ end
+
+ def test_subclasses_of_with_multiple_roots
+ classes = Class.subclasses_of(ClassI, ClassK)
+ assert_equal %w(ClassJ Nested::ClassL), classes.collect(&:to_s).sort
+ end
+
+ def test_subclasses_of_doesnt_find_anonymous_classes
+ assert_equal [], Class.subclasses_of(Foo)
+ bar = Class.new(Foo)
+ assert_nothing_raised do
+ assert_equal [bar], Class.subclasses_of(Foo)
+ end
+ end
+end
+
class ObjectTests < Test::Unit::TestCase
class DuckTime
def acts_like_time?

2 comments on commit d193895

@josh
Ruby on Rails member
josh commented on d193895 Feb 10, 2010

Why not put deprecation notices on those methods?

@jeremy
Ruby on Rails member
jeremy commented on d193895 Feb 10, 2010

Just reverting since it broke some plugins and the 3.0 upgrade should be smoother.

Please sign in to comment.
Something went wrong with that request. Please try again.