16

I am building a simple json API in Rails 3.1. I created a controller that has two functions:

class Api::DogsController < ActionController::Base
  respond_to :json, :xml
  def index
    respond_with({:msg => "success"})
  end

  def create
    respond_with({:msg => "success"})
  end
end

In routes.rb I have

namespace :api do 
  resources :dogs
end

When I make a get request to http://localhost:3000/api/dogs I get the correct json from above. When I make a post to the same url, I get a rails exception:

ArgumentError in Api::DogsController#create
Nil location provided. Can't build URI.
actionpack (3.1.0) lib/action_dispatch/routing/polymorphic_routes.rb:183:in `build_named_route_call`
actionpack (3.1.0) lib/action_dispatch/routing/polymorphic_routes.rb:120:in `polymorphic_url'
actionpack (3.1.0) lib/action_dispatch/routing/url_for.rb:145:in `url_for'

But if I change the create code to

def create
  respond_with do |format|
    format.json { render :json => {:msg => "success"}}
  end
end

it returns the json just fine.

Can someone explain what is going on here?

Thomas Tremble
  • 281
  • 1
  • 3
  • 5

3 Answers3

38

After coming across this issue myself and overcoming it, I believe I can supply an answer.

When you simply say:

def create
  respond_with({:msg => "success"})
end

What rails tries to do is "guess" a url that the newly created resource is available at, and place it in the HTTP location header. That guess fails miserably for a hash object (the location it deduces is nil, which leads to the error message you are seeing).

To overcome this issue, then, you would need to do the following:

def create
  respond_with({:msg => "success"}, :location => SOME_LOCATION)
end

Assuming that you know where the new resource is located. You can even specify "nil" as "SOME_LOCATION" and that will work (somewhat absurdly).

Aubergine
  • 1,117
  • 10
  • 11
  • 1
    Setting location to the string literal "nil" worked: `respond_with({:error => "bad"}, :status => 401, :location => "nil")` –  Nov 16 '11 at 17:53
  • @kwbeam I was talking about an actual nil value in the last paragraph (I realize now that the quotes may have been a tad misleading) – Aubergine Nov 16 '11 at 21:13
  • I did think you meant nil the object, not the string literal, but when I tried it, it still failed. However, I just changed it back to nil (the object, not the string literal) and it is working. So apparently I had a fail somewhere, but I'm not able to reconstruct what I did wrong. Sorry for the confusion. (I had updated the version of Rails and many gems in the interim, so perhaps there was a bug that got fixed...but I'm not sure) –  Nov 20 '11 at 22:00
  • Thanks a lot. I had this same problem and this solved it. If StackOverflow would allow me, I would vote you up twice. Thanks! – Ernesto Sep 26 '12 at 13:49
  • +1 - Helped me too! Thanks so much! Note: I just set my location to "" and it worked fine for me. – Topher Fangio Oct 29 '12 at 21:41
  • fyi if you're responding with a json object, the location should be set to the path to that object, if I understand correctly. – Steven Soroka Jul 18 '13 at 15:35
1

I had the problem myself.

It is, like Aubergine says, something related with the http location header.

Actually, rails seems to build this location using by default the show route.

If you don't have a show action, -which is weird in an API, but can happen (i think)`, then you have to set a location by yourself. I have no idea what is the standard in this case.

If it happens that you need a show route, then code it, and everything should work fine.

Cheers.

Pasta
  • 1,750
  • 2
  • 14
  • 25
0

I have found that errors = {:error => @device.errors.full_messages} respond_with( errors, :status => :bad_request, :location => nil) works. The :location is required and setting it to nil helps when using with spec: expect(last_response.status).not_to eql 201 expect(last_response.location).to be_nil

The problem I was having is that I was not returning the errors hash, just the status. Adding the errors hash and setting the location myself solved it.

Donald French
  • 1,731
  • 1
  • 17
  • 30