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:
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:
1、If the :html format was requested:
1)If it was a GET request, invoke render (which will display the view template for the current action)
2)If it was a POST request and the resource has validation errors, render :new (so the user can fix their errors)
3)If it was a PUT request and the resource has validation errors, render :edit (so the user can fix their errors)
4)Else, redirect to the resource location (i.e. user_url)
2、If another format was requested, (i.e. :xml or :json)
1)If it was a GET request, invoke the :to_format method on the resource and send that back
2)If the resource has validation errors, send back the errors in the requested format with the :unprocessable_entity status code
3)If it was a POST request, invoke the :to_format method on the resource and send that back with the :created status and the :location of the new created resource
4)Else, send back the :ok response with no body
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):
# 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
# 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
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:
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):
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.