Safely overriding method_missing in a class that already has it
Today I’m making a ridiculous module. Pretty much all it does is override method_missing. There’s a big problem with that, though. What happens when the class you’re including the module in already has method_missing, and you don’t want to mess that up? After a few failed attempts, watching stack levels get too deep, and a little head-scratching, I made this:
module Hero def self.included(base) base.class_eval do unless method_defined? :method_missing def method_missing(meth, *args, &block); super; end end alias_method :old_method_missing, :method_missing alias_method :method_missing, :hero_method_missing end end def hero_method_missing(meth, *args, &block) if meth.to_s == 'hero' "Did I ever tell you you're my #{meth}?" else old_method_missing(meth, *args, &block) end end end
There’s my trivial example. Hero is ready to be included in any class, method_missing defined or not. It’ll handle whenever you try to call a method named hero, and pass everything else on to whatever (the class’ original method_missing, or its parent’s method_missing). Now you can ruin ActiveRecord without totally ruining ActiveRecord!
The secret is defining method_missing in the class if it isn’t already, just super, so that the double alias_method is cool.
Take a look at it in action:
mvb:~ cms$ irb 001:0> load 'hero.rb' true 002:0> class C;def method_missing(meth);"I'm fancy!";end;end nil 003:0> C.new.hero "I'm fancy!" 004:0> class C; include Hero; end C 005:0> C.new.hero "Did I ever tell you you're my hero?" 006:0> C.new.garbage! "I'm fancy!"
I promise the actual use is more interesting, though possibly just as useless.
Dr Nic said,
July 15, 2007 @ 10:10 pm
Another thing you can try is using Modules. When you include a module in a class, it behaves a bit like superclasses - you can override methods but call
superto access the version you overrode.I’m not sure which is preferred/ruby-fu-esque.
Chris Shea said,
July 15, 2007 @ 10:55 pm
I’m not seeing what you’re seeing Dr. Nic:
Given hero.rb containing:
module Coward def to_i(mod) if mod == 2 'hahaha' else super end end endI get:
mvb:~ cms$ irb 001:0> load 'hero.rb' true 002:0> [1,2].to_i(2) NoMethodError: undefined method `to_i' for [1, 2]:Array from (irb):2 003:0> class Array; include Coward; end Array 004:0> [1,2].to_i(2) "hahaha" 005:0> [1,2].to_i(3) NoMethodError: super: no superclass method `to_i' from ./hero.rb:6:in `to_i' from (irb):5 from :0 006:0> '1101'.to_i(2) 13 007:0> class String; include Coward; end String 008:0> '1101'.to_i(2) 13Dr Nic said,
July 16, 2007 @ 5:13 am
My example was more related to method_missing - http://pastie.caboo.se/79144
But I was wrong.
If a method is defined on the class, then you can only reimplement it with aliases and not modules.
But if you include the module in a subclass of the class with the method to be overridden, then that’s fine - http://pastie.caboo.se/79145
Doh - the last example doesn’t show the use of
superFixed here - http://pastie.caboo.se/79146
Dr Nic said,
July 16, 2007 @ 5:15 am
PS. When you commented, it was the first time your name appears against this article. (its not in the about page either). Not sure if that’s deliberate or perhaps its worth adding your name to the sidebar + the article itself.
ara.t.howard said,
January 15, 2008 @ 1:35 pm
cfp:~ > cat a.rb class A def foo p "A.foo" end end class B end module M NoGC = [] def self.included other other.module_eval do if((foo = instance_method 'foo' rescue false)) NoGC.push foo supra = "ObjectSpace._id2ref(#{ foo.object_id }).bind(self).call(*a, &b)" end eval ruby a.rb "A.foo" "M.foo" "M.foo"