Thursday, August 04, 2011

designing with rails routes

I have been starting any new work in Rails by creating or modifying the models in the application, and then adding tests to the models. Up until today, I had found the transition in coding models to coding controllers to be very confusing. Models and models' tests seem very intuitive to me, but I was having trouble putting my finger on what made controllers so tough. I think I finally learned the missing piece - routes!

One of the cooler elements of Rails is the way it inherently supports ReST. The routes.rb file's "resources" keyword allows you to quickly express the structure of your ReSTful API. The buckblog has a nice brief on one of the more useful bits: nested resources, and links to the more canonical tutorials from the Rails community. Here are the most useful things I've learned:

  1. When you are ready to modify your controllers, especially if you want to add a new resource or change relationships between them, start with the routes.rb file, and think of 'rake routes' like a compiler. Make changes and 'rake routes' to make sure they reflect your intent before you start writing tests or views.
  2. Rails does quite a lot to automate and abstract you from the details of routing. You do need to understand a few key concepts though:
    • A controller has many actions. 
    • Routes and Actions are one to one (but...)
    • URLs and Routes are not one to one. ReST uses the http verb to distinguish between reading and deleting at the same URL. In other words, a Route is just a URL plus a verb. 
    • Resources are a collection of routes, usually pointing to one controller. Resources provide nice ReSTful semantics, making the intent of your routing more clear in routes.rb.
  3. Nesting resources is a strong spice, best used sparingly. In my mind, nesting is ideal for certain actions for a 1:N / one-many relationship. I found it very important to understand that you can pick and choose routes you want to nest. A single resource can have both nested and unested routes. Imagine you have the classic "post has many comments" relationship. 
    • I'd suggest using nested resources routes for 
      • :index = since you will almost always want to filter comments down to comments on a single post, build it into your route
      • :create = you will always need to specify a post for your comment, so build it into the route
    • But I'd avoid nesting the resource routes for
      • :destroy, because you'll be most commonly deleting a single comment
      • :show, because :index is for listing, :show is for a single comment. Why require the caller to specify both the post id and the comment id?
    • I think :new is very debatable, and ultimately depends on the structure of your pages. To set the action of the form properly, you need to have the parent's ID. If you are rendering the form in a context that has the parent set already, you may not need to pass it along to the controller via the request. But if you find yourself putting the parent's ID into a parameter, you should nest the :new action.
    • Rails creates convenience functions that will generate the URL path for a particular route. There are decent conventions for the naming, but to be honest, I've found the patterns difficult to remember or apply by hand. Mostly, I have been running into trouble when dealing with controllers/models that have multi-word names, but often with the "natural" way Rails deals with singular/plural names. Now I don't even try to remember, because you can always get the function name from the leftmost column of rake routes. 

      3 comments:

      1. If you know the rule for needing "_url" on the helper function that rake routes reports, do tell. Seems like it is necessary for nested resources, but I'm not sure if that is the only case.

        ReplyDelete
      2. _path gives you the relative URL, _url gives you the absolute. Doesn't matter if the route is nested or not

        Another nice convenience is you can just use the object. link_to @post for example. If your resource is restful you can even do form_for @post which will do either an update or create (PUT or POST) depending on whether @post is persisted or not, which in turn means you can use the same partial for both new and edit

        ReplyDelete
      3. Thanks, that makes much more sense. I have been using link_to and form_for, which are amazing when used with :remote => true. jquery's unobtrusive javascript adapter and the whole idea of executing the js generated with ajax requests is pure genius. If I have time, I'd like to write up another "make sure I get this idea" post on remote => true. I did find, however, that with nested resources, I had to use the _path or _url function to set :url, otherwise Rails looked for the helper function as though the resource is _not_ nested. I'm not sure why, maybe it has some dependency on the current page's context.

        ReplyDelete