Using method_missing and respond_to? to create dynamic methods
method_missing
is a well-known tool in the Ruby metaprogramming toolbox. It’s callback method you can implement that gets called when a object tries to call a method that’s, well, missing. A well known example of this is ActiveRecord dynamic finders. For example, if your User
has an email
attribute, you can User.find_by_email('joe@example.com')
even though User
nor ActiveRecord::Base
have defined it.
method_missing
’s cousin, respond_to?
, is often forgotten though. respond_to?
is used to determine if an object responds to a method. It is often used to check that an object knows about a method before actually calling it, in order to avoid an error at runtime about the method existing.
To have a consistent API when using method_missing
, it’s important to implement a corresponding respond_to?
.
Example
Imagine we have a Legislator
class. We want a dynamic finder that will translate find_by_first_name('John')
to find(:first_name => 'John')
. Here’s a first pass:
That seems to do the trick. But Legislator.respond_to?(:find_by_first_name)
would return false
. Let’s add respond_to?
to fix it.
As commented, respond_to?
takes two parameters. If you don’t do this, there’s a chance some library may expect to be able to invoke respond_to?
with 2 parameters, resulting in an ArgumentError
. In particular, the RSpec mocking library does this exactly this, and caused me a bit of befuddlement until I turned on full backtraces.
Possible refactorings
DRY
You might notice it’s not very DRY. We use the same pattern matching twice. This particular logic is simple, but it could be grow to be more complicated.
We can again look to ActiveRecord for inspiration. It encapsulates the logic in ActiveRecord::DynamicFinderMatch, to avoid repetition between method_missing
and respond_to?
, in addition to simplifying the methods.
Caching method_missing
Always passing through method_missing
can be slow.
Another lesson we can take from is ActiveRecord is defining the method during method_missing
, and then send
to the now-defined method.
Testing
Being the test-minded individual that I am, no code would be complete without some testing.
Creating the LegislatorDynamicFinderMatch
makes it straight forward to test your matching logic. An example using RSpec could look like:
If you are creating dynamic methods which are just syntactic sugar around another method, like our finder, I think it is sufficient to use mocking to ensure the main method gets called with the correct arguments. Here’s an RSpec example:
Summary
If you’re going to use method_missing
, be sure to implement respond_to?
in kind.
You can improve the DRYness of these methods by encapsulating the matching logic in a separate class, and you can improve performance by defining methods when they are missing.