`Respond_to` Without All the Pain

When you generate a scaffold in Rails, you’ll see the usual respond_to blocks:

app/controllers/tasks_controller.rb
  def destroy
    @task.destroy
    respond_to do |format|
      format.html { redirect_to tasks_url, notice: 'Task was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

But some of your actions, like index, don’t have them!

app/controllers/tasks_controller.rb
  # GET /tasks
  # GET /tasks.json
  def index
    @tasks = Task.all
  end

This is bad. Why? If you hit /tasks.txt, and txt isn’t supported by your app, you’ll get the wrong error:

ActionView::MissingTemplate (Missing template tasks/index, application/index with {:locale=>[:en], :formats=>[:text], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :coffee, :jbuilder]}

This isn’t quite right. You should be telling the client that they’re requesting a format you don’t support, not that you can’t find the right file.

If this was a UnknownFormat error, you could return a better response code. Instead, these errors will get mixed together with other, unrelated errors, and it’ll be really hard to handle them.

You could add a respond_to block to your index action:

app/controllers/tasks_controller.rb
  # GET /tasks
  # GET /tasks.json
  def index
    @tasks = Task.all
    respond_to do |format|
      format.html
      format.json
    end
  end

Then, you’d get the exception and error code you’d expect:

Started GET "/tasks.txt" for 127.0.0.1 at 2014-11-03 22:05:12 -0800
Processing by TasksController#index as TEXT
Completed 406 Not Acceptable in 21ms

ActionController::UnknownFormat (ActionController::UnknownFormat):
  app/controllers/tasks_controller.rb:8:in `index'

Much better. But littering all your controllers with respond_to is crazy. It feels un-Rails-ish. It violates DRY. And it distracts you from the work your controller is actually doing.

You still want to handle bad formats correctly. So what do you do?

A respond_to shortcut

If you’re not doing anything special to render your objects, you can take a shortcut. If you write:

app/controllers/tasks_controller.rb
def index
  @tasks = Task.all
  respond_to :html, :json
end

it works the same way as writing the full respond_to block in index. It’s a short way to tell Rails about all the formats your action knows about. And if different actions support different formats, this is a good way to handle those differences without much code.

Handle formats at the controller level

Usually, though, each action in your controller will work with the same formats. If index responds to json, so will new, and create, and everything else. So it’d be nice if you could have a respond_to that would affect the entire controller:

app/controllers/tasks_controller.rb
class TasksController < ApplicationController
  before_action :set_task, only: [:show, :edit, :update, :destroy]
  respond_to :html, :json

  # GET /tasks
  # GET /tasks.json
  def index
    @tasks = Task.all
    respond_with(@tasks)
  end

And this actually works:

Started GET "/tasks.txt" for 127.0.0.1 at 2014-11-03 22:17:37 -0800
Processing by TasksController#index as TEXT
Completed 406 Not Acceptable in 7ms

ActionController::UnknownFormat (ActionController::UnknownFormat):
  app/controllers/tasks_controller.rb:8:in `index'

Exactly the kind of error we were hoping to get! And you didn’t have to mess with each action to do it.

Sometimes you’ll want to do different things depending on the state of a model. For instance, for create, you’d either redirect or re-render the form, depending on whether or not the model is valid.

Rails can handle this. But you still have to tell it which object you want it to check, with respond_with. So instead of:

app/controllers/tasks_controller.rb
  def create
    @task = Task.new(task_params)

    respond_to do |format|
      if @task.save
        format.html { redirect_to @task, notice: 'Task was successfully created.' }
        format.json { render :show, status: :created, location: @task }
      else
        format.html { render :new }
        format.json { render json: @task.errors, status: :unprocessable_entity }
      end
    end
  end

you can write:

app/controllers/tasks_controller.rb
  def create
    @task = Task.new(task_params)
    flash[:notice] = "Task was successfully created." if @task.save
    respond_with(@task)
  end

This way, you separate your code from the formats you respond to. You can tell Rails once which formats you want to handle. You don’t have to repeat them in every action.

The responders gem

In Rails 4.2, there’s a catch: respond_with is no longer included. But you can get it back if you install the responders gem. And the responders gem brings some other nice features with it.

You can set flash messages in respond_with by including responders :flash at the top of your controller:

app/controllers/tasks_controller.rb
class TasksController < ApplicationController
  responders :flash

Conveniently, you can set defaults for these flash messages in your locale files.

Also, if you have the responders gem in your Gemfile and you generate a Rails scaffold, the generator will create controllers using respond_with instead of respond_to:

app/controllers/tasks_controller.rb
class TasksController < ApplicationController
  before_action :set_task, only: [:show, :edit, :update, :destroy]
  respond_to :html, :json
  
  def index
    @tasks = Task.all
    respond_with(@tasks)
  end

  def show
    respond_with(@task)
  end

  # ...

This is a lot cleaner than the scaffolds Rails comes with.

And finally, if you want to only respond with a format for specific controller actions, you can call respond_to multiple times:

class TasksController < ApplicationController
  respond_to :html
  respond_to :js, only: :create
end

Thanks to Jeroen Weeink in the comments for that last tip!

respond_with or respond_to?

If you want to return different information for different formats, you have a few options. The controller-level respond_to combined with respond_with is a great way to get short controllers. But it tends to help the most when all of your controller actions respond to the same format, and act in the way Rails expects them to.

Sometimes, though, you want to be able to have a few actions that act differently. The one-liner respond_to is great for handling that situation.

If you need more control, use the full respond_to with a block, and you can handle each format however you want.

With any of these, requests for formats you don’t support will get the right error. And both your app and its clients will be a lot less confused.

Pushing through tutorials, and still not learning anything?

Have you slogged through the same guide three times and still don't know how to build a real app?

In this free 7-day Rails course, you'll learn specific steps to start your own Rails apps — without giving up, and without being overwhelmed.

You'll also discover the fastest way to learn new Rails features with your 32-page sample of Practicing Rails: Learn Rails Without Being Overwhelmed.

Sign up below to get started:

Powered by ConvertKit

Did you like this article? You should read these:

Comments