Undefining a Method

Problem

You want to remove an already defined method from a class or module.

Solution

From within a class or module, you can use Module#remove_method to remove a method's implementation, forcing Ruby to delegate to the superclass or a module included by a class.

In the code below, I subclass Array and override the << and [] methods to add some randomness. Then I decide that overriding [] wasn't such a good idea, so I undefine that method and get the inherited Array behavior back. The override of << stays in place.

class RandomizingArray < Array def <<(e) insert(rand(size), e) end def [](i) super(rand(size)) end end a = RandomizingArray.new a << 1 << 2 << 3 << 4 << 5 << 6 # => [6, 3, 4, 5, 2, 1] # That was fun; now let's get some of those entries back. a[0] # => 1 a[0] # => 2 a[0] # => 5 #No, seriously, a[0]. a[0] # => 4 #It's a madhouse! A madhouse! a[0] # => 3 #That does it! class RandomizingArray remove_method('[]') end a[0] # => 6 a[0] # => 6 a[0] # => 6 # But the overridden << operator still works randomly: a << 7 # => [6, 3, 4, 7, 5, 2, 1]

 

Discussion

Usually you'll override a method by redefining it to implement your own desired behavior. However, sometimes a class will override an inherited method to do something you don't like, and you just want the "old" implementation back.

You can only use remove_method to remove a method from a class or module that explicitly defines it. You'll get an error if you try to remove a method from a class that merely inherits that method. To make a subclass stop responding to an inherited method, you should undefine the method with undef_method.

Using undef_method on a class prevents the appropriate method signals from reaching objects of that class, but it has no effect on the parent class.

class RandomizingArray remove_method(:length) end # NameError: method 'length' not defined in RandomizingArray class RandomizingArray undef_method(:length) end RandomizingArray.new.length # NoMethodError: undefined method 'length' for []:RandomizingArray Array.new.length # => 0

As you can see, it's generally safer to use undef_method on the class you actually want to change than to use remove_method on its parent or a module it includes.

You can use remove_method to remove singleton methods once you're done with them. Since remove_method is private, using it to remove a singleton method requires some unorthodox syntax:

my_array = Array.new def my_array.random_dump(number) number.times { self << rand(100) } end my_array.random_dump(3) my_array.random_dump(2) my_array # => [6, 45, 12, 49, 66] # That's enough of that. class << my_array remove_method(:random_dump) end my_array.random_dump(4) # NoMethodError: undefined method 'random_dump' for [6, 45, 12, 49, 66]:Array

When you define a singleton method on an object, Ruby silently defines an anonymous subclass used only for that one object. In the example above, my_array is actually an anonymous subclass of Array that implements a method random_dump. Since the subclass has no name (my_array is a variable name, not a class name), there's no way of using the class syntax. We must "append" onto the definition of the my_array object.

Class methods are just a special case of singleton methods, so you can also use remove_method to remove class methods. Ruby also provides a couple of related methods for removing things besides methods. Module#remove_constant undefines a constant so that it can be redefined with a different value, as seen in Recipe 8.17. Object#remove_instance_variable removes an instance variable from a single instance of a class:

class OneTimeContainer def initialize(value) @use_just_once_then_destroy = value end def get_value remove_instance_variable(:@use_just_once_then_destroy) end end object_1 = OneTimeContainer.new(6) object_1.get_value # => 6 object_1.get_value # NameError: instance variable @use_just_once_then_destroy not defined object_2 = OneTimeContainer.new('ephemeron') object_2.get_value # => "ephemeron"

You can't remove a particular instance variable from all instances by modifying the class because the class is its own object, one which probably never defined that instance variable in the first place:

class MyClass remove_instance_variable(:@use_just_once_then_destroy) end # NameError: instance variable @use_just_once_then_destroy not defined

You should definitely not use these methods to remove methods or constants in system classes or modules: that might make arbitrary parts of the Ruby standard library crash or act unreliably. As with all metaprogramming, it's easy to abuse the power to remove and undefine methods at will.

See Also

Категории