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…