{"id":49,"date":"2009-08-07T15:35:20","date_gmt":"2009-08-07T18:35:20","guid":{"rendered":"http:\/\/blog.plataformatec.com.br\/?p=49"},"modified":"2014-03-24T18:46:46","modified_gmt":"2014-03-24T21:46:46","slug":"embracing-rest-with-mind-body-and-soul","status":"publish","type":"post","link":"https:\/\/blog.plataformatec.com.br\/2009\/08\/embracing-rest-with-mind-body-and-soul\/","title":{"rendered":"Embracing REST with mind, body and soul"},"content":{"rendered":"

UPDATE:<\/strong> ActionController::Renderer was renamed to ActionController::Responder, so this post was changed to properly reflect such changes.<\/p>\n

About two and a half years ago, resources started to be a first class citizen in Rails when version 1.2 was released and it was all about RESTful admiration and HTTP Lovefest<\/a>. Since then we’ve added map.resources<\/em> to our routes, started to use different formats in respond_to<\/em> and really learned how to love all HTTP verbs.<\/p>\n

Your application entry point (the router) has become completely RESTful, but it still haven’t reached ActionPack core. Today we are bringing the missing ingredient: make your controllers more resource aware.<\/p>\n

The first step: respond_with(@resource)<\/h3>\n

About one week ago the first step was given<\/a>. We brought Merb’s provide\/display into Rails, just as DHH proposed<\/a>: you can define supported formats at the class level and tell in the instance the resource to be represented by those formats. Let’s see some code:<\/p>\n

  class UsersController < ApplicationController\r\n    respond_to :html, :xml, :json\r\n\r\n    def index\r\n      @users = User.all\r\n      respond_with(@users)\r\n    end\r\n  end<\/pre>\n

It works like this: when a request comes, for example with format xml, it will first search for a template at users\/index.xml. If the template is not available, it tries to render the resource given (in this case, @users) by calling :to_xml on it. Before Rails 3.0, the equivalent to the index action above would be:<\/p>\n

  class UsersController < ApplicationController\r\n    def index\r\n      @users = User.all\r\n      respond_to do |format|\r\n        format.html\r\n        format.xml { render :xml => @users }\r\n        format.json { render :json => @users }\r\n      end\r\n    end\r\n  end<\/pre>\n

The gain with respond_with introduction is more obvious if you compare index, new and show actions:<\/p>\n

  class UsersController < ApplicationController\r\n    respond_to :html, :xml, :json\r\n\r\n    def index\r\n      @users = User.all\r\n      respond_with(@users)\r\n    end\r\n\r\n    def new\r\n      @user = User.new\r\n      respond_with(@user)\r\n    end\r\n\r\n    def show\r\n      @user = User.find(params[:id])\r\n      respond_with(@user)\r\n    end\r\n  end<\/pre>\n

With older version:<\/p>\n

  class UsersController < ApplicationController\r\n    def index\r\n      @users = User.all\r\n      respond_to do |format|\r\n        format.html\r\n        format.xml { render :xml => @users }\r\n        format.json { render :json => @users }\r\n      end\r\n    end\r\n\r\n    def new\r\n      @user = User.new\r\n      respond_to do |format|\r\n        format.html\r\n        format.xml { render :xml => @user }\r\n        format.json { render :json => @user }\r\n      end\r\n    end\r\n\r\n    def show\r\n      @user = User.find(params[:id])\r\n      respond_to do |format|\r\n        format.html\r\n        format.xml { render :xml => @user }\r\n        format.json { render :json => @user }\r\n      end\r\n    end\r\n  end<\/pre>\n

However, even if respond_with is full featured (Ryan Daigle has done an excellent job covering all respond_with features<\/a>), it started to show some flaws on create, update and destroy actions. A default create action could be written with respond_with as:<\/p>\n

  def create\r\n    @user = User.new(params[:user])\r\n    if @user.save\r\n      flash[:notice] = \"User was created successfully.\"\r\n      respond_with(@user, :status => :created, :location => @user) do |format|\r\n        format.html { redirect_to @user }\r\n      end\r\n    else\r\n      respond_with(@user.errors, :status => :unprocessable_entity) do |format|\r\n        format.html { render :action => :new }\r\n      end\r\n    end\r\n  end<\/pre>\n

You can notice that:<\/p>\n

    \n
  1. You have to call respond_with twice;<\/li>\n
  2. On the first respond_with, you have to give the location twice. One as a hash and other as parameter to redirect_to;<\/li>\n
  3. And by giving a block to respond_with, you focus more on the exception than on the default behavior.<\/li>\n<\/ol>\n

    Suddenly we realized that respond_with is useful just for GET requests. There was no HTTP Lovefest, it was more like HTTP monotheism.<\/p>\n

    2. Second step: Love all<\/h3>\n

    At this point, we started to ask ourselves: why can’t respond_with include HTTP verb semantics? Isn’t that what RESTful is all about?<\/p>\n

    After this commit<\/a>, we brought all HTTP verbs to respond_with, but only for resourceful formats like xml and json (ie. formats that don’t need to render a template). Then our create action with POST request could be rewritten as:<\/p>\n

      def create\r\n    @user = User.new(params[:user])\r\n    respond_with(@user) do |format|\r\n      if @user.save\r\n        flash[:notice] = \"User was created successfully.\"\r\n        format.html { redirect_to @user }\r\n      else\r\n        format.html { render :action => :new }\r\n      end\r\n    end\r\n  end<\/pre>\n

    Internally, when a xml request happened, respond_with would check the current request method (in this case, POST) and whether the resource has errors or not. Depending on these values, it will render the resource or the resource errors, setting accordingly the status and location headers. Now we just have to worry with non-RESTful requests, like html, mobile and iphone… (which we call navigational formats).<\/p>\n

    Personally, I was quite happy with the results at this iteration, since it solves two of the three problems exposed previously. However, Jeremy Kemper and Yehuda Katz wanted more. And they were right, yeah!<\/p>\n

    3. Third step: Responder<\/h3>\n

    In step 2, we were able to abstract POST, PUT and DELETE requests for formats like xml and json, but we still would have to repeat html behavior through all controllers, even if almost all of them behave similarly.<\/p>\n

    So what we want is a simple way to tell the controller how to render our resources depending on the format AND HTTP verb<\/strong>. In this commit<\/a>, we’ve added ActionController::Responder<\/strong>.<\/p>\n

    By default, ActionController::Responder holds all formats behavior in a method called to_format. It’s similar to this:<\/p>\n

      def to_format\r\n    return render unless resource.respond_to?(:\"to_#{format}\")\r\n\r\n    if get?\r\n      render format => resource\r\n    elsif has_errors?\r\n      render format => resource.errors, :status => :unprocessable_entity\r\n    elsif post?\r\n      render format => resource, :status => :created, :location => resource\r\n    else\r\n      head :ok\r\n    end\r\n  end<\/pre>\n

    As you can see, it renders the resource based on the HTTP verb and whether it has errors or not. If some format, like :html, does not fall into the to_format behavior, we just need to define a to_html in ActionController::Responder, which by default is:<\/p>\n

      def to_html\r\n    if get?\r\n      render\r\n    elsif has_errors?\r\n      render :action => (post? ? :new : :edit)\r\n    else\r\n      redirect_to resource\r\n    end\r\n  end<\/pre>\n

    As result, you have your resources representation encapsulated in one place. Your controller code just have to send the resource using respond_with(@resource)<\/strong> and respond_with will call ActionController::Responder<\/strong> which will know what to do. Our create action (POST request) can then be written as:<\/p>\n

      def create\r\n    @user = User.new(params[:user])\r\n    flash[:notice] = \"User was created successfully.\" if @user.save\r\n    respond_with(@user)\r\n  end<\/pre>\n

    If you need to change the redirect URL, you can overwrite just the html behavior:<\/p>\n

      def create\r\n    @user = User.new(params[:user])\r\n    flash[:notice] = \"User was created successfully.\" if @user.save\r\n    respond_with(@user) do |format|\r\n      format.html { redirect_to user_confirmation_url }\r\n    end\r\n  end<\/pre>\n

    On the other hand, if you want to change the redirect url and the Location header for XML and JSON, you can simply give :location as option:<\/p>\n

      def create\r\n    @user = User.new(params[:user])\r\n    flash[:notice] = \"User was created successfully.\" if @user.save\r\n    respond_with(@user, :location => user_confirmation_url)\r\n  end<\/pre>\n

    The best of all is that the responder implementation is quite simple and straight-forward, but still powerful. We haven’t enforced any restriction in the API. Anything that responds to :call can be a responder, so you can create your custom classes or even give procs, fibers and so on.<\/p>\n

    Embrace REST in your design and enjoy a consistent behavior through all your controllers. Spread the word!<\/p>\n","protected":false},"excerpt":{"rendered":"

    UPDATE: ActionController::Renderer was renamed to ActionController::Responder, so this post was changed to properly reflect such changes. About two and a half years ago, resources started to be a first class citizen in Rails when version 1.2 was released and it was all about RESTful admiration and HTTP Lovefest. Since then we’ve added map.resources to our … \u00bb<\/a><\/p>\n","protected":false},"author":4,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"footnotes":""},"categories":[1],"tags":[86,7,17,57,18],"aioseo_notices":[],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/49"}],"collection":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/comments?post=49"}],"version-history":[{"count":27,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/49\/revisions"}],"predecessor-version":[{"id":3780,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/49\/revisions\/3780"}],"wp:attachment":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/media?parent=49"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/categories?post=49"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/tags?post=49"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}