This feature is schedule for: Rails v3.0
A few days ago I wrote about the new respond_with functionality of Rails 3. It’s basically a clean way to specify the resource to send back in response to a RESTful request. This works wonders for successful :xml and :json requests where the default response is to send back the serialized form of the resource, but still presents a lot of cruft when handling user-invoked :html requests (i.e. ‘navigational’ requests) and requests where error handling is required. For instance, consider your standard create action:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
class UsersController < ApplicationController::Base respond_to :html, :xml, :json def create @user = User.new(params[:user]) # Have to always override the html format to properly # handle the redirect if @user.save flash[:notice] = "User was created successfully." respond_with(@user, :status => :created, :location => @user) do |format| format.html { redirect_to @user } end # Have to send back the errors collection if they exist for xml, json and # redirect back to new for html. else respond_with(@user.errors, :status => :unprocessable_entity) do |format| format.html { render :action => :new } end end end end |
Even with the heavy lifting of respond_with you can see that there’s still a lot of plumbing left for you to do – plumbing that is mostly the same for all RESTful requests. Well José and the Rails team have a solution to this and have introduced controller responders.
Controller Responders
Controller responders handle the chore of matching the HTTP request method and the resource format type to determine what type of response should be sent. And since REST is so well-defined it’s very easy to establish a default responder to handle the basics.
Here’s what a controller utilizing responder support (now baked into respond_with) looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
class UsersController < ApplicationController::Base respond_to :html, :xml, :json def index respond_with(@users = User.all) end def new respond_with(@user = User.new) end def create respond_with(@user = User.create(params[:user])) end def edit respond_with(@user = User.find(params[:id])) end def update @user = User.find(params[:id]) @user.update_attributes(params[:user]) respond_with(@user) end end |
The built-in responder performs the following logic for each action:
- If the
:htmlformat was requested:- If it was a
GETrequest, invokerender(which will display the view template for the current action) - If it was a
POSTrequest and the resource has validation errors,render:new(so the user can fix their errors) - If it was a
PUTrequest and the resource has validation errors,render:edit(so the user can fix their errors) - Else, redirect to the resource location (i.e.
user_url)
- If it was a
- If another format was requested, (i.e.
:xmlor:json)- If it was a
GETrequest, invoke the:to_formatmethod on the resource and send that back - If the resource has validation errors, send back the errors in the requested format with the
:unprocessable_entitystatus code - If it was a
POSTrequest, invoke the:to_formatmethod on the resource and send that back with the:createdstatus and the:locationof the new created resource - Else, send back the
:okresponse with no body
- If it was a
Wading through this logic tree you can see that the default logic for each RESTful action is appropriately handled, letting your controller actions focus exclusively on resource retrieval and modification. And with that cruft out of the way your controllers will start to look even more similar – I suspect we’ll be seeing a solution for this coming around the bend shortly as well…?
So, just to recap the basics, here are a few action implementations side by side (the first being before responders and the latter being after):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# Old def index @users = User.all respond_to do |format| format.html format.xml { render :xml => @users } format.json { render :json => @users } end end # New def index respond_with(@users = User.all) end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# Old def create @user = User.new(params[:user]) if @user.save flash[:notice] = "User successfully created" respond_to do |format| format.html { redirect_to @user } format.xml { render :xml => @user, :status => :created, :location => user_url(@user) } format.json { render :json => @users, :status => :created, :location => user_url(@user) } end else respond_to do |format| format.html { render :new } format.xml { render :xml => @user.errors, :status => :unprocessable_entity } format.json { render :json => @user.errors, :status => :unprocessable_entity } end end end # New def create @user = User.new(params[:user]) flash[:notice] = "User successfully created" if @user.save respond_with(@user) end |
Oh yeah, that’s getting real lean.
Overriding Default Behavior
If you need to override the default behavior of a particular format you can do so by passing a block to respond_with (as I wrote about in the original article):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class UsersController < ApplicationController::Base respond_to :html, :xml, :json # Override html format since we want to redirect to the collections page # instead of the user page. def create @user = User.new(params[:user]) flash[:notice] = "User successfully created" if @user.save respond_with(@user) do |format| format.html { redirect_to users_url } end end end |
Nested Resources
It’s quite common to operate on resources within a nested resource graph (though I prefer to go one level deep, at most). For such cases you need to let respond_with know of the object hierarchy (using the same parameters as polymorphic_url):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class UsersController < ApplicationController::Base respond_to :html, :xml, :json # In this case, users exist within a company def create @company = Company.find(params[:company_id]) @user = @company.users.build(params[:user]) flash[:notice] = "User successfully created" if @user.save # Ensure that the new user location is nested within @company, # for html format (/companies/1/users/2.html) as well as # resource formats (/companies/1/users/2) respond_with(@company, @user) end end |
If you have a singleton resource within your resource graph just use a symbol instead of an actual object instance. So to get /admin/users/1 you would invoke respond_with(:admin, @user).
Custom Responders
While there’s no facility to provide your own responder classes, it will no doubt be added shortly. If you look at the current responder class definition, it’s a very simple API essentially only requiring a call method (more intuitively take a look at the :to_html and :to_format methods).
Stay tuned here for further refinements to this very handy functionality – you’re going to see a lot more tightening in the coming weeks.
tags: ruby, rubyonrails

wow. Very very useful. Maby I don’t need resource_controller gem anymore. Ra
Great writeup!
Thanks for the writeup. fyi, these changes are coming almost directly from merb. Merb has had this setup for over a year now.
http://www.loudthinking.com/posts/37-bringing-merbs-providesdisplay-into-rails-3
What can I say apart of this is a really nice addition to Rails. :)
Great news. I guess this could replace my heavy use of make_resourceful Definitly a MUST read on the way to 3.0, Thanks
Hmm now you have me thinking that I need to start building my newest apps on Edge Rails again like the good ol’ days :)
This looks good. Thank you for summarizing.
I might be overlooking something, but looking at the code for Responder, I see no way of overriding the default behavior when validation error occurs.
What if I wanted to render “foo” action if the user could not be saved?
It looks really clean now. I really disliked respond_to construct, it have always remind me of switch-case. The new respond_with looks great, I’m just wondering if it could be called automatically and not to have to write respond_with in every last line of the controller action code.
I wonder if it would be possible to just use the return value of the methods as arguments to respond_with in the case where no respond_to/render/redirect was called in the action? That would further cut down the default case to the bare minimum logic.
@Radoslav Stankov and Dan Kubb – After getting a running demo going on Rails 3.0.pre, I must agree with you guys that it would be nice to drop the respond_with in the default case. For example,
In any case, I’m very impressed with the code reduction with the implementation of this new feature.
Yeah, it should really be implicit to further dry the code up. I have used the ResourceController plugin before which already introduced a very nice DRY structure.
Put me in the new controller, coach!>
def show respond_with(@person = Person.find(params[:id])) end