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!