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’.
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