Follow Me On

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:

classLegislator# Pretend this is a real implementationdeffind(conditions={})end# Define on self, since it's a class methoddefself.method_missing(method_sym,*arguments,&block)# the first argument is a Symbol, so you need to_s it if you want to pattern matchifmethod_sym.to_s=~/^find_by_(.*)$/find($1.to_sym=>arguments.first)elsesuperendendend

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.

classLegislator# ommitted# It's important to know Object defines respond_to to take two parameters: the method to check, and whether to include private methods# http://www.ruby-doc.org/core/classes/Object.html#M000333defself.respond_to?(method_sym,include_private=false)ifmethod_sym.to_s=~/^find_by_(.*)$/trueelsesuperendendend

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.

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:

describeLegislatorDynamicFinderMatchdodescribe'find_by_first_name'dobeforedo@match=LegislatorDynamicFinderMatch.new(:find_by_first_name)endit'should have attribute :first_name'do@match.attribute.should==:first_nameendit'should be a match'do@match.shouldbe_a_matchendenddescribe'zomg'dobeforedo@match=LegislatorDynamicFinderMatch(:zomg)endit'should have nil attribute'do@match.attribute.shouldbe_nilendit'should not be a match'do@match.should_notbe_a_matchendendend

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: