58

I am converting a Rails 2 application to Rails 3. I currently have a controller set up like the following:

class Api::RegionsController < ApplicationController
  respond_to :xml, :json
end

with and an action that looks like the following:

def index
  @regions = Region.all

  respond_with @regions  
end

The implementation is pretty straightforward, api/regions, api/regions.xml and api/regions.json all respond as you would expect. The problem is that I want api/regions by default to respond via XML. I have consumers that expect an XML response and I would hate to have them change all their URLs to include .xml unless absolutely necessary.

In Rails 2 you would accomplish that by doing this:

respond_to do |format|
  format.xml { render :xml => @region.to_xml }
  format.json { render :json => @region.to_json }
end

But in Rails 3 I cannot find a way to default it to an XML response. Any ideas?

James Chevalier
  • 10,604
  • 5
  • 48
  • 74
bdorry
  • 978
  • 1
  • 8
  • 14
  • How about api/regions.html? Should that render HTML? Or...? – zetetic Jan 10 '11 at 18:38
  • No HTML should be rendered by the API, the APIs are separated out because a limited set of functionality is available through the API compared to the site itself. – bdorry Jan 12 '11 at 22:33
  • This blog post covers it: http://ryandaigle.com/articles/2009/8/10/what-s-new-in-edge-rails-default-restful-rendering Particularly, look at the section titled "Overriding Default Behavior" – Cody Caughlan Jan 10 '11 at 04:51

5 Answers5

81

If I understand what you are trying to do, you probably can solve the issue by setting the default resource format to XML. This will allow your users to make requests using 'api/regions' and have the response default to XML. Take a look at look at the 'Controller Namespaces and Routing' and the 'Defining Defaults' sections at:

http://guides.rubyonrails.org/routing.html

You could do something like the following in routes.rb:

namespace "api" do
  resources :regions, :defaults => { :format => 'xml' }
end

Then you should be able to have the following work for your controller methods:

class Api::RegionsController < ApplicationController
  respond_to :xml, :json

  def index 
    respond_with(@regions = Region.all)
  end
end
Mike Clymer
  • 1,218
  • 11
  • 15
  • 1
    For some reason this doesn't work for me. I'm getting an ActionController::UnknownFormat error. – Brandon Oct 09 '14 at 04:56
  • Starting from Rails 4.2 version, you will need to use `gem responder` to be able to use respond_with. You can read more about [respond_with here](https://kolosek.com/rails-respond_to-block/). – Nesha Zoric Apr 11 '18 at 13:50
51

I have been fighting this issue today, and I settled for the before_filter solution you mentioned yourself in your comment:

before_filter :default_format_xml

# Set format to xml unless client requires a specific format
# Works on Rails 3.0.9
def default_format_xml
  request.format = "xml" unless params[:format]
end

This solution also allows for taking into account content negotiation, which was a factor in my case. I wanted web browsers to get an HTML view but custom clients (with no Accept headers) to get JSON. This solved my problem:

before_filter :default_format_json

def default_format_json
  if(request.headers["HTTP_ACCEPT"].nil? &&
     params[:format].nil?)
    request.format = "json"
  end
end
clacke
  • 7,688
  • 6
  • 46
  • 48
30

Not what you're after but related:

def index
  @regions = Region.all
  respond_to do |format|
    format.json { render :json => @regions }
    format.any(:xml, :html) { render :xml => @regions }
  end
end

"Respond to also allows you to specify a common block for different formats by using any"

Heikki
  • 15,329
  • 2
  • 54
  • 49
0

Well, as you have been noted that each format should be explicitly rendered with specific render call, you can also avoid any request with unknown or unsupported format, for my example called default, as follows:

rescue_from ActionController::UnknownFormat, with: ->{ render nothing: true }

You can simulate the unknown format call with the simple browser (exmp.firefox) line (in development mode):

http://localhost/index.default

It will call :index method of a root controller with format called as default.

Малъ Скрылевъ
  • 16,187
  • 5
  • 56
  • 69
-1

An easy but ugly solution is to override html content type handling to render xml:

   respond_to :html, :xml, :json

   def index
      @regions = Region.all
      respond_with @regions do |format|
        format.html { render :xml => @regions }
      end
    end
buru
  • 3,190
  • 25
  • 35
  • Thanks, this is similar to how I was doing it in Rails 2, but I was hoping that there was a way to do this without listing it in every controller action. – bdorry Jan 12 '11 at 22:34