What's New in Edge Rails: Default RESTful Rendering

Posted by ryan
at 9:39 AM on Monday, August 10, 2009

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 :html format was requested:
    • If it was a GET request, invoke render (which will display the view template for the current action)
    • If it was a POST request and the resource has validation errors, render :new (so the user can fix their errors)
    • If it was a PUT request 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 another format was requested, (i.e. :xml or :json)
    • If it was a GET request, invoke the :to_format method on the resource and send that back
    • If the resource has validation errors, send back the errors in the requested format with the :unprocessable_entity status code
    • 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
    • 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):

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

Comments

Leave a response

  1. Hussein MorsyAugust 10, 2009 @ 11:30 AM

    wow. Very very useful. Maby I don’t need resource_controller gem anymore. Ra

  2. ChessAugust 10, 2009 @ 11:49 AM

    Great writeup!

  3. Scott MotteAugust 10, 2009 @ 11:59 AM

    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

  4. Prem SichanugristAugust 10, 2009 @ 12:42 PM

    What can I say apart of this is a really nice addition to Rails. :)

  5. georgeAugust 10, 2009 @ 06:39 PM

    Great news. I guess this could replace my heavy use of make_resourceful Definitly a MUST read on the way to 3.0, Thanks

  6. Ryan WalkerAugust 10, 2009 @ 08:22 PM

    Hmm now you have me thinking that I need to start building my newest apps on Edge Rails again like the good ol’ days :)

  7. Rasmus Rønn NielsenAugust 11, 2009 @ 02:49 AM

    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?

  8. Radoslav StankovAugust 11, 2009 @ 03:36 AM

    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.

  9. Dan KubbAugust 11, 2009 @ 03:46 AM

    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.

  10. Conrad TaylorAugust 11, 2009 @ 06:26 AM

    @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,

    1. explicit form def index respond_with(@users = User.all) end
    1. implicit form def index @users = User.all end

    In any case, I’m very impressed with the code reduction with the implementation of this new feature.

  11. Philipp KursaweAugust 21, 2009 @ 04:58 AM

    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.

  12. RobsSeptember 10, 2009 @ 03:55 PM

    Put me in the new controller, coach!>

    def show respond_with(@person = Person.find(params[:id])) end