From 071f7d9102f9ca51fc320e9411bfd8817e0ed085 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sun, 17 Jan 2010 10:36:07 +0100 Subject: [PATCH] defines anonymous classes are reachable, adds tests, defines Class#anonymous? --- .../lib/active_support/core_ext/class/removal.rb | 4 -- .../lib/active_support/core_ext/module.rb | 2 + .../active_support/core_ext/module/anonymous.rb | 22 +++++++++++++ .../active_support/core_ext/module/reachable.rb | 22 +++++++++++++ .../active_support/core_ext/object/extending.rb | 4 +- .../test/core_ext/module/anonymous_test.rb | 14 ++++++++ .../test/core_ext/module/reachable_test.rb | 34 ++++++++++++++++++++ 7 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 activesupport/lib/active_support/core_ext/module/anonymous.rb create mode 100644 activesupport/lib/active_support/core_ext/module/reachable.rb create mode 100644 activesupport/test/core_ext/module/anonymous_test.rb create mode 100644 activesupport/test/core_ext/module/reachable_test.rb diff --git a/activesupport/lib/active_support/core_ext/class/removal.rb b/activesupport/lib/active_support/core_ext/class/removal.rb index 652be4e..fe13c66 100644 --- a/activesupport/lib/active_support/core_ext/class/removal.rb +++ b/activesupport/lib/active_support/core_ext/class/removal.rb @@ -3,10 +3,6 @@ require 'active_support/core_ext/module/introspection' class Class #:nodoc: - def reachable? - eval("defined?(::#{self}) && ::#{self}.equal?(self)") - end - # Unassociates the class with its subclasses and removes the subclasses # themselves. # diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb index fbe89fe..4e57d0d 100644 --- a/activesupport/lib/active_support/core_ext/module.rb +++ b/activesupport/lib/active_support/core_ext/module.rb @@ -8,3 +8,5 @@ require 'active_support/core_ext/module/attr_accessor_with_default' require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/module/loading' require 'active_support/core_ext/module/synchronization' +require 'active_support/core_ext/module/reachable' +require 'active_support/core_ext/module/anonymous' diff --git a/activesupport/lib/active_support/core_ext/module/anonymous.rb b/activesupport/lib/active_support/core_ext/module/anonymous.rb new file mode 100644 index 0000000..51631d1 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/module/anonymous.rb @@ -0,0 +1,22 @@ +require 'active_support/core_ext/object/blank' + +class Module + # A module may or may not have a name. + # + # module M; end + # M.name # => "M" + # + # m = Module.new + # m.name # => "" + # + # A module gets a name when it is first assigned to a constant. Either + # via the +module+ or +class+ keyword or by an explicit assignment: + # + # m = Module.new # creates an anonymous module + # M = m # => m gets a name here as a side-effect + # m.name # => "M" + # + def anonymous? + name.blank? + end +end \ No newline at end of file diff --git a/activesupport/lib/active_support/core_ext/module/reachable.rb b/activesupport/lib/active_support/core_ext/module/reachable.rb new file mode 100644 index 0000000..46ea3bb --- /dev/null +++ b/activesupport/lib/active_support/core_ext/module/reachable.rb @@ -0,0 +1,22 @@ +require 'active_support/core_ext/module/anonymous' + +class Module + # A module is reachable if it is anonymous or the constant that corresponds + # to its name is defined and holds a reference to +self+. + # + # This is ordinarily the case, but sometimes module objects become phantoms. + # For example, let's say your application does this in an initializer: + # + # # Don't do this + # ADMIN = User.find_by_login("admin") + # + # There the +User+ class is autoloaded and the +ADMIN+ constant has the admin user. + # When a second request comes in development mode the original +User+ constant is + # removed from +Object+ and a new class with the same name is created. + # + # But the instance in +ADMIN+ has a pointer to the original class object. That + # one is no longer reachable. + def reachable? + anonymous? || eval("defined?(::#{self}) && ::#{self}.equal?(self)") + end +end \ No newline at end of file diff --git a/activesupport/lib/active_support/core_ext/object/extending.rb b/activesupport/lib/active_support/core_ext/object/extending.rb index b8b6970..0d25034 100644 --- a/activesupport/lib/active_support/core_ext/object/extending.rb +++ b/activesupport/lib/active_support/core_ext/object/extending.rb @@ -1,5 +1,5 @@ require 'active_support/core_ext/class/removal' -require 'active_support/core_ext/object/blank' +require 'active_support/core_ext/module/reachable' class Class # Rubinius @@ -47,7 +47,7 @@ class Object def subclasses_of(*superclasses) #:nodoc: subclasses = [] superclasses.each do |klass| - subclasses.concat klass.descendents.select {|k| k.name.blank? || k.reachable?} + subclasses.concat klass.descendents.select(&:reachable?) end subclasses end diff --git a/activesupport/test/core_ext/module/anonymous_test.rb b/activesupport/test/core_ext/module/anonymous_test.rb new file mode 100644 index 0000000..64cc330 --- /dev/null +++ b/activesupport/test/core_ext/module/anonymous_test.rb @@ -0,0 +1,14 @@ +require 'abstract_unit' +require 'active_support/core_ext/module/anonymous' + +class AnonymousTest < ActiveSupport::TestCase + test "an anonymous module is anonymous" do + assert Module.new.anonymous? + end + + test "a named module is not anonymous" do + eval "module M; end" + assert !M.anonymous? + end +end + diff --git a/activesupport/test/core_ext/module/reachable_test.rb b/activesupport/test/core_ext/module/reachable_test.rb new file mode 100644 index 0000000..77e8837 --- /dev/null +++ b/activesupport/test/core_ext/module/reachable_test.rb @@ -0,0 +1,34 @@ +require 'abstract_unit' +require 'active_support/core_ext/module/reachable' + +class ReachableTest < ActiveSupport::TestCase + def new_module(name) + eval "module ::#{name}; end" + end + + def remove_module(name) + Object.send(:remove_const, name) + end + + test "anonymous modules are reachable" do + assert Module.new.reachable? + end + + test "ordinary modules are reachable" do + new_module(:M) + assert M.reachable? + end + + test "modules whose constant is gone are not reachable" do + new_module(:M1) + phantom = remove_module(:M1) + assert !phantom.reachable? + end + + test "modules whose constant is defined but holds a different object are not reachable" do + new_module(:M2) + phantom = remove_module(:M2) + new_module(:M2) + assert !phantom.reachable? + end +end \ No newline at end of file -- 1.6.6