What's New in Edge Rails: Simply RESTful Support - And How to Use It

Posted by ryan
at 8:40 AM on Tuesday, August 01, 2006

See here from some syntactical changes that occurred after this post was published

Though I’ve been beaten to the punch on this one, it’s still worth a mention. Edge rails now has native support for the simply resful plugin functionality (though the implementation is not backwards-compatible as it now uses pluralized naming conventions in its mappings).

This addition continues the Rails march towards REST-vana and allows for easy declaration of REST-able controller methods. Where as edge Rails’s new Active Resource library provides the client-side part of the REST equation, simply RESTful provides the server-side handling of REST-based requests.

Basic Usage

The way it works is that you get a new map.resources method to use in your routes.rb config file that specifies which resource you want to RESTize. For example, assuming I have a standard users_controller I would add the following to my routes:

map.resources :users

Yeah, that’s it. And with that declaration, here are the requests that are mapped for me (all to the users controller):

GET: /users => [:action => 'index']
GET: /users.xml => [:action => 'index', :format => 'xml']
GET: /users/1 => [:action => 'show', :id => 1]
GET: /users/1;edit => [:action => 'edit', :id => 1]
GET: /users/1.xml => [:action => 'show', :id => 1, :format => 'xml']

POST: /users => [:action => 'create']
PUT: /users/1 => [:action => 'update', :id => 1]
DELETE: /users/1 => [:action => 'destroy', :id => 1]

Note that in true REST form, this one URL: /users/1 serves four different actions depending on request method (GET => show, POST => create, etc…). Beauty in simplicity…

It should be noted that Rails has to cheat to emulate the various HTTP request methods as most browsers don’t support sending PUT or DELETE requests. Rails does this by using the _method parameter in the various link_to helper methods

Advanced Usage

So REST is great for calling simple CRUD actions, but how do you invoke other actions while not stepping outside the friendly confines of the REST world? As was hinted at in the edit mapping above, you use the ‘;’ delimiter to denote a different action – along with telling map.resources what HTTP methods to use for the non-standard actions.


# Provided as default resource mappings
GET: /users/1;edit => [:action => 'edit', :id => 1]
GET: /users/1.xml;edit => [:action => 'edit', :id => 1, :format => 'xml']

# Update routes to handle non-standard CRUD actions
# This one says that a GET on 'filter' is accessing the collection
map.resources :users, :collection => { :filter => :get }

GET: /users;filter => [:action => 'filter']
GET: /users;filter?active=true => [:action => 'filter', :active => 'true']

# And this says that a POST to 'deactivate' is accessing a member item
# (i.e. a single item)
map.resources :users, :member => { :deactivate => :post }

POST: /users/1;deactivate => [:action => 'deactivate', :id => 1]

# Get funky and allow a PUT to 'new;admin'
map.resources :users, :new => { :admin => :put }

PUT: /users/new;admin => [:action => 'admin']

Hopefully this makes it pretty clear how you can layer your application functionality on top of this new RESTful routing. And for those looking for a real wow factor…

Wow Usage (i.e. Nested & Prefixed Routes)

...you can also specify path prefixes to your routes for trully spectacular routes:


# Give my user routes a prefix by group
map.resources :users, :path_prefix => "/groups/:group_id" 

GET: /groups/13/users/1 => [:controller => 'users', :action => 'show',
        :group_id => 13, :id => 1]

Another way you can achieve this nested routing is by nesting the resource mappings themselves


# Make both groups and users RESTable, with users as nested resources of groups
map.resources :groups do |group|
  group.resources :users
end

GET: /groups/13/users/1 => [:controller => 'users', :action => 'show',
      :group_id => 13, :id => 1]
GET: /users/1 => [:controller => 'users', :action => 'show', :id => 1]
GET: /groups/1 => [:controller => 'groups', :action => 'show', :id => 1]

Tidbits

With each resource mapping you specify, you also get a handy named route. So with:

map.resources :users, :collection => { :filter => :get }

You get a filter_users_url helper method:

filter_users_url #=> "/users;filter"

Don’t like the default naming? Fine, add your own prefix to the generated url name:

map.resources :users, :collection => { :filter => :get }, :name_prefix => 'my_'

Now:

my_filter_users_url #=> "/users;filter"

I think I’m in love.


tags: , , ,

Comments

Leave a response

  1. Chris AndersonJuly 28, 2006 @ 05:16 PM
    Chris, Rails is still simple. No one is forced to use map.resource. It's just that word on the street is that by using resources and CRUD-style controllers, you can have productivity gains over the alternative. See here for an example: http://scottraymond.net/articles/2006/07/20/refactoring-to-rest Well, ok, productivity is nice, but so is *enjoying* the code you write. CRUD controllers are more consistent, so you get more enjoyment *and* higher productivity.
  2. DHHJuly 28, 2006 @ 05:16 PM
    There's indeed a big difference between verbs and actions. The verbs are the overarching interaction, the semicolon represent more of an aspect or sliver of something. So GET /people/1;edit means the same as the regular get, show me the person, but it further specifies that we want to see that person in an editing light. We'll be sure to get all the reasoning written up about this. There's been a TON of debate and digging around to get where we are now. Also, remember that its only browsers that don't natively get stuff like PUT/DELETE (yet). We're in large parts doing simply restful to get a "free" API that can be consumed by clients that _do_ understand all the HTTP verbs. For extra style points, this is the recommended declaration for nesting: map.resources :groups { |groups| groups.resources :users }
  3. NateJuly 28, 2006 @ 05:16 PM
    Thanks for the details. I eagerly await the write-up.
  4. Ryan DaigleJuly 28, 2006 @ 05:16 PM
    Lee, you're absolutely right about the nested maps example - it's been fixed. Thanks for the heads up!
  5. ChrisJuly 28, 2006 @ 05:16 PM
    hmm... Rails seems to have changed... It's not so simple anymore. Oh Well. There is plenty of debate of how REST works.
  6. DannoJuly 28, 2006 @ 05:16 PM
    Something doesn't seem right about this. The idea of REST, as I interpreted it, is that the resources represent themselves and all I'm doing is validation and in the case of human viewing of the resources, prettying up the presentation. Having to do this kind of mapping seems very unRESTful: # Give my user routes a prefix by group map.resources :users, :path_prefix => "/groups/:group_id" Because the user should be implied by the way that the group is modeled. Maybe I'm just misunderstanding the vision though.
  7. RabbitJuly 28, 2006 @ 05:16 PM
    Hmm... In your routing example you say: @map.resources :users@ But that doesn't work. It should be: @map.resources :user@ I also tried using the crazy *%w() deal and it's the same thing -- Rails wants the singular version of your resource.
  8. Dean StrelauJuly 28, 2006 @ 05:16 PM
    Nate: The reason is that PUT & DELETE are being 'faked' not just made-up. PUT & DELETE are standard parts of the HTTP protocol spec[1], they're just not supported in .. well, much of anything. [1]: http://www.freesoft.org/CIE/RFC/2068/index.htm
  9. Ryan DaigleJuly 28, 2006 @ 05:16 PM
    Nate - Dean already answered your point but to add my own slant: Rails is not faking controller action calls - it's faking the @HTTP@ request methods that browsers don't natively support (usually just @PUT@, @DELETE@, @HEAD@). It handles non-standard action calls independent of the @HTTP@ request method using the 'gross' syntax of _;action_name_. This way the way you call the action (the request method) is completely seperate from the action itself. It's actually quite consistent...
  10. Ryan DaigleJuly 28, 2006 @ 05:16 PM
    Stoffe - yeah, you've noticed that there was a little bug w/ timestamping w/ postgresql in the version of Rails that Typo 2.6 relies on. Quite annoying - but fixed in Typo 4!
  11. NateJuly 28, 2006 @ 05:16 PM
    Action calls are HTTP methods, except for the half of them that aren't actually supported by browsers so they're faked with request parameters, otherwise they're appended to the url with a semicolon. That's consistent?
  12. StoffeJuly 28, 2006 @ 05:16 PM
    Excellent, exactly the kind of summary I was looking for until there are some docs available! :) Thanks! Btw, something seems messed up with the comments, they all are 3 days old when the post is 4 hours, and should they really go from newest to oldest in order (had me confused a while)?
  13. Ryan DaigleJuly 28, 2006 @ 05:16 PM
    Nate, if I understand you right, your issue is that GET: /users/1 => [:action => 'show', :id => 1] while GET: /users/1;deactivate => [:action => 'deactivate', :id => 1] and this represents an inconsistency? If so, I would only offer that it makes sense to provide default mappings for the basic CRUD actions as REST maps so nicely to CRUD. No sense in forcing people to say: GET: /users/1;show => [:action => 'show', :id => 1] When this is the most likely scenario. You're basically implying that no reasonable default mappings should be given, and that the user should have to explicitly write out their intentions every time? That seems a bit extreme?
  14. Kevin WilliamsJuly 28, 2006 @ 05:17 PM
    This article would be awesome if you mentioned all the named urls created for you. For example: users_url => "/users" instead of "/users/list user_url(@user) => "/users/1" instead of :action => 'show', :id => @user new_user_url => "/users/new" edit_user_url(@user) => "/users/1;edit" and so on.
  15. NateJuly 28, 2006 @ 05:17 PM
    So as to not be critical without offering a solution: If all action calls were appended to the url with a semicolon there would be perfect consistency.
  16. Mike MooreJuly 28, 2006 @ 05:17 PM
    Nate, when you call 123;edit, you are still getting the resource 123. The only difference is that the resource is formatted for editing. As I understand it there is a difference between methods and actions.
  17. RabbitJuly 28, 2006 @ 05:17 PM
    I'm sorry, I should elaborate. Everything will *appear* to work, until you attempt to use a *_url method. Then it tanks. So, in order to get the *_url methods working you must use the singular version of your resource.
  18. NateJuly 28, 2006 @ 05:17 PM
    What I don't understand about the magic method usage is why it isn't used for edit. Be honest: ;edit is gross. If we're faking PUT then why not fabricate EDIT? It seems just as likely that browser manufacturers will be compelled to implement PUT as to implement the brand-new EDIT. Otherwise we might as well just use ;create, ;destroy, etc. What happened to consistency?
  19. Lee JensenJuly 28, 2006 @ 05:17 PM
    Your nesting example may be a bit broken. Should be map.resources :groups do |m| m.resources :users end Also, be careful not to use map, inside the || because if you do it will override the map in the outer scope.
  20. ChrisJuly 28, 2006 @ 05:17 PM
    Newcomers to RAils will associate REST with RAILS, and go "ewwww!", and move away. This is unfortunate. Only the people who have been with rails for a decent time will give this new REST a try. Does DHH see this? What hapenned to those all so simple actions?
  21. DHHAugust 04, 2006 @ 10:49 AM
    Chris, what are you talking about? Who are these users that would go "ewww"? The REST support in Rails is more or less limited to one method: map.resources. Everything else stays the same and there's requirement that you use this way of declaring routes. If for some strange reason you don't subscribe to the REST principles, you can just ignore all of this and go about your daily business as if nothing happened. But then don't come crying when all the other apps are announcing their near-no-effort APIs and you're still fumbling with a SOAP strategy ;)
  22. jonniiAugust 04, 2006 @ 12:02 PM
    I've been using the simply_restful plugin for about a month now since sam stephenson pointed me in its direction and i have to say that it makes writing and testing your applications a lot easier. For example, instead of having a controller for users with login handling stuff etc, you create one for 'sessions' and your new/delete methods now login and logout your users. Now, all this was already achievable, but it makes you THINK in such a way where you map your controllers to CRUD actions which in turn makes them simpler .
  23. jamesAugust 04, 2006 @ 02:55 PM
    How does this work if your controller for the resource being mapped isn't named using standard conventions? Example: A members controller is really members_base_controller.rb because there is a members folder that holds nested controllers. So doing map.resources :members results in "uninitialized constant MembersController"
  24. John WhitleyAugust 04, 2006 @ 03:31 PM
    I've been hacking around with the earlier Simply RESTful plugin trying to get a RESTful controller in place that uses an alternate key for its ids. Examples for this include wikis ("/page/SomeLongPhrase"), package tracking interfaces ("/track/1Z2345678;detail"), and many more. While I was able to shoehorn something into place, it's required doing ugly things like: map.resources :foo, :requirements => { :id => %r{[^/;]+} } This has so far made route testing a major PITA (i.e. haven't gotten it working). Anyone worked on or seen commentary on similar efforts with the new Rails REST support?
  25. MikeAugust 05, 2006 @ 09:57 AM
    Great post. I'm having a string issue with nested routes though, and was wondering if anyone could shed some light on this... If I have: map.resources :users do |users| users.resources :items end When I try to call
    <%= link_to_remote 'Delete', :url => item_url(@item), :with => "'_method=delete'", :confirm => 'Are you sure?' %>
    
    From inside the user url http://localhost:3001/users/1/items Or edit_item_url(@item.id) it doesn't always render the correct URL, and sometimes I get a nil error trying to render the url. Not sure what I'm missing here, but any help is greatly appreciated. Thanks.
  26. jonniiAugust 07, 2006 @ 10:29 AM
    james: just like the rest of rails there are certain naming conventions that you need to stick to. john: I don't see how this would be causing you a problem, just override to_param in your model and get it return the 'id' field. I do that for users, and i can do user_url(user) without any problems. mike: You need to do item_url(user_id => user, @item) as the restful stuff doesn't infer the parameter from the request (which would be nice).
  27. Donald PiretAugust 16, 2006 @ 10:59 AM
    Ok, I really like this new RESTful stuff, it seems to make your apps simpler and more consistent. The only thing that's keeping me away from it for now is that I don't see a way of integrating this with standard HTML forms with the nice validation and error messages that I've come to thank Rails for so much. Same thing for RJS, how do you deal with validation? I don't think there's any good examples of this out there, so I'd be really happy if anybody could point me to one
  28. TomAugust 19, 2006 @ 10:11 PM
    Donald...I agree...I would love a reference to the same information. Does anyone have any tips?
  29. joonAugust 25, 2006 @ 07:36 PM
    In the nested resources, individual resources inside the nest are not working as noted in the article. # Make both groups and users RESTable, with users as nested resources of groups map.resources :groups do |group| group.resources :users end GET: /groups/13/users/1 => [:controller => 'users', :action => 'show', :group_id => 13, :id => 1] GET: /users/1 => DOES NOT WORK: Routing Error GET: /groups/1 => [:controller => 'groups', :action => 'show', :id => 1] GET: /users/1 only works after adding map.resources :users.
  30. Tim ConnorOctober 11, 2006 @ 10:36 PM
    Hey thanks for the heads up. While I might have gotten around to this eventually (I do have the v2 Agile book), this helped push me over the Edge into doing it now. As I mention in my linked to blog, if any of you are having trouble with the named routes and Markaby, just throw a .to_s on the end, as they have code now to not directly output a helper that is just returning a string for consumption.
  31. Tim ConnorOctober 17, 2006 @ 06:32 PM
    Umm, previous post has an old link. I just updated my permalinks to a new scheme, and forgot I had one in the wild already. That should be http://www.timocracy.com/articles/2006/10/11/1-markaby-and-rails-named-routes-and-form_for instead.
  32. Ryan DaigleOctober 18, 2006 @ 03:39 AM
    Tim - your signature link in the previous comment has been updated, thanks for the heads up.
  33. HavocadoDecember 06, 2006 @ 11:33 AM
    I like the idea of REST, but there's one issue that's bothering me. In my application, I want the resources to be accessible through pretty URLs. That is, multiple subdirectories and no rails-like magic numbers. So, instead of: GET: /users/1 I want to do like this: GET: /users/friends/jesse Is it possible with Simply Restful?
  34. DanDecember 08, 2006 @ 12:59 PM
    I agree with Havocado, search engines eat these key words up when you have pretty URLs. Just seems like simply restful is too tied to the idea of a database record and the numerical id that represents it. I thought URLs/resources were supposed to be opaque.
  35. Andre LewisJanuary 16, 2007 @ 05:51 PM
    I was having trouble getting nested resources up and running. I've put up a tutorial here, in case others are struggling with the same thing: http://earthcode.com/blog/2007/01/nested_crud_resources_in_rails.html Havacado/Dan: You don't have to be tied to the numeric id, you can override the model's to_param method to get a friendlier URL. For example: def to_param "#{id}-#{title.gsub(/[^a-z0-9]+/i, '-')}" end
  36. MattJanuary 18, 2007 @ 09:10 AM
    Someone please, I love the REST stuff... but how do I create a backend application only for admins? I've had people tell me hat I should just implement the backend into the frontend; if you are authenticated, the views show more actions and more actions are available. I can't do that... because the admin are is just not the same thing as the front end. I can't have my clients messing with templates and risk messing with admin templates at the same time. My first idea was to have methods in the app controller, for creating the template paths in the actions. That way the app controller would check the credentials and decide whether or not it's an admin view or public. But that just seemed weird, because then the action doesn't even know what it's getting. Maybe the admin view doesn't need as much as the public view etc. My second idea was to dynamically set the ActionController::Base.template_root in the app with a before_filter, but my tests no longer pass and actionmailer templates still need to be dealt with. And I'm still using the same controllers for front and back. Next, I tried changing the global config.view_path setting. This is more like what I see as a solution because it even fixes action mailer template paths, but I couldn't figure out how to dynamically change that in the env file. I couldn't instantiate my session object and do checks etc. I'm using the restful_authentication plugin by the way. I'm not sure what to do. Ideally, I'd really like to be able to create a sub url like "admin/users" but that is not restful is it? And I can't even figure out how to do that! "admin" specifies a location to users, but it is not a resource. Actually it's a path_prefix, but like I said, I can't get that to work using something like map.resources :users, :path_prefix => 'admin/' Any one have any ideas or solutions to this? Thanks for the tutorial by the way!
  37. Ian WhiteFebruary 01, 2007 @ 06:12 AM
    Hi Ryan, thanks for this great resource. A lot of you have commented asking for a simpler controller side to ActiveResource. You might like to check out a plugin I've written http://blog.ardes.com/articles/2007/02/01/announcing-resources_controller Cheers, Ian
  38. linojMarch 23, 2007 @ 11:03 PM

    Matt, you might try these articles (i have no affiliation but found them very helpful) http://www.fallenrogue.com/articles/178-Creating-a-RESTful-admin-section-in-Rails http://www.fallenrogue.com/articles/181-Creating-a-RESTful-admin-section-in-Rails-with-2-controllers

  39. sathishAugust 09, 2007 @ 06:57 AM

    this is wasteeeeeeeeee