As a new developer to Ruby you might wonder how certain methods seem to
be magically available without being strictly defined. Rails’s dynamic
finders (e.g. find_by_name
) are one example of this kind of magic. It’s
very simple to implement magic such as this in Ruby, but it’s also easy
to implement things in a way that doesn’t entirely mesh with standard
Ruby object expectations.
Your Friend method_missing
The way that many magic methods are implemented is by overriding
method_missing
. This special method in Ruby is automatically called by
the interpreter whenever a method is called that cannot be found. The
default behavior of method_missing
is to raise a NoMethodError
letting the user know that the method that was called does not exist.
However, by overriding this behavior we can allow the user to call
methods that aren’t strictly defined but rather programatically
determined at runtime. Let’s look at a simple example:
class Nullifier
def method_missing(*args)
nil
end
end
nullifier = Nullifier.new
nullifier.some_method # => nil
nullifier.foo(:bar, :baz) # => nil
Here we simply told method_missing
to immediately return nil, regardless
of the method name or arguments passed. This essentially means that, for
this class, any method call that is not defined on Object
(the default superclass for new classes) will return nil.
While this example is certainly interesting, it doesn’t necessarily give us more use in the real world. Let’s take another example that actually does something useful. Let’s make a hash that allows us to access its keys by making method calls:
class SuperHash < Hash
def method_missing(method_name, *args)
if key?(method_name.to_s)
self[method_name].to_s
else
super
end
end
end
h = SuperHash.new
h['abc'] = 'def'
h.abc # => 'def'
h.something_else # => NoMethodError
This behavior gives us something pretty simple yet powerful: we have
manipulated the foundation of the class to give us runtime methods.
There’s a problem, though: using method_missing
alone is only half the
story.
Quack Check With respond_to?
In Ruby, you can call respond_to?
with a symbol method name on any
object and it should tell you whether or not that method exists on
the object in question. This is part of what makes Ruby’s duck-typing
work so well. So in our example, we also want to be able to know if a
method is there using respond_to?
. So let’s add a new override for the
respond_to?
method of our example above:
class SuperHash < Hash
def respond_to?(symbol, include_private=false)
return true if key?(symbol.to_s)
super
end
end
Well, that was easy enough. Now our SuperHash will return hash keys
based on method_missing
and even tell you if the method is there with
respond_to?
. But there’s still one more thing we can do to clean
things up a bit: notice how we have repeated functionality in that we
check key?
in both methods? Now that we have a respond_to?
we can
use that as a guard for method_missing
to make it more confident:
class SuperHash < Hash
def method_missing(method_name, *args)
return super unless respond_to?(method_name)
self[method_name].to_s
end
end
Wait, that can’t be right, can it? Can we just assume that we can
call the key like that? Of course! We already know that no existing
method was called if method_missing
is activated. That means that if
respond_to?
is true but no existing method was called, there must
be a key in our hash that caused respond_to?
to return true. Therefore
we can confidently assume that the key exists and simply return it,
removing the conditional and cleaning up the method_missing
substantially.
Now that you know how method_missing
and respond_to?
can work
together to add functionality to an object at runtime, you have a
powerful new tool in your metaprogramming arsenal. Enjoy it!