#-- # Author:: Tyler Rick # Copyright:: Copyright (c) 2007 QualitySmith, Inc. # License:: Ruby License # Submit to Facets?:: Yes! # Developer notes:: #++ require 'rubygems' require 'qualitysmith_extensions/object/ignore_access' require 'qualitysmith_extensions/module/split' require 'facets/core/module/by_name' require 'facets/core/module/modspace' class Module alias_method :remove_const_before_was_added_to_Kernel, :remove_const end module Kernel # This is similar to the built-in Module#remove_const, but it is accessible from all "levels" (because it is defined # in +Kernel+) and can handle hierarchy. # # Makes it possible to write simply: # remove_const(A::B::C.name) # rather than having to think about which module the constant is actually defined in and calling +remove_const+ on that module. # # This is how you would otherwise have to do it: # A::B.send(:remove_const, :C) # # +const_name+ must be an object that responds to +to_s+. # # +const_name+ must be a fully qualified name. For example, this will not work as expected: # # module Mod # Foo = 'foo' # remove_const(:Foo) # end # # because it will try to remove ::Foo instead of Mod::Foo. Fortunately, however, this will work as expected: # # module Mod # Foo = 'foo' # remove_const(Foo.name) # end # # This method is partially inspired by Facets' Kernel#constant method, which provided a more user-friendly alternative to const_get. # def remove_const(const_name) #require 'pp' #puts "remove_const(#{const_name})" raise ArgumentError unless const_name.respond_to?(:to_s) nesting = const_name.to_s.split(/::/).map(&:to_sym) if nesting.size > 1 parent_module = constant(nesting[0..-2].join('::')) # For example, would be A::B for A::B::C const_to_remove = nesting[-1] # For example, would be :C for A::B::C parent_module.ignore_access.remove_const_before_was_added_to_Kernel(const_to_remove) else ignore_access.remove_const_before_was_added_to_Kernel(const_name) end end end #p Module.private_instance_methods.grep(/remove_const/) # Lists it Module.send(:remove_method, :remove_const) #p Module.instance_methods.grep(/remove_const/) # Does list it, because inherits *public* remove_const from Kernel #p Module.private_instance_methods.grep(/remove_const/) # Does not list it, because it's no longer private Module.send(:define_method, :remove_const, Kernel.method(:remove_const)) #p Module.private_instance_methods.grep(/remove_const/) # Lists it # _____ _ # |_ _|__ ___| |_ # | |/ _ \/ __| __| # | | __/\__ \ |_ # |_|\___||___/\__| # =begin test require 'test/unit' # Important regression test. This was failing at one point. module A B = nil remove_const :B end # How it would be done *without* this extension: module TestRemoveABC_TheOldWay module A module B C = 'foo' end end class TheTest < Test::Unit::TestCase def test_1 assert_nothing_raised { A::B::C } A::B.send(:remove_const, :C) assert_raise(NameError) { A::B::C } end end end # How it would be done *with* this extension (all tests that follow): module TestRemoveABC_CIsString module A module B C = 'foo' end end class TheTest < Test::Unit::TestCase def test_1 assert_nothing_raised { A::B::C } assert_raise(NoMethodError) { remove_const(A::B::C.name) } # Because C is a *string*, not a *module* assert_nothing_raised { remove_const A::B.name + '::C' } assert_raise(NameError) { A::B::C } end end end module TestRemoveAB_UsingName module A module B end end class TheTest < Test::Unit::TestCase def test_1 assert_nothing_raised { A::B } remove_const(A::B.name) assert_raise(NameError) { A::B } end end end module TestRemoveAB_Symbol module A module B Foo = :Foo end end remove_const(:'A::B::Foo') # This tests that Module#remove_const was overriden as well. # If we hadn't also overriden Module#remove_const, then this would have caused this error: # in `remove_const': `A::B::Foo' is not allowed as a constant name (NameError) class TheTest < Test::Unit::TestCase def test_1 assert_nothing_raised { A::B } assert_equal 'TestRemoveAB_Symbol::A', A.name assert_raise(NameError) { remove_const(:'A::B') } # This doesn't work because A, when evaluated in this context, # is TestRemoveAB_Symbol::TheTest::A, which is *not* defined. remove_const(:'TestRemoveAB_Symbol::A::B') assert_raise(NameError) { A::B } end end end module TestRemoveAB_Symbol2 class TheTest < Test::Unit::TestCase module A module B end end def test_1 assert_nothing_raised { A::B } assert_equal 'TestRemoveAB_Symbol2::TheTest::A', A.name remove_const(:'A::B') # Does work, because A is defined *within* TheTest this time. assert_raise(NameError) { A::B } end end end =end