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
User.find_by_email('email@example.com') even though
ActiveRecord::Base have defined it.
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
Imagine we have a
Legislator class. We want a dynamic finder that will translate
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.
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.
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
respond_to?, in addition to simplifying the methods.
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.
Being the test-minded individual that I am, no code would be complete without some testing.
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:
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.