REST 101: Part 4 - Routing

Posted by jeff Wednesday, April 18, 2007 13:17:00 GMT

Traffic routing sign

Photo credit: http://flickr.com/photos/drdul/

UPDATE April 18, 2007: I fixed the URL convention for the "new" action to /airports/new instead of /airports/1;new which was incorrect. It's only the edit action 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 = edit

    NOTE: 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.

Comments

Leave a response

  1. Steve Erickson   April 16, 2007 @ 10:22 PM

    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.

  2. Jamie Hill   April 16, 2007 @ 10:27 PM

    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

  3. TomC   April 17, 2007 @ 12:10 PM

    Jeff, this has been a great series - keep going !!

  4. Mark Neustadt   April 17, 2007 @ 01:22 PM

    Kudos on your great work! I find this series to be tremendously helpful. Keep it up!

  5. Dylan Bennett   April 17, 2007 @ 08:06 PM

    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.

  6. Sanat Gersappa   April 18, 2007 @ 02:06 AM

    Great series. Cleared up a lot of things for me.

    Thanks a ton!

  7. David, biologeek   April 18, 2007 @ 12:32 PM

    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...

  8. Jeff   April 18, 2007 @ 01:22 PM

    @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.

  9. Jon Maddox   April 18, 2007 @ 02:21 PM

    great post, but the update action handles a PUT request not a POST

  10. Jeff   April 18, 2007 @ 06:25 PM

    @Jon: argh! Thanks for that. I fixed the original text.

  11. SRTech   April 18, 2007 @ 11:56 PM

    Please feel free to ask any questions here in the comments

    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?

  12. Casper Fabricius   April 22, 2007 @ 07:39 PM

    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!

  13. Oliver Andrich   April 30, 2007 @ 11:17 AM

    Jeff, I have to thank you so much. I think I finally got this REST thingy. :)

  14. Andrei   May 05, 2007 @ 10:26 PM

    Still, how can you send from HTML the PUT and DELETE verbs ? :)

  15. Jeff   May 07, 2007 @ 02:05 AM

    Andreai, Rails 1.2 added support for a :method parameter in your link_to statements, which will simulate any HTTP verb you want; for example, let's say you're in the show action for an airport, and in your RHTML template you want to include a link to delete the airport:

    link_to "Delete", airport_path(@airport), :method => :delete
    

    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.

  16. adam   June 04, 2007 @ 04:04 PM

    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?

  17. utahkay   June 19, 2007 @ 04:50 PM

    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!

  18. Eric Prugh   July 19, 2007 @ 09:18 PM

    I stumbled across this blog hoping that the REST mentality would finally click... and you made it happen. Thanks a lot, dudes!

  19. Scott   July 23, 2007 @ 03:17 PM

    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?

  20. cjman   December 07, 2007 @ 06:24 PM

    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?

  21. kino   May 24, 2008 @ 01:13 AM

    The Categories, thus, prove the validity of our ideas, as any dedicated reader can clearly see.

  22. Ellroy   May 30, 2008 @ 11:18 PM

    We have not simply lost the phenomenon for phenomenology; we retain it, consequently, by a freely actualizable return to cogitationes.

  23. Dan   July 20, 2008 @ 02:55 PM

    I’m using rails 2.1 and generate scaffold_resource gave me the following error: script: scaffold_resource: No such file or directory.

    I think in the newer versions of rails the command has been shortened to just ‘scaffold’, which seemed to work for me.

    Thanks for the great tutorial. I am finding it both fun and educational.

  24. Ken Wagner   December 18, 2008 @ 11:52 AM

    Why aren’t you writing books? (Resources or Physical?)

    Talent, man. You have it in trumps.

  25. sohdubom   December 25, 2008 @ 12:35 PM

    hi. as an experiment i decide to: rake routes , considering my routes.rb file has only: map.resources users. well, we all know the output, but based on this output the last 3 mappings which are: show(get), update(put) and destroy(delete):

    user GET /users/:id {:action=>”show”, :controller=>”users”} PUT /users/:id {:action=>”update”, :controller=>”users”} DELETE /users/:id {:action=>”destroy”, :controller=>”users”}

    based on the output above i decided to write the equivalent routes in the routes.rb file in order to mimic what map.resources does. so we will have 7 routes. to simplify let’s just consider here these last 3 routes. first, based on the output above, my conclusion is that i could use 3 routes with the same name: map.user and differentiate then with the :method symbol, being get, put and delete for each one of them … so rails would know which one to use based on the :method

    map.user ‘users/:id’, … , :method => :get map.user ‘users/:id’, … , :method => :put map.user ‘users/:id’, … , :method => :delete

    well, that doesn’t work … if i click delete in the view then the show action will be triggered. my 2nd conclusion is that since i’m not using map.resources and instead i’m using named_routes, then the routing system will use the top-first rule

    my doubt is, how rails understand map.resources since the output from rake routes doesn’t imply us to do something like:

    map.user_show ‘users/:id’, … , :method => :get map.user_update ‘users/:id’, … , :method => :put map.user_delete ‘users/:id’, … , :method => :delete

    plus if i named_route like the sample above, i don’t need to specify which method to use, cause the routes are named. right?

    then my 3rd conclusion is that rails should understand:

    map.user ‘users/:id’, … , :method => :get map.user ‘users/:id’, … , :method => :put map.user ‘users/:id’, … , :method => :delete

    based on the :method differentiation. i also did: :requirements => {:method => :delete} in routes.rb or user_path(user) in my view, but no success.

    obs. my delete link_to code: link_to ‘delete’, user, :confirm => ‘Confirm delete?’, :method => :delete

  26. sabat   March 09, 2009 @ 01:36 AM

    Great tutorials; maybe making sense of this to me for the first time.

    But I also noticed that the Rails implementation itself advertises the flaws of REST: the fact that GET means more than one thing, including “I want to edit this resource”, shows that real REST must be violated to do realistic work. GET should not mean “update”. That should be a form of PUT—you’re editing. But then again, according to the REST philosophy, you shouldn’t need to GET a resource more than one way. Ergo, REST oversimplifies the world, and is flawed.

    I’ll use REST, and maybe I’ll believe that it simplifies my code and my designs. But this REST worship that’s been going on for the past few years—I don’t know that I’ll ever understand it. Truthfully speaking, I think XML-RPC is simpler in practice.