23 September 2009

Keeping it RESTful

REST is now the standard way of doing things in Rails and I am a convert. I have always spent far too long worrying about the best way to write my applications, but now REST has come into my life and taken a whole universe of procrastination and indecision away from me.

So, these days when I see Rails code that is not RESTful, I look for ways to clean it up.

Take the following lines in a routes file:

map.connect '/posts/needs', :controller => 'posts', :action => 'needs'
map.connect '/posts/offers', :controller => 'posts', :action => 'offers'

And the following methods in the posts controller:

def needs 
  @posts = Post.find(:all, :conditions => { :kind => "Need" }
  respond_to do "
    format.html { render :action => 'index'} 
  end
end

def offers
  @posts = Post.find(:all, :conditions => { :kind => "Offer" }
  respond_to do ":formatformat"format":
    format.html { render :action => 'index'} 
  end
end

The only difference between these two methods is a difference in the “kind” of post. They can easily be refactored into a single index method that handles both kinds of post:

def index
  @kind = params[:kind]
  @posts = Post.find(:all, :conditions => { :kind => "Offer" }
  respond_to do 
    format.html
  end
end

The “kind” is now identified from the :kind paramater. This parameter can come from a query string, like this:

/posts?kind=Offer

But if you don’t want query strings in your URLs, then you can create new routes for each “kind”:

map.connect '/posts/offers', :controller => 'posts', :action => 'index', :kind => "Offer" 
map.connect '/posts/needs', :controller => 'posts', :action => 'index', :kind => "Need"

Or, if you prefer, you can define the routes in a single line:

map.connect '/posts/:kind', :controller => 'posts', :action => 'index', :kind => /Offer|Need/

Note the regular expression used to constrain what :kind the URL can contain and also note that the URLs required are now of the form ‘/posts/Offer’, rather than ‘/posts/offers’.

One final change is to give the route a name (in this case, “kinds”):

map.kinds '/posts/:kind', :controller => 'posts', :action => 'index', :kind => /Offer|Need/@

Allowing resources to be linked to like this:

<%= link_to "Offers", kinds_path('Offer') %>

As general rules of thumb, keep controllers and routes RESTful and use customized mappings sparingly (but that doesn’t mean don’t use them at all).

d. sofer