mike enriquez

Mac/iOS/Web Developer

Technical stuff

Don't forget about respond_to? when implementing method_missing

method_missing can be used to do some really cool things in Ruby, but it can also cause some headaches when done improperly. One cause for headache is when method_missing comes with a broken or missing respond_to? implementation.

Example Proxy class

Consider the following example of a proxy class that uses method_missing, but doesn’t implement respond_to?.

class Proxy
  def initialize(subject)
    @subject = subject
  end
  
  def method_missing(method)
    @subject.send(method)
  end
end

proxy = Proxy.new(Time)
proxy.respond_to?(:now) # => false
proxy.now # => Fri Feb 05 00:34:53 -0500 2010

Our proxy object is a dirty liar. When we ask if it responds to now it returns false, but when we actually call now it responds successfully.

Here’s a better implementation for Proxy:

class Proxy
  def initialize(subject)
    @subject = subject
  end

  def method_missing(method)
    if @subject.respond_to?(method)
      @subject.send(method)
    else
      super(method)
    end
  end

  def respond_to?(method, include_private = false)
    super || @subject.respond_to?(method, include_private)
  end
end

proxy = Proxy.new(Time)
proxy.respond_to?(:now) # => true
proxy.now # => Fri Feb 05 00:34:53 -0500 2010

That’s better. We added an implementation for respond_to? and some calls to super. Keep in mind that we’re overriding existing methods, and we want to add behavior to them unobtrusively. You can learn some techniques for DRYing and testing method_missing and respond_to? at Technical Pickles.

Let’s examine a bug with respond_to? in a Rails plugin you’re probably using…

will_paginate and respond_to?

Here is a Post model that contains a class method called paginate_by_something.

# will_paginate is installed
class Post < ActiveRecord::Base
  def self.paginate_by_something
    # "something" is not an attribute
  end
end

Post.respond_to?(:paginate_by_something) # => false

What!? We explicitly define a class level method, but when we interrogate it with respond_to? it returns false.

The problem is with will_paginate’s respond_to?. Below is a snippet of code from will_paginate that gets mixed into your models.

def respond_to?(method, include_priv = false) #:nodoc:
  case method.to_sym
  when :paginate, :paginate_by_sql
    true
  else
    super(method.to_s.sub(/^paginate/, 'find'), include_priv)
  end
end

The first thing will_paginate does is return true for its obvious methods: paginate and paginate_by_sql. Then the interesting part of this code is the call to super. It turns a method like paginate_by_author into find_by_author, which respond_to? would return true for, if it is an ActiveRecord dynamic finder. Going back to the paginate_by_something example in our Post model, we can see that respond_to? returns false because find_by_something is NOT a dynamic finder (when “something” is not an attribute).

The fix to will_paginate is fairly simple, and I’ve already submitted a patch. Props to Ryan Briones for pairing with me while debugging the problem on a project at Chase.

On a side note, if you have a particular RSpec test that fails when you run the whole test suite, but pass when ran individually; Check your mocks on methods that don’t implement respond_to? correctly. RSpec uses respond_to? internally when using mocks, and RSpec expects it to work!