Ruby 101: method_missing gotchas
Forgetting ‘super’ with ‘method_missing’
method_missing is a hallmark of Ruby metaprogramming. It is one of those coding techniques that you need to master if you want to move from white belt to black belt. Aside from that, it is also fun to use.
class RadioActive
def initialize(path)
...
end
def to_format(format)
...
end
end
d = RadioActive.new('/path/to/uranium')
d.to_format('xml')
It works but you want explicit methods for each format. So you tap into your Ruby skills and implement your own method_missing.
class RadioActive
def method_missing(name, *args)
if name.to_s =~ /^to_(\w+)$/
to_format($1)
end
end
end
d = RadioActive.new('/path/to/uranium')
d.to_xml # WORKS
d.to_format('xml') # WORKS
d.undefined_method # FAILS
Unfortunately, the last call to ‘undefined_method’ fails. Actually, you would not know it fails because Ruby will not fire any exception. In case there is an undefined method, let us see how Ruby handles it.
>> s = 'uranium' => "uranium" >> s.to_xml NoMethodError: undefined method `to_xml' for "uranium":String
There you go. But there is no need to raise the ‘NoMethodError’ in your code. Instead, simply call ‘super’ if you are not handling the method. Whether you have your own class or inheriting from another, do not forget to call ‘super’ with your ‘method_missing’
class RadioActive
def method_missing(name, *args)
if name.to_s =~ /^to_(\w+)$/
to_format($1)
else
super
end
end
end
d = RadioActive.new('/path/to/uranium')
d.undefined_method
# => in `method_missing': undefined method `undefined_method' for # (NoMethodError)
Calling ‘super’ is not just for ‘missing_method’. You also need to do the same for the other hook methods like ‘const_missing’, ‘append_features’, ‘method_added’.
Forgetting respond_to?
If you modify ‘method_missing’, it will also affect the behavior of ‘respond_to?’ because what you are adding in as methods do not actually exist — they are ghost methods. If you check the list of instance methods for our class, it will only show 2.
>> RadioActive.instance_methods(false)
=> ["method_missing", "to_format"]
>> d.respond_to?('to_format')
=> true
>> d.respond_to?('to_xml')
=> false
Every time you modify ‘method_missing’, you also need to update ‘respond_to?’
class RadioActive
def respond_to?(name)
!!(name.to_s =~ /^to_/ || super)
end
end