Bug #12206
closedundef_method on prepended module undefines same-name method on prepending class
Description
- Define a class with a method
- Define a module with a same-name method
- Prepend the module on the class
- Undefine the same-name method on the module, not touching the original method on the class
- Observe the class method to also become undefined
class MyClass
def foo
"MyClass#foo"
end
end
module MyMod
def foo
"#{super} prepended by MyMod#foo"
end
end
MyClass.prepend(MyMod)
MyClass.new.foo
# => "MyClass#foo prepended by MyMod#foo"
MyClass.instance_method(:foo) == MyMod.instance_method(:foo)
# => false
MyMod.send(:undef_method, :foo)
# The bug:
MyClass.new.foo
# => NoMethodError: undefined method `foo' for #<MyClass:0x007ffdf29614a8>
Expected behavior: MyClass#foo
would still return "MyClass#foo" since I only undefined foo
on the module, not on the class.
Real life use case: I'm setting up instrumentation code that can track and measure/benchmark call times of specific methods, but after it's done I want to clean up and restore original code the way it was. For me, it would be even better to support to "unprepend" a module from a class instead of undef_method on the mod, but that's more of a feature request than a bug).
Updated by znz (Kazuhiro NISHIYAMA) almost 9 years ago
How about using remove_method
instead of undef_method
?
Updated by EugeneG (Eugene Gilburg) almost 9 years ago
Kazuhiro NISHIYAMA wrote:
How about using
remove_method
instead ofundef_method
?
It worked, thanks! Behavior is still confusing since I undef'd the method on the module rather than the class, but this solves my problem.
Updated by jeremyevans0 (Jeremy Evans) over 5 years ago
- Status changed from Open to Closed
This isn't a bug. undef_method
and remove_method
mean two different things in Ruby:
-
remove_method
: Remove the method with this name from the module/class method table -
undef_method
: Add an entry to the module/class method table, such as Ruby will consider the method not defined if it reaches that entry.
If you prepend MyMod
to MyClass
, that means that calling MyClass.new.foo
looks up methods in the following order:
MyMod -> MyClass -> Object -> Kernel -> BasicObject
. So if you undef_method
in MyMod
, Ruby raises a NoMethodError
when it reaches the entry in MyMod
's method table, before it reaches the method in MyClass
's method table. remove_method
works because it removes the entry in the method table inMyMod
, so lookup proceeds to MyClass
's method table.