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) over 9 years ago
How about using remove_method instead of undef_method ?
Updated by EugeneG (Eugene Gilburg) over 9 years ago
Kazuhiro NISHIYAMA wrote:
How about using
remove_methodinstead 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 6 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.