One of the many awesome things in Rails 3 is the new routing DSL. I was pretty attached to the Rails 2.3 style of doing things, and so I was a bit skeptical, but I have to say: the Rails team has outdone themselves.
I've been knee-deep in routes for the past few days, picking through the Rails guides as well as the very source code which makes up the routing.
The guides, are hands-down amazing. They are clear, concise and explain very thoroughly how the new DSL is to be used.
But the documentation is nothing next to the source code. It's clean, elegant, and overall very well-written. What I couldn't garner from the Rails guides, I was able to learn by reading directly from the source.
One thing that I had a hard time dealing with in Rails 2.x, even though it was thoroughly supported and (eventually) documented, was the RESTful routes. Even having used them for well over two years, something about them just wasn't working for me. Well, no more! Rails 3 makes RESTful routing a piece of cake, and the new syntax even helps to clear up some of the confusion about just what REST is.
What is REST?
There's a common misunderstanding about REST, and I have to blame Rails 2.x at least a little for perpetuating it. See, Rails was initially written to support the CRUD style of doing things -- Create, Read, Update and Delete. This was so ingrained into the Rails paradigm that when REST support was implemented, it was added as a 1-to-1 mapping from REST to CRUD:
A few others exist for rendering the corresponding forms and whatnot, but we'll get into that shortly.
In REST, it's the HTTP verb (POST, GET, PUT, DELETE) that changes which action a particular request is routed to.
So, now that I finally understand the situation myself, let me do my part in setting the record straight. Just because an application is RESTful does not mean it has to have a strict adherence to CRUD! You can map your actions however you like.
Let me say that again. You can map your actions however you like. This was certainly possible in Rails 2, but it was never particularly intuitive, in my humble opinion.
Single vs. Multiple Resources
Here's another commonly-confused point: the difference between one "resource" and multiple "resources". For multiple resources, the CRUD table above isn't actually complete, because a few actions are missing:
Take a breath and really understand what this table means. We're dealing with the concept of multiple resources, so the application needs to be able to list those resources as well as show a particular resource from that collection. It's probably easier to understand multiple resources than it is a single resource, because we tend to work with the database records in multiples. That's why Rails pluralizes table names by default.
Now consider the concept of a single resource. The application itself virtually always has multiples of any given record, and I think that's why this can be confusing. That's why it may be easier to understand single resources if you approach it from the end user's point of view.
A single resource, then, is any resource which a given user will only ever have one of. For instance, a user will probably only ever have one account. Therefore, it's redundant and confusing for a user to see
when all they want to do is view their profile. A single resource removes this redundancy because there's no need for an ID. Further, RESTful routing negates the need for the action name, "show". What we're left with is simply:
As for how the server "knows" which profile to actually show to the user? Simple: it cheats, by storing such information in the user's session cookie when they log in.
Here's the routing table as it would look for a single resource:
The Routing Code
First, let's take the happiest case. If CRUD works fine for you -- and it does just that for the vast majority of developers -- then why change it? To map routes for this happy scenario, it's as simple as:
Note that we maintain pluralization: singular for one resource, plural for many. Keeping with this convention will save you some headaches in the future.
- A neat trick that was in Rails 2 and has been carried over into Rails 3 is: This will show you at the command line all of the routes that Rails is producing, based on your routes file. Very helpful.
One very cool thing you can do in Rails is nest routes beneath each other. For instance, what if you wanted to see all of the posts belonging to the current user's profile?
This will produce the single resource routes as:
as well as the multiple resource routes as:
...and so forth.
Moving Away from CRUD
Now, what if you need to depart from CRUD? For instance, in one of my recent projects I had a model called Device. I needed it to support an activate as well as a deactivate action in addition to all of the standard CRUD stuff. What's a poor fellow to do?
The member block tells Rails that we're going to define some actions that are specific to the resource members -- that is, individual records and not the collection as a whole. You can accomplish the opposite of this by using a collection block instead.
The call to put tells Rails that the actions can only be used with the HTTP verb 'PUT'. All of the others -- GET, POST, and DELETE -- will produce routing errors.
That's right. I've created a nested resource -- devices -- under a single resource -- user. The devices resource supports two additional actions: 'activate' and 'deactivate'. So now my routes include:
Isn't that cool? This was all totally possible in Rails 2, but it's the new Rails 3 DSL that makes it easy and intuitive. Also, a
device_activate_path method is generated for me automatically. I can just call that method whenever I need to render an "Activate" link!
Want to change the automatically-generated method name? Just use:
Now the exact same route will be generated, but the method will be called
device_enable_path instead of
Omitting the Canonical Routes
So, what if you don't plan to support some of the automatically-generated routes? I was developing a Web Services API the other day and didn't particularly need the 'edit' or 'new' actions, as these are only used for rendering end user forms. There are two ways to constrain which actions will be generated:
Renaming the Canonical Actions
So, what if you want to keep the canonical routes, but are unhappy with the name of the action? I had an example where a user's action was effectively being cancelled. The destroy action was doing the job and all was happy, but I wasn't satisfied because calling an action called 'destroy' masked the true purpose of the action: the user was cancelling something, not destroying it. Here's how I renamed the action name while keeping the path itself constant:
Finally, there's one more Really Cool Thing that you should be made aware of: scopes.
Scopes serve a dual purpose. They're designed to allow you to create hardcoded path prefixes for your routes. For instance, say you want an admin section where the user can manage accounts. Your routes might look something like:
The corresponding paths, then, will look like:
Another really cool thing you can do with scopes is set options and group your routes accordingly. For instance, in my Web Services app, I didn't need the new or edit actions for any of my resources. So I just wrapped them all in a scope:
In this developer's opinion, Rails 3 has really hit the nail on the head in terms of RESTful routing. There's lots more to learn, but hopefully this article has given you a decent understanding of how it all ties together. Again, if you haven't already, take a look through the excellent Rails Guides and maybe even take a peek at the Rails routing source code. They'll help you out tremendously.
Oh, and one last thing: don't forget to test your routes using Test::Unit or RSpec. This step is easily overlooked, but routes can get very confusing very fast, and it's always nice to have that sanity check in place.