Rails respond_to block Explained

Rails respond_to block Explained

When you start your first Rails application you might think that controller actions are only capable of rendering HTML. So why and how to use a respond_to block? Here, we will show you how handy respond_to blocks can be.

respond_to

The first step is to create a new project and generate a scaffold in Rails. Let's take a look at some of our controller actions.

def index
  @blogs = Blog.all
end

As you can see, index action does not have a respond_to block. So, this will work when just rendering an HTML. But what if, however, a client asks to get the page in TEXT format? It will lead to an exception:

ActionView::MissingTemplate (Missing template blogs/index ... with { ... :formats=>[:text], ...})

Rails goes through the registered formats and tries to find a compatible format for the MIME type in the request. If there is no handler it will raise an error. Credits to max.

Instead of telling our clients that the file is missing, we want to tell them that the requesting format is not supported. You could add a respond_to block to your index action:

def index
  @blogs = Blog.all

  respond_to do |format|
    format.html # index.html
    format.js   # index.js
    format.xml { render xml: @blogs }
  end
end

After the change, the client will get 406 error when the format is not supported. Also, your index action will respond to two new formats: js and xml.

If you need a simple and quick way to render your objects, you can take this shortcut, it works in the same way as the example above:

def index
  @blogs = Blog.all
  respond_to :html, :json, :xml
end

Not sure if this really works? Go on and test it using RSpec and FactoryBot!

Responding to the Same Format

It’d be nice if you could have a respond_to that would affect the entire controller. Usually, each action in your controller can work with the same formats, you can achieve this by using respond_with. Here is an example of how to implement it:

class BlogController < ApplicationController
  respond_to :html, :json, :xml

  # GET /blogs
  # GET /blogs.json
  # GET /blogs.xml
  def index
    @blogs = Blog.all
    respond_with(@blogs)
  end
end

If you need more control and want to be able to have a few actions that act differently, you can always use a full respond_to block.

In Rails 4.2, respond_with is no longer included. But you can get it back if you install the responder gem. Once you install it and generate a Rails scaffold, the generator will create controllers using respond_with instead of respond_to.

class BlogController < ApplicationController
  before_action :set_blog, only: [:show, :edit, :update, :destroy]
  respond_to :html, :json
  
  def index
    @blogs = Blog.all
    respond_with(@blogs)
  end
  ...
end

Format ALL or Format ANY?

If you’d like to specify a respond_to to render something for all formats while keeping other options untouched, you can always use format.all in the following way:

respond_to do |format|
  format.csv { render_csv }
  format.all { render_404 }
end

Use format.any if you want to specify a common block of different formats:

def index
  @blogs = Blog.all

  respond_to do |format|
    format.html
    format.any(:xml, :json) { 
      render request.format.to_sym @blogs 
    }
  end
end

All the controller actions need to be tested to make sure they work as intended!

Defining Custom MIME Type

If you need to use a MIME type which isn't supported by default, you can register your own handlers in config/initializers/mime_types.rb:

Mime::Type.register "text/markdown", :markdown

That's it for this article! Thank you for your time reading it!

Be sure to subscribe to our newsletter, we will respond_to with many more future articles!