Custom REST routes
When developing rails applications using REST, all controllers have only seven actions (index, show, new, create, edit, update and destroy). Occasionally there is a need for additional custom actions. They can be added very simply in config/routes.rb. For example, in part of an application showing a list of venues, you may want to list only venues which have not closed down, and you may want to mark a venue as closed_down.
Your initial config/routes.rb setup would look something like this:
ActionController::Routing::Routes.draw do |map|
map.resources :venues
end
To list all venues that are closed down you would first need to add the following action to your venue_controller.rb:
def closed
@venues = Venue.find(:all, :conditions => ['closed_down = ?', true])
end
However, trying to navigate to this action will result in a routing error. To fix this, you will need to amend routes.rb:
ActionController::Routing::Routes.draw do |map|
map.resources :venues, :collection => {:closed => :get}
end
The collection hash is used to specify actions which reference collections of objects instead of actions which reference one object. In this case the closed action is referencing a collection of venues. Inside the hash :closed is mapped to a GET request. This will also automatically create a named route: closed_venues_path. Cool!
So that’s working. Now to be able to mark a venue as closed_down.
In your venues_controller.rb you would add the following action:
def mark_closed_down @venue = Venue.find(params[:id]) @venue.update_attribute :closed_down, true redirect_to closed_venues_path end
However, trying to navigate to this action will currently throw an error, as following the current RESTful setup the application will try to render the show action, which will try to find a venue with id => ‘mark_closed_down’.
To fix this you need to make the following change to your routes.rb file:
ActionController::Routing::Routes.draw do |map|
map.resources :venues, :collection => {:closed => :get}, :member => { :mark_closed_down => :put }
end
The member hash is used to specify actions that reference one object in particular. In this case mark_closed_down is referencing one venue. Inside the hash :mark_closed_down is mapped to a PUT request. This also gives you a named route: mark_closed_down_venue_path(venue). Thats it!
You can add as many custom routes as you like to each controller, but you should do so with caution. If you find yourself needing lots of them it probably means that you have missed a model, or that you should create another controller. Each controller maps one to one with a resource, but not necessarily with a model. In this case, for example, it would be possible to map another resource in routes.rb maybe called closed_down_venues:
map.resources :closed_down_venues
Then create a closed_down_venues controller with:
script/generate controller closed_down_venues
The index action would then be the list of closed venues, and the update action would be the mark_closed_down action.
I hope this is all clear. Please add a comment if not, and I will do my best to clarify…
Trackbacks
Use this link to trackback from your own site.
The use of a get request to mark a venue as closed down doesn’t seem very RESTful to me.
Shouldn’t this be done with an update request rather than a get?
You are absolutely right Rick. I really should proof read my posts. I have updated that to a PUT request.
I would recommend to delegate the closed and mark_closed_down methods to the Venue model and leave the controller as dumb as possible. That makes it much simpler to refactor the controllers.
Thanks for this article, that helped me much !
and if you want a different form for this same new object, you can customize route like :
map.resources :venues, :new => { :good => :get } or
map.resources :venues, :new => { :bad => :get }
This will create a named route:
good_new_venue_path and bad_new_venue_path ;-)
look Module ActionController::Resources API for more comprehension
I’m glad to have helped you Joel.
Thanks for the workingwithrails recommendation!