From a5dda97602f2188a13cbcab5c7e9a5ba84ba876b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 5 Jul 2010 12:50:08 +0200 Subject: Define a convention for descendants and subclasses. The former should be symmetric with ancestors and include all children. However, it should not include self since ancestors + descendants should not have duplicated. The latter is symmetric to superclass in the sense it only includes direct children. By adopting a convention, we expect to have less conflict with other frameworks, as Datamapper. For this moment, to ensure ActiveModel::Validations can be used with Datamapper, we should always call ActiveSupport::DescendantsTracker.descendants(self) internally instead of self.descendants avoiding conflicts. --- actionpack/lib/action_controller/base.rb | 6 -- actionpack/lib/action_controller/railtie.rb | 1 - .../action_dispatch/routing/deprecated_mapper.rb | 4 +- activerecord/lib/active_record/observer.rb | 6 +- activesupport/lib/active_support/callbacks.rb | 4 +- .../active_support/core_ext/class/subclasses.rb | 57 +++++++-------- .../lib/active_support/core_ext/object.rb | 1 - .../active_support/core_ext/object/extending.rb | 11 --- .../lib/active_support/descendants_tracker.rb | 24 ++++--- activesupport/test/core_ext/class_test.rb | 40 +++++------ .../test/core_ext/object_and_class_ext_test.rb | 49 ------------- activesupport/test/descendants_tracker_test.rb | 8 ++- .../source/active_support_core_extensions.textile | 81 ++++++++-------------- 13 files changed, 100 insertions(+), 192 deletions(-) delete mode 100644 activesupport/lib/active_support/core_ext/object/extending.rb diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index a70ba0d2e3..1a2cbaab65 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -60,17 +60,11 @@ module ActionController include ActionController::Compatibility def self.inherited(klass) - ::ActionController::Base.subclasses << klass.to_s super klass.helper :all end - def self.subclasses - @subclasses ||= [] - end - config_accessor :asset_host, :asset_path - ActiveSupport.run_load_hooks(:action_controller, self) end end diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 86395c4d93..9261422f0b 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -2,7 +2,6 @@ require "rails" require "action_controller" require "action_dispatch/railtie" require "action_view/railtie" -require "active_support/core_ext/class/subclasses" require "active_support/deprecation/proxy_wrappers" require "active_support/deprecation" diff --git a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb index e122bdf232..05e8dfded6 100644 --- a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb +++ b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb @@ -19,8 +19,8 @@ module ActionDispatch def in_memory_controller_namespaces namespaces = Set.new - ActionController::Base.subclasses.each do |klass| - controller_name = klass.underscore + ActionController::Base.descendants.each do |klass| + controller_name = klass.name.underscore namespaces << controller_name.split('/')[0...-1].join('/') end namespaces.delete('') diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb index 5f80bd86df..d2ed643f35 100644 --- a/activerecord/lib/active_record/observer.rb +++ b/activerecord/lib/active_record/observer.rb @@ -94,7 +94,7 @@ module ActiveRecord def initialize super - observed_subclasses.each { |klass| add_observer!(klass) } + observed_descendants.each { |klass| add_observer!(klass) } end def self.method_added(method) @@ -108,8 +108,8 @@ module ActiveRecord protected - def observed_subclasses - observed_classes.sum([]) { |klass| klass.send(:descendants) } + def observed_descendants + observed_classes.sum([]) { |klass| klass.descendants } end def observe_callbacks? diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index c4e1eb2c04..1c7802f7de 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -432,7 +432,7 @@ module ActiveSupport options = filters.last.is_a?(Hash) ? filters.pop : {} filters.unshift(block) if block - ([self] + self.descendants).each do |target| + ([self] + ActiveSupport::DescendantsTracker.descendants(self)).each do |target| chain = target.send("_#{name}_callbacks") yield chain, type, filters, options target.__define_runner(name) @@ -506,7 +506,7 @@ module ActiveSupport def reset_callbacks(symbol) callbacks = send("_#{symbol}_callbacks") - self.descendants.each do |target| + ActiveSupport::DescendantsTracker.descendants(self).each do |target| chain = target.send("_#{symbol}_callbacks") callbacks.each { |c| chain.delete(c) } target.__define_runner(symbol) diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb index 7d58a8b56a..3e5d1a2a42 100644 --- a/activesupport/lib/active_support/core_ext/class/subclasses.rb +++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb @@ -2,54 +2,49 @@ require 'active_support/core_ext/module/anonymous' require 'active_support/core_ext/module/reachable' 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 - # Rubinius if defined?(Class.__subclasses__) + alias :subclasses :__subclasses__ + def descendants - subclasses = [] - __subclasses__.each {|k| subclasses << k; subclasses.concat k.descendants } - subclasses + descendants = [] + __subclasses__.each do |k| + descendants << k + descendants.concat k.descendants + end + descendants end - else - # MRI + else # MRI begin ObjectSpace.each_object(Class.new) {} def descendants - subclasses = [] + descendants = [] ObjectSpace.each_object(class << self; self; end) do |k| - subclasses << k unless k == self + descendants.unshift k unless k == self end - subclasses + descendants end - # JRuby - rescue StandardError + rescue StandardError # JRuby def descendants - subclasses = [] + descendants = [] ObjectSpace.each_object(Class) do |k| - subclasses << k if k < self + descendants.unshift k if k < self end - subclasses.uniq! - subclasses + descendants.uniq! + descendants 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.descendants.select {|k| k.anonymous? || k.reachable?} + # Returns an array with the direct children of +self+. + # + # Integer.subclasses # => [Bignum, Fixnum] + def subclasses + subclasses, chain = [], descendants + chain.each do |k| + subclasses << k unless chain.any? { |c| c > k } + end + subclasses end - subclasses end end diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb index 8922016cd7..27618b55c6 100644 --- a/activesupport/lib/active_support/core_ext/object.rb +++ b/activesupport/lib/active_support/core_ext/object.rb @@ -6,7 +6,6 @@ require 'active_support/core_ext/object/try' require 'active_support/core_ext/object/conversions' require 'active_support/core_ext/object/instance_variables' require 'active_support/core_ext/object/misc' -require 'active_support/core_ext/object/extending' require 'active_support/core_ext/object/returning' require 'active_support/core_ext/object/to_json' diff --git a/activesupport/lib/active_support/core_ext/object/extending.rb b/activesupport/lib/active_support/core_ext/object/extending.rb deleted file mode 100644 index c4c37b6a2a..0000000000 --- a/activesupport/lib/active_support/core_ext/object/extending.rb +++ /dev/null @@ -1,11 +0,0 @@ -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 diff --git a/activesupport/lib/active_support/descendants_tracker.rb b/activesupport/lib/active_support/descendants_tracker.rb index a587d7770c..6cba84d79e 100644 --- a/activesupport/lib/active_support/descendants_tracker.rb +++ b/activesupport/lib/active_support/descendants_tracker.rb @@ -4,16 +4,23 @@ module ActiveSupport # This module provides an internal implementation to track descendants # which is faster than iterating through ObjectSpace. module DescendantsTracker - @@descendants = Hash.new { |h, k| h[k] = [] } + @@direct_descendants = Hash.new { |h, k| h[k] = [] } - def self.descendants - @@descendants + def self.direct_descendants(klass) + @@direct_descendants[klass] + end + + def self.descendants(klass) + @@direct_descendants[klass].inject([]) do |descendants, klass| + descendants << klass + descendants.concat klass.descendants + end end def self.clear - @@descendants.each do |klass, descendants| + @@direct_descendants.each do |klass, descendants| if ActiveSupport::Dependencies.autoloaded?(klass) - @@descendants.delete(klass) + @@direct_descendants.delete(klass) else descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) } end @@ -26,14 +33,11 @@ module ActiveSupport end def direct_descendants - @@descendants[self] + DescendantsTracker.direct_descendants(self) end def descendants - @@descendants[self].inject([]) do |descendants, klass| - descendants << klass - descendants.concat klass.descendants - end + DescendantsTracker.descendants(self) end end end \ No newline at end of file diff --git a/activesupport/test/core_ext/class_test.rb b/activesupport/test/core_ext/class_test.rb index b7f3dd9930..08bb13dd35 100644 --- a/activesupport/test/core_ext/class_test.rb +++ b/activesupport/test/core_ext/class_test.rb @@ -1,29 +1,27 @@ require 'abstract_unit' require 'active_support/core_ext/class' -class A -end +class ClassTest < Test::Unit::TestCase + class Parent; end + class Foo < Parent; end + class Bar < Foo; end + class Baz < Bar; end -module X - class B - end -end + class A < Parent; end + class B < A; end + class C < B; end -module Y - module Z - class C - end + def test_descendants + assert_equal [Foo, Bar, Baz, A, B, C], Parent.descendants + assert_equal [Bar, Baz], Foo.descendants + assert_equal [Baz], Bar.descendants + assert_equal [], Baz.descendants 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 + def test_subclasses + assert_equal [Foo, A], Parent.subclasses + assert_equal [Bar], Foo.subclasses + assert_equal [Baz], Bar.subclasses + assert_equal [], Baz.subclasses end -end +end \ No newline at end of file diff --git a/activesupport/test/core_ext/object_and_class_ext_test.rb b/activesupport/test/core_ext/object_and_class_ext_test.rb index 5e03389dc2..6588f2e345 100644 --- a/activesupport/test/core_ext/object_and_class_ext_test.rb +++ b/activesupport/test/core_ext/object_and_class_ext_test.rb @@ -40,55 +40,6 @@ 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 < ActiveSupport::TestCase class DuckTime def acts_like_time? diff --git a/activesupport/test/descendants_tracker_test.rb b/activesupport/test/descendants_tracker_test.rb index ff24e310de..79fb893592 100644 --- a/activesupport/test/descendants_tracker_test.rb +++ b/activesupport/test/descendants_tracker_test.rb @@ -37,7 +37,9 @@ class DescendantsTrackerTest < Test::Unit::TestCase def test_clear_with_autoloaded_parent_children_and_granchildren mark_as_autoloaded(*ALL) do ActiveSupport::DescendantsTracker.clear - assert ActiveSupport::DescendantsTracker.descendants.slice(*ALL).empty? + ALL.each do |k| + assert ActiveSupport::DescendantsTracker.descendants(k).empty? + end end end @@ -64,12 +66,12 @@ class DescendantsTrackerTest < Test::Unit::TestCase old_autoloaded = ActiveSupport::Dependencies.autoloaded_constants.dup ActiveSupport::Dependencies.autoloaded_constants = klasses.map(&:name) - old_descendants = ActiveSupport::DescendantsTracker.descendants.dup + old_descendants = ActiveSupport::DescendantsTracker.class_eval("@@direct_descendants").dup old_descendants.each { |k, v| old_descendants[k] = v.dup } yield ensure ActiveSupport::Dependencies.autoloaded_constants = old_autoloaded - ActiveSupport::DescendantsTracker.descendants.replace(old_descendants) + ActiveSupport::DescendantsTracker.class_eval("@@direct_descendants").replace(old_descendants) end end \ No newline at end of file diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 844b9428bd..de0c00ac68 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -387,40 +387,6 @@ TIP: Since +with_options+ forwards calls to its receiver they can be nested. Eac NOTE: Defined in +active_support/core_ext/object/with_options.rb+. -h5. +subclasses_of+ - -The method +subclasses_of+ receives an arbitrary number of class objects and returns all their anonymous or reachable descendants as a single array: - - -class C; end -subclasses_of(C) # => [] - -subclasses_of(Integer) # => [Bignum, Fixnum] - -module M - class A; end - class B1 < A; end - class B2 < A; end -end - -module N - class C < M::B1; end -end - -subclasses_of(M::A) # => [N::C, M::B2, M::B1] - - -The order in which these classes are returned is unspecified. The returned collection may have duplicates: - - -subclasses_of(Numeric, Integer) -# => [Bignum, Float, Fixnum, Integer, Date::Infinity, Rational, BigDecimal, Bignum, Fixnum] - - -See also +Class#subclasses+ in "Extensions to +Class+ FIX THIS LINK":FIXME. - -NOTE: Defined in +active_support/core_ext/object/extending.rb+. - h4. Instance Variables Active Support provides several methods to ease access to instance variables. @@ -1141,36 +1107,47 @@ If for whatever reason an application loads the definition of a mailer class and NOTE: Defined in +active_support/core_ext/class/delegating_attributes.rb+. -h4. Descendants +h4. Descendants & Subclasses -h5. +subclasses+ +h5. +descendants+ -The +subclasses+ method returns the names of all the anonymous or reachable descendants of its receiver as an array of strings: +The +descendants+ method returns all classes, including its children, that inherits from self. class C; end -C.subclasses # => [] - -Integer.subclasses # => ["Bignum", "Fixnum"] +C.descendants #=> [] -module M - class A; end - class B1 < A; end - class B2 < A; end -end +class B < C; end +C.descendants #=> [B] -module N - class C < M::B1; end -end +class A < B; end +C.descendants #=> [B, A] -M::A.subclasses # => ["N::C", "M::B2", "M::B1"] +class D < C; end +C.descendants #=> [B, A, D] -The order in which these class names are returned is unspecified. +h5. +subclasses+ + +The +subclasses+ method returns all direct classes that inherits from self. + + +class C; end +C.subclasses #=> [] + +class B < C; end +C.subclasses #=> [B] + +class A < B; end +C.subclasses #=> [B] + +class D < C; end +C.subclasses #=> [B, D] + -See also +Object#subclasses_of+ in "Extensions to All Objects FIX THIS LINK":FIXME. +The order in which these class are returned is unspecified. -WARNING: This method is redefined in some Rails core classes. +WARNING: This method is redefined in some Rails core classes but should be all compatible in Rails 3.1. NOTE: Defined in +active_support/core_ext/class/subclasses.rb+. -- cgit v1.2.3