Flowdock
method

respond_to

Importance_5
respond_to(*types, &block) public

Without web-service support, an action which collects the data for displaying a list of people might look something like this:

  def list
    @people = Person.find(:all)
  end

Here’s the same action, with web-service support baked in:

  def list
    @people = Person.find(:all)

    respond_to do |wants|
      wants.html
      wants.xml { render :xml => @people.to_xml }
    end
  end

What that says is, "if the client wants HTML in response to this action, just respond as we would have before, but if the client wants XML, return them the list of people in XML format." (Rails determines the desired response format from the HTTP Accept header submitted by the client.)

Supposing you have an action that adds a new person, optionally creating their company (by name) if it does not already exist, without web-services, it might look like this:

  def add
    @company = Company.find_or_create_by_name(params[:company][:name])
    @person  = @company.people.create(params[:person])

    redirect_to(person_list_url)
  end

Here’s the same action, with web-service support baked in:

  def add
    company  = params[:person].delete(:company)
    @company = Company.find_or_create_by_name(company[:name])
    @person  = @company.people.create(params[:person])

    respond_to do |wants|
      wants.html { redirect_to(person_list_url) }
      wants.js
      wants.xml  { render :xml => @person.to_xml(:include => @company) }
    end
  end

If the client wants HTML, we just redirect them back to the person list. If they want Javascript (wants.js), then it is an RJS request and we render the RJS template associated with this action. Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also include the person’s company in the rendered XML, so you get something like this:

  <person>
    <id>...</id>
    ...
    <company>
      <id>...</id>
      <name>...</name>
      ...
    </company>
  </person>

Note, however, the extra bit at the top of that action:

  company  = params[:person].delete(:company)
  @company = Company.find_or_create_by_name(company[:name])

This is because the incoming XML document (if a web-service request is in process) can only contain a single root-node. So, we have to rearrange things so that the request looks like this (url-encoded):

  person[name]=...&person[company][name]=...&...

And, like this (xml-encoded):

  <person>
    <name>...</name>
    <company>
      <name>...</name>
    </company>
  </person>

In other words, we make the request so that it operates on a single entity—a person. Then, in the action, we extract the company data from the request, find or create the company, and then create the new person with the remaining data.

Note that you can define your own XML parameter parser which would allow you to describe multiple entities in a single request (i.e., by wrapping them all in a single root note), but if you just go with the flow and accept Rails' defaults, life will be much easier.

If you need to use a MIME type which isn’t supported by default, you can register your own handlers in environment.rb as follows.

  Mime::Type.register "image/jpg", :jpg
Show source
Register or log in to add new notes.
June 25, 2008
13 thanks

How to test different responses in controller tests/specs

When you want to write a controller test or spec (rspec) to test out a different response type other than html, just set the HTTP_ACCEPTS header like so before the request:

@request.env['HTTP_ACCEPT'] = "application/rss"
post :create, :blog => {}
November 26, 2008
10 thanks

Types array shorthand

You can have respond_to blocks that look like this:

respond_to do |format|
  format.html
  format.xml
end

Here each individual format doesn’t receive a block and so Rails automatically tries to render the appropriate view for the mime type (e.g. action.html.erb, action.xml.erb or as a last resort action.erb)

You can do exactly the same thing by passing an array of Mime types to respond_to like this:

respond_to(:html, :xml)
June 26, 2008
8 thanks

Rails defined Mime Types

Here are all the default Rails Mime Types:

"*/*"                      => :all
"text/plain"               => :text
"text/html"                => :html 
"application/xhtml+xml"    => :html
"text/javascript"          => :js 
"application/javascript"   => :js 
"application/x-javascript" => :js 
"text/calendar"            => :ics   
"text/csv"                 => :csv   
"application/xml"          => :xml 
"text/xml"                 => :xml 
"application/x-xml"        => :xml 
"text/yaml"                => :yaml 
"application/x-yaml"       => :yaml 
"application/rss+xml"      => :rss   
"application/atom+xml"     => :atom  
"application/json"         => :json 
"text/x-json"              => :json
February 10, 2009
8 thanks

Security issue with non-HTML formats

Please note that using default to_xml or to_json methods can lead to security holes, as these method expose all attributes of your model by default, including salt, crypted_password, permissions, status or whatever you might have.

You might want to override these methods in your models, e.g.:

def to_xml
  super( :only => [ :login, :first_name, :last_name ] )
end

Or consider not using responds_to at all, if you only want to provide HTML.

January 24, 2009
8 thanks

How to test different responses of respond_to

You can shorten this:

@request.env['HTTP_ACCEPT'] = "application/rss"

To this:

@request.accept = "application/rss"

Also, if you send more than one Mime type, it will render the first one:

@request.accept = "text/javascript, text/html" #=> renders JS
@request.accept = "text/html, text/javascript" #=> renders HTML
April 1, 2009
5 thanks

Ordering of format blocks is important

The order in which your format blocks appear, like:

format.html { } format.js { }

are used to infer priority in cases where the appropriate format is ambiguous.

June 4, 2009
5 thanks

A catch-all format

If you’d like to specify a respond_to only for 1 or a few formats and render something else for all other formats, eg: (action.rss returns a feed but action.html or action.js should just render 404), use format.all:

respond_to do |format|
  format.rss { render_rss }
  format.all { render_404 }
end

Rails will render an empty string for all formats that don’t specify a response explicitly.

June 25, 2008
4 thanks

Custom MIME Type

After you register a custom Mime::Type like stated above, you can do:

respond_to do |format|
  # .jpg corresponds to the second argument passed to #register
  # Mime::Type.register "image/jpg", :jpg
  format.jpg { ...do something here... }
end
June 19, 2009
2 thanks

Re: How to test different responses

In addition to using:

@request.accept = "text/javascript" #=> request JS

as rubymaverick and nachocab suggested, you can also pass :format when calling your action, eg:

it "GET #most_viewed renders #most_viewed.js.rjs template if js requested" do
  get :most_viewed, :format => 'js'
  response.should render_template('most_viewed')
end
August 5, 2014
0 thanks

include respond_to in your controller action only if there are multiple formats of that view

consider this

def index
 @users=User.get_users
  respond_to do |format|
   format.html
   format.json
   format.js
  end
end

is good if you have a call to users/index by both

<%= link_to ("Show users",user_path)%>
##will render users/index.html.erb
===Also same call but with ajax
<%= link_to ("Show users",user_path,remote=>true)%>
 ##will render users/index.js.erb..handled by respond_to   block
===Also same call but with json
<%= link_to ("Show users",user_path,remote=>true,:format=>json)%>
##will render users/index.json.erb..handled by respond_to   block.

But if you have just first one,so remove respond_to block as you are sure that you only need index.html.erb ALWAYS

def index
  @users=User.get_users
end 

So if your action is being called both by BOTH ajax,non-ajax call,its good to use respond_to.

April 24, 2010
0 thanks

Rendering After Exception In respond_to() Block

Remember, format blocks set the response’s content type. This can present problems when handling errors.

class MediaController
  rescue_from  ActionController::MissingFile do |e|
    # User's browser probably wont display this 
    # Content-Type is application/x-shockwave-flash
    render :file => File.join(Rails.public_path, '404.html'), :status => 404 
  end

  # show details or stream video
  def show
    @media = Media.find params[:id]
    respond_to do |format|
      format.html
      format.flv { send_file @media.path, :disposition => 'inline' }
    end
  end
end

For these situations you must set :content_type when calling render:

render :file => File.join(Rails.public_path, '404.html'), :status => 404, :content_type => 'text/html' 
May 14, 2010
0 thanks

You must use the yielded object

A warning note (at least for v2.3.4): if you don’t use the yielded format object, you will get a cryptic error like this:

NoMethodError (You have a nil object when you didn't expect it!
The error occurred while evaluating nil.call):
 app/controllers/comments_controller.rb:11:in `create'

So make sure you use it!