aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport')
-rw-r--r--activesupport/lib/active_support.rb1
-rw-r--r--activesupport/lib/active_support/callbacks.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/class/subclasses.rb10
-rw-r--r--activesupport/lib/active_support/descendants_tracker.rb39
-rw-r--r--activesupport/test/descendants_tracker_test.rb75
5 files changed, 127 insertions, 8 deletions
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index e34e46b4cf..f93b351655 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -51,6 +51,7 @@ module ActiveSupport
autoload :Concern
autoload :Configurable
autoload :Deprecation
+ autoload :DescendantsTracker
autoload :Gzip
autoload :Inflector
autoload :JSON
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 3ff33eea72..c4e1eb2c04 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -1,6 +1,6 @@
+require 'active_support/descendants_tracker'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/class/inheritable_attributes'
-require 'active_support/core_ext/class/subclasses'
require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/kernel/singleton_class'
@@ -85,6 +85,10 @@ module ActiveSupport
module Callbacks
extend Concern
+ included do
+ extend ActiveSupport::DescendantsTracker
+ end
+
def run_callbacks(kind, *args, &block)
send("_run_#{kind}_callbacks", *args, &block)
end
@@ -428,7 +432,7 @@ module ActiveSupport
options = filters.last.is_a?(Hash) ? filters.pop : {}
filters.unshift(block) if block
- ([self] + self.descendents).each do |target|
+ ([self] + self.descendants).each do |target|
chain = target.send("_#{name}_callbacks")
yield chain, type, filters, options
target.__define_runner(name)
@@ -502,7 +506,7 @@ module ActiveSupport
def reset_callbacks(symbol)
callbacks = send("_#{symbol}_callbacks")
- self.descendents.each do |target|
+ self.descendants.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 bbd8f5aef6..7d58a8b56a 100644
--- a/activesupport/lib/active_support/core_ext/class/subclasses.rb
+++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb
@@ -11,9 +11,9 @@ class Class #:nodoc:
# Rubinius
if defined?(Class.__subclasses__)
- def descendents
+ def descendants
subclasses = []
- __subclasses__.each {|k| subclasses << k; subclasses.concat k.descendents }
+ __subclasses__.each {|k| subclasses << k; subclasses.concat k.descendants }
subclasses
end
else
@@ -21,7 +21,7 @@ class Class #:nodoc:
begin
ObjectSpace.each_object(Class.new) {}
- def descendents
+ def descendants
subclasses = []
ObjectSpace.each_object(class << self; self; end) do |k|
subclasses << k unless k == self
@@ -30,7 +30,7 @@ class Class #:nodoc:
end
# JRuby
rescue StandardError
- def descendents
+ def descendants
subclasses = []
ObjectSpace.each_object(Class) do |k|
subclasses << k if k < self
@@ -48,7 +48,7 @@ class Class #:nodoc:
def self.subclasses_of(*superclasses) #:nodoc:
subclasses = []
superclasses.each do |klass|
- subclasses.concat klass.descendents.select {|k| k.anonymous? || k.reachable?}
+ subclasses.concat klass.descendants.select {|k| k.anonymous? || k.reachable?}
end
subclasses
end
diff --git a/activesupport/lib/active_support/descendants_tracker.rb b/activesupport/lib/active_support/descendants_tracker.rb
new file mode 100644
index 0000000000..a587d7770c
--- /dev/null
+++ b/activesupport/lib/active_support/descendants_tracker.rb
@@ -0,0 +1,39 @@
+require 'active_support/dependencies'
+
+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] = [] }
+
+ def self.descendants
+ @@descendants
+ end
+
+ def self.clear
+ @@descendants.each do |klass, descendants|
+ if ActiveSupport::Dependencies.autoloaded?(klass)
+ @@descendants.delete(klass)
+ else
+ descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) }
+ end
+ end
+ end
+
+ def inherited(base)
+ self.direct_descendants << base
+ super
+ end
+
+ def direct_descendants
+ @@descendants[self]
+ end
+
+ def descendants
+ @@descendants[self].inject([]) do |descendants, klass|
+ descendants << klass
+ descendants.concat klass.descendants
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activesupport/test/descendants_tracker_test.rb b/activesupport/test/descendants_tracker_test.rb
new file mode 100644
index 0000000000..3a424180de
--- /dev/null
+++ b/activesupport/test/descendants_tracker_test.rb
@@ -0,0 +1,75 @@
+require 'abstract_unit'
+require 'test/unit'
+require 'active_support'
+require 'active_support/core_ext/hash/slice'
+
+class DescendantsTrackerTest < Test::Unit::TestCase
+ class Parent
+ extend ActiveSupport::DescendantsTracker
+ end
+
+ class Child1 < Parent
+ end
+
+ class Child2 < Parent
+ end
+
+ class Grandchild1 < Child1
+ end
+
+ class Grandchild2 < Child1
+ end
+
+ ALL = [Parent, Child1, Child2, Grandchild1, Grandchild2]
+
+ def test_descendants
+ assert_equal [Child1, Grandchild1, Grandchild2, Child2], Parent.descendants
+ assert_equal [Grandchild1, Grandchild2], Child1.descendants
+ assert_equal [], Child2.descendants
+ end
+
+ def test_direct_descendants
+ assert_equal [Child1, Child2], Parent.direct_descendants
+ assert_equal [Grandchild1, Grandchild2], Child1.direct_descendants
+ assert_equal [], Child2.direct_descendants
+ end
+
+ def test_clear_with_autoloaded_parent_children_and_granchildren
+ mark_as_autoloaded *ALL do
+ ActiveSupport::DescendantsTracker.clear
+ assert ActiveSupport::DescendantsTracker.descendants.slice(*ALL).empty?
+ end
+ end
+
+ def test_clear_with_autoloaded_children_and_granchildren
+ mark_as_autoloaded Child1, Grandchild1, Grandchild2 do
+ ActiveSupport::DescendantsTracker.clear
+ assert_equal [Child2], Parent.descendants
+ assert_equal [], Child2.descendants
+ end
+ end
+
+ def test_clear_with_autoloaded_granchildren
+ mark_as_autoloaded Grandchild1, Grandchild2 do
+ ActiveSupport::DescendantsTracker.clear
+ assert_equal [Child1, Child2], Parent.descendants
+ assert_equal [], Child1.descendants
+ assert_equal [], Child2.descendants
+ end
+ end
+
+ protected
+
+ def mark_as_autoloaded(*klasses)
+ old_autoloaded = ActiveSupport::Dependencies.autoloaded_constants.dup
+ ActiveSupport::Dependencies.autoloaded_constants = klasses.map(&:name)
+
+ old_descendants = ActiveSupport::DescendantsTracker.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)
+ end
+end \ No newline at end of file