REST 101: Part 4 - Routing 20

Photo credit: http://flickr.com/photos/drdul/
UPDATE April 18, 2007: I fixed the URL convention for the "new" action to
/airports/newinstead of/airports/1;newwhich was incorrect. It's only theeditaction that uses the semicolon in Rails 1.2.
Last time we learned that a REST design starts by identifying a few key resources. Today we need to talk about how Rails will route all of the REST-compliant requests, and how it expects you to handle those requests.
I want also take a step back for a moment, and mention that REST is not a Rails-only thing. REST was developed long before Rails, and is simply an approach that can be applied to many kinds of software applications. For example, now that I know about REST, I'll probably never write another WinForms app the same way again.
Resource == Controller
You already know about models and controllers. But how do you coordinate them to implement your newly-found RESTful design? First, let's get a few things out of the way:
- A resource is not always a one-to-one mapping to a Rails model or database table. Sometimes a resource is "virtual" and exists in your domain vocabulary and application logic, but is not backed by a database table.
- However, a resource is always mapped to a Rails controller.
- For you abstract-interface lovers out there (like me), you can consider your controller class to be the "concrete implementation" of your REST interface.
- Your controller will implement your resource regardless of which kind of client is talking to it (html browser, xml client, feed aggregator, etc.) As such, it will need to be able to respond accordingly, and send its responses and return values back to the client in the format that the client expects (html, xml, etc.) Back in part 3, I said we wanted different kinds of clients (web, cell phone, rich client, etc.) to be able to access the same resources. A single controller is responsible for rendering its resource, regardless of which client is making the request.
Seven
Most resources want to be accessible individually (an airport) an as a collection (the list of all airports). Your controller will have these seven actions (methods):
These four:
- show : this handles a GET request for the representation of one resource instance.
- create : this handles a POST request for creating a new resource instance.
- update : this handles a PUT request for updating an existing resource.
- destroy : this handles a DELETE request on a resource instance.
plus these three:
- index : this handles a GET request for a representation of the collection.
- new : this handles a GET request for a blank form useful for creating a new resource. Usually makes sense only for HTML clients.
- edit : this handles a GET request for a form that is pre-filled with current values of an existing resource. Again, usually only for HTML clients.
The names of these methods are important, because the Rails routing infrastructure will be expecting them.
Of course, you can choose to implement fewer if you want. If your business rules say that a certain resource should only be rendered as read-only, then you might only need to implement the index and show methods, for example.
You may already be wondering how in the world you're going to develop a Rails application if those are the only actions you're allowed to have in your controllers. Well, first of all, don't worry. If you really, really think you have a situation that requires a few extra actions, then go right ahead and add them to your controller. Just remember that having any extra actions can be a warning sign that you haven't identified all of your resources yet.
The Traffic Cop
We need to connect some dots now. How do we map the URLs and HTTP verbs into method calls on our controller?
Normally, the action to be called in your controller is obvious just by looking at the url. I hope you remember how Rails normally translates incoming HTTP requests into calling methods in your controller code. If you don't remember, here's a five-second refresher course: your routes.rb file specifies how to translate URLs into controller classes and methods:
map.connect '/airports/:action/:id', :controller => 'airports'
This example maps a url like www.mydomain.comm/airports/open/45 to a controller class named AirportsController and a method named open. While the open method is running, params[:id] will yield 45.
But to implement REST with the Rails framework, things would need to be done differently. First of all, we will want the same url to sometimes map to different actions, depending on which HTTP verb has come along for the ride. In other words, this URL:
/airports/1
should map to our show action (for airport #1) if the HTTP verb was GET, but it should map to our destroy action if the HTTP verb was DELETE.
Prior to Rails 1.2, there wasn't a way to specify all that logic in the map.connect statement. Fortunately, 1.2 introduced a small but extremely important enhancement to the routing vocabulary. It's so cool, in fact, that unless you are aware of REST, you might have absolutely no idea how great this little enhancement really is to the Rails framework:
map.resources 'airports'
This code is short but it says a lot. It tells Rails all of these things:
- you have resources called airports (note the plural form, that's important)
- you want Rails to follow RESTful conventions and map incoming requests accordingly
- routing will not be based on URL pattern alone, but URL pattern paired with an HTTP verb
- it should route all requests to this resource through a controller named AirportsController (note, plural again)
- all URLs for this resource will follow a very specific convention (I'll describe those below)
- you want a set of pre-defined named routes for each action for this resource
The URL/verb pairs Rails will now automatically map for you look like this:
/airports/+POST= create/airports/1+GET= show/airports/1+PUT= update/airports/1+DELETE= destroy/airports/+GET= index/airports/new+GET= new/airports/1;edit+GET= editNOTE: That strange-looking semicolons are correct for Rails 1.2.3, but they will be changed to forward-slashes in Rails 2.0
Now all you have to do is implement the seven methods in your controller, and Rails handles the rest!
Don't Order Yet
But wait, there's more. There's a really neat generator that comes with Rails 1.2 and later called scaffold_resource, that will give a complete RESTful skeleton for your resource: it will add the routing code, a simple but functional controller implementing all seven methods, a migration for the underlying table, and RHTML templates for all of them! It's a great way to get started.
In fact, the best thing you can do to learn about REST is to start a fresh rails app and generate a resource. Create a database called myapp_development and myapp_test (for example), and then do this:
c:\dev> rails myapp
c:\dev> cd myapp
c:\dev\myapp> ruby script/generate scaffold_resource airport name:string designator:string
c:\dev\myapp> rake db:migrate
Start up ruby script/server and go to localhost:3000/airports. You'll be able to create new airports, edit them, and delete them! How does it work?
Just open up airport_controller.rb - it's all right there. The Ruby code will be amazingly short. Study the code. It's very important that you understand the controller code before we move on.
Then, look at each of the .rhtml templates that were generated for you, and look for the use of named routes. Get a feel for how an incoming request gets routed to a controller action and which .rhtml will get rendered back to the client.
Now, there's probably one construct in the controller code that you haven't seen before: the strange-looking respond_to block. That's an important pillar of of our entire RESTful implementation, and we'll go into more details about it next time.



This is a great series. Thanks so much for taking the time to share what you've learned. The part in this post that I found especially helpful was the distinction between models and resources. I think that's the trickiest part of this whole new paradigm.
Great article. With regards the 7 actions, I still maintain there should be 8 :)
"I blogged about it last summer":http://thelucid.com/articles/2006/07/26/simply-restful-the-missing-action
Jeff, this has been a great series - keep going !!
Kudos on your great work! I find this series to be tremendously helpful. Keep it up!
Bastard. You gave us homework! :)
As everyone has commented, this is an excellent series. Many thanks to you for writing it. Definitely looking forward to next article. The writing is clear and the concepts explained well.
Great series. Cleared up a lot of things for me.
Thanks a ton!
Yet another great post!
I just wonder why did Rails use:
/airports/1;new + GET = new
instead of:
/airports/new + GET = new
If you need a simple empty form, how did you know the id of your airport? Anyway "edit" and "new" seemed a bit hackish to me, even if I had to recognize that Rails is far more advanced than Django on the REST point...
@David: My fault! I wrote the wrong thing - thanks for catching that. I've updated the original text. Should be simply /airports/new as you (correctly) expected.
Thanks to everyone for the feedback. Please feel free to ask any questions here in the comments, and I'll try to address them in subsequent articles. I think I'll be done after one or maybe two more, so if you have any questions that you want answered, just let me know.
great post, but the update action handles a PUT request not a POST
@Jon: argh! Thanks for that. I fixed the original text.
OK, I've got one. I recently read that Rails is going to stop using semicolons in a future version: http://www.ryandaigle.com/articles/2007/3/29/what-s-new-in-edge-rails-restful-routes-get-a-new-custom-delimiter
What can I do to make our publicly facing URLs that use semicolons (for custom actions affecting a particular record) compatible with future rails versions? Is there a way (maybe a plugin?) to have rails start using the forward slash now, before I release the app?
Hi Jeff. This REST series is great. I've been trying to communicate this stuff to people since we "saw the light" together at RailsConf last year, but this is just so much more clearly explained than anything else I've seen. Hope you guys are doing well!
Jeff, I have to thank you so much. I think I finally got this REST thingy. :)
Still, how can you send from HTML the PUT and DELETE verbs ? :)
Andreai, Rails 1.2 added support for a
:methodparameter in your link_to statements, which will simulate any HTTP verb you want; for example, let's say you're in theshowaction for an airport, and in your RHTML template you want to include a link to delete the airport:If you now browse to the airport page (say,
/airports/1), you should see a "Delete" link. If you view source, you'll see that actually Rails has configured the click event to create a tiny dynamic form and submit it with POST, and include a hidden form variable to indicate to the server that you want to simulate the DELETE verb.Hope this made sense... check out the Rails API docs for link_to for more details, or feel free to ask a follow-up question here in the comments.
Great series! I'm inspired to try putting RESTful principles into action as I learn ruby on rails. I'd like to ask a how-to question, so I apologize if this isn't really the place for it. Here goes:
I'm creating a basic forum-style web site on which users can post discussions on a limited number of topics. Let's say I start with just two: music and cars. People can click on a "music" tab to discuss music; they can click on a "cars" tab for discussions about cars.
I create a model called Topic that hasmany discussions and a model called Discussion that belongsto Topic. Then, rather than having lots of actions within my discussioncontroller that, for example, show music discussions or create a new car discussion, I create two controllers -- carDiscussioncontroller and musicDiscussion_controller. Each controller has the standard 7 RESTful methods (index, show, new, edit, update, create, destroy).
Is this a correct RESTful way?
Great series! I am new to Rails. I found that I had to create the database before running rake db:migrate. This is probably obvious to everyone else, but I didn't know what the rails stuff does for me and what it doesn't. It seems to do everything except create the database.
c:\dev> rails myapp c:\dev> cd myapp c:\dev\myapp> ruby script/generate scaffold_resource airport name:string designator:string
c:\dev\myapp> mysqladmin -u root create myapp_development
c:\dev\myapp> rake db:migrate
Thank you so much for this great series!
I stumbled across this blog hoping that the REST mentality would finally click... and you made it happen. Thanks a lot, dudes!
To start, thank you for an excellent series!
In regard to your response (May 07, 2007 @ 02:05 AM) to Andrei's comment (May 05, 2007 @ 10:26 PM) -- the fact that Rails has to use a work around (a little form with a hidden field) for PUT and DELETE exposes the lameness of ... something?? However, it's not clear to me if the lameness is the browser or HTML.
In HTML there is an anchor tag () that implements GET and a form tag (<form>) that implements POST, both of which are implemented by browsers. Is the issue that HTML does not have tags for PUT and DELETE, or is it that browsers don't implement those tags?
First off, great tutorial. Thank you.
I have a problem implementing the example though:
rake db:migrate (in /cygdrive/c/src/myapp) rake aborted! No such file or directory - /tmp/mysql.sock
I have mysql install with the WAMP stack.
Anybody having a similar issue?