Using Routes Instead of Custom REST Actions

Posted by jeff Friday, August 22, 2008 01:40:00 GMT

Photo credit

Suppose you’re trying to be a good Rails developer and use RESTful routing wherever possible in your application. Using the ever-present blog example1, you might implement your PostController’s index action like this:

def index
  @posts = Post.all unless request.format.rss?

  respond_to do |format|
    format.html  # render posts.html.erb
    format.xml   { render :xml => @posts }
    format.rss   { @posts = Post.all(:limit => 10, :order => 'created_at desc') }
  end
end

In other words, for HTML and XML clients, we return all the posts, formatted accordingly. For RSS readers, we only give out the 10 most recent posts2.

Without doing anything special, this url:

/posts.rss

will automatically use /app/views/posts/index.rss.rxml (which we have to write) to generate our RSS data feed.

But I Knew That Already

Ok, but suppose you’re converting an existing site, and your readers already grab your feed at this url:

/posts/feed

Now, our code won’t work. Rails will try to call the show action, using feed as an :id parameter. Not good.

At this point, the easiest thing to do is to add a custom action to your controller:

def feed
  @posts = Post.all(:limit => 10, :order => 'created_at desc')
  # render default template
end

Rails will automatically find a template named, say, app/views/posts/feed.rxml and use it generate the feed.

But those who know me, know that I despise custom actions. Yes, once in a blue moon I have to use them. But in this situation, I prefer to use a more elegant solution: routes.

Ok, But Did You Know How To Do This?

We need to support /posts/feed as our url for RSS feeds. Remember that Rails routing allows us to route any url we want into any controller action we want. So somewhere above the map.resources :posts line in our routes.rb file, we do this3:

  map.feed 'posts/feed', :controller => 'posts', :format => 'rss'

And now if you go to /posts/feed, your glorious index action will be called and will respond as if an RSS client has made the request.

Cool, no?

1 At our workshop, we will build something more interesting than a blog.

2 We’ll also learn how to use named scopes to simplify this kind of code.

3 Even in development mode, you might have to restart your local server (mongrel or webrick or thin or whatever) to get Rails to pickup your routing changes.


Ready to learn more about RESTful development? Register now for REST for Rails before the seats are all gone.

Comments

Leave a response

  1. josh   August 22, 2008 @ 04:51 AM

    not only cool.. it solves one problem I was having, although slightly different than the rss example. thanks.

  2. Nathan   August 22, 2008 @ 01:32 PM

    Nice tip – thanks!

  3. Ryan Bates   August 22, 2008 @ 03:45 PM

    Good tip. An alternative solution is to use a 301 redirect. You can set up a controller dedicated to redirects to handle this in Rails. Or you can just add a line to your web server config. In Apache it might look like this.

    Redirect permanent /posts/feed http://example.com/posts.rss
    

    The advantage of 301 redirects is that most services will remember the new URL (search engines, feed readers, etc.) so you don’t have to support the old one indefinitely.

  4. Justin   August 22, 2008 @ 04:25 PM

    @jeff so do you ONLY use the default actions in your controllers? And leave logic in helpers, models, etc?

    @Ryan sounds like a good approach. I have search EVERYWHERE for a good introduction to proper configuration in Apache; do you have any good resources to suggest?

  5. A Different Justin   August 22, 2008 @ 05:40 PM

    @Justin We go the redirect controller route, and then set up the routes.rb file to point routes we want to expire to the controller. This is great for people like me who can never remember the difference between 301 and 302 redirects, cause I can give them names like ‘permanent’ and ‘temporary,’ but there’s the drawback of requests that don’t need Rails having to go through the full stack.

  6. Jeff   August 22, 2008 @ 06:03 PM

    @Ryan: That’s also an excellent solution.

    @Justin: Yes. Like I said, once in a blue moon I have to add a custom action. But it’s like they say in chess: when it’s your turn, and you find a good move, wait one more minute, and look for a better move. That’s kind of what I was trying to illustrate here: adding a custom action often seems like a good thing to do, but often if you continue to think about the problem, you’ll find “the Rails way” of solving it instead.

  7. Thomas R. Koll   August 23, 2008 @ 07:06 PM

    About limiting the rss feed: In my opinion an action should always return the same set of data no matter what format.

  8. Nuwan Chaturanga   August 27, 2008 @ 03:21 AM

    Thank you. Another lesson for me to enhance my Rails routing knowledge.

  9. Geoffrey Grosenbach   August 30, 2008 @ 01:18 AM

    I use the redirect_routing plugin, which makes this easy.

    http://github.com/larspind/redirect_routing/tree/master

    Little-known tip…it’s possible to call model methods in routes.rb, thereby using the database to build your routes file for these kinds of redirections.

  10. Matt White   September 12, 2008 @ 09:01 PM

    Excellent alternative to custom REST actions, which I also find annoying.