method_missing is a piece of Ruby’s meta-programming arsenal that’s pretty powerful in my opinion. It allows you to call methods that don’t actually exist, but still do something useful with it.

I’m trying to keep this short and sweet, so imagine you have a blog like this one. You have Article, Comment and Category models and you want a way to find all Articles/Comments/Categories by calling find_all_( articles | comments | categories). So instead of create a find_all_* method for each resource, we’ll let method_missing do the work for us:

  1   class ApplicationController < ActionController::Base
  2       
  3       private
  4       def method_missing(method, *args)
  5           resource = method.to_s.split("_").last
  6           if method.to_s.match /^find_all_/
  7               resource.singularize.camelize.constantize.find(:all)
  8           end
  9       end
 10  
 11  end

This is a very simple example of what method_missing can accomplish but it’s a relevant one at least. method_missing takes two main arguments, the name of the method the user tried to call and the arguments passed through to that non-existent method.

If you follow a find_all_( model ) naming convention the last word will always be the Model. In my example, first thing we do is set resource to that String representation of the Model (the last word of the find_all_* method). Next, we make sure the call is actually to a find_all_* method. The long “-ize” method chain makes the String singular ( “comments”.singularize #=> “comment” ), uppercases the first letter ( “comment”.camelize #=> “Comment” ), constantizes it ( “Comment”.constantize #=> Comment ) and finally calls a normal old find(:all) on it and returns the result. Now you can do this in your controllers:

  1   # CommentsController.rb
  2   @comments = find_all_comments
  3   
  4   # ArticlesController.rb
  5   @articles = find_all_articles
  6   
  7   # CategoriesController.rb
  8   @categories = find_all_categories

My example only shows one “magic method” and is only a bit more readable, but you could extend it to check for find_recent_* or maybe find_10_* pretty easily.