Given the following controller in rails:
class AccountsController < ApplicationController
respond_to :json, :xml
def update
@account = Account.where(uuid: params[:id]).first
unless @account.nil?
if @account.update_attributes params[:account]
respond_with @account, location: account_url(@account)
else
respond_with error_hash, status: :unprocessable_entity, root: :error, location: api_account_url(@account)
end
else
respond_with error_hash, status: :not_found, root: :error, location: accounts_url
end
end
def error_hash
{ :example => "Example for this question", :parameter => 42 }
end
end
I would expect a PUT
request to /accounts/update/ to do the following
204 (No Content)
success message. (I have it set to return @account, which would be nice, but no big deal. 204 is fine here.)422 (Unprocessable Entity)
error message, along with the xml/json to represent the error.404 (Not Found)
error message, along with the xml/json to represent the error.What actually happens is:
Why is it that it ignores both my status and my body? I've had a similar setup for GET
requests that work out just fine (correct status, correct body).
Example CURL
request (for an ID that does not exist):
PUT
request
curl -i --header "Accept: application/xml" --header "Content-type: application/json" -X PUT -d '{"name": "whoop"}' http://localhost:3000/api/accounts/3d2cc5d0653911e2aaadc82a14fffee9 HTTP/1.1 204 No Content Location: http://localhost:3000/api/accounts X-Ua-Compatible: IE=Edge Cache-Control: no-cache X-Request-Id: bf0a02f452fbace65576aab6d2bd7c1e X-Runtime: 0.029193 Server: WEBrick/1.3.1 (Ruby/1.9.3/2013-01-15) Date: Thu, 24 Jan 2013 08:01:31 GMT Connection: close Set-Cookie: _bankshare_session=BAh7BkkiD3Nlc3Npb25faWQGOgZFRkkiJWFmNmI2MmU0MzViMmE3N2YzMDIzOTdjMDJmZDhiMzEwBjsAVA%3D%3D--133e394eb760a7fce07f1fd51349dc46c2d51626; path=/; HttpOnly
GET
request
curl -i --header "Accept: application/json" --header "Content-type: application/json" -X GET http://localhost:3000/api/accounts/3d2cc5d0653911e2aaadc82a14fffee9 HTTP/1.1 404 Not Found Content-Type: application/json; charset=utf-8 X-Ua-Compatible: IE=Edge Cache-Control: no-cache X-Request-Id: 9cc0d1cdfb27bb86a206cbc38cd75473 X-Runtime: 0.005118 Server: WEBrick/1.3.1 (Ruby/1.9.3/2013-01-15) Date: Thu, 24 Jan 2013 08:19:45 GMT Content-Length: 116 Connection: Keep-Alive {"friendly-status":"not-found","status":404,"message":"No account with id '3d2cc5d0653911e2aaadc82a14fffee9' found"}
According to this discussion this rather non intuitive behavior is due to the desire to maintain compatibility with the scaffold.
In general we keep the responder the same implementation as the scaffold. This allows us to say: replace respond_to by respond_with and everything will work exactly the same.
-- josevalim
You have two choices to override the default behavior.
unless @account.nil?
if @account.update_attributes params[:account]
respond_with @account do |format|
format.json { render json: @account.to_json, status: :ok }
format.xml { render xml: @account.to_xml, status: :ok }
end
else
respond_with error_hash do |format|
format.json { render json: error_hash.to_json(root: :error), status: :unprocessable_entity }
format.xml { render xml: error_hash.to_xml(root: :error), status: :unprocessable_entity }
end
end
else
respond_with error_hash do |format|
format.json { render json: error_hash.to_json(root: :error), status: :not_found }
format.xml { render xml: error_hash.to_xml(root: :error), status: :not_found }
end
end
It's unfortunate that we have to return to duplication for each format, but that seems to be the current recommendation up to Rails 4.0 so far; see here.
You should return 200 - OK, not 204 - No Content if you are returning the updated object, or don't return anything and have your client code 'GET' the updated object. :location is not meaningful in an api context, it's for redirecting an html response.
respond_with @account, status: :ok, responder: MyResponder
I've not done this myself so I can't give an example, but it seems like overkill here anyway.
Check out Railscasts Episode:224 for some discussion of respond_with including custom responders.
Did you see ActionController::Responder class ? Here are some methods to think about
# All other formats follow the procedure below. First we try to render a
# template, if the template is not available, we verify if the resource
# responds to :to_format and display it.
#
def to_format
if get? || !has_errors? || response_overridden?
default_render
else
display_errors
end
rescue ActionView::MissingTemplate => e
api_behavior(e)
end
and
def api_behavior(error)
raise error unless resourceful?
if get?
display resource
elsif post?
display resource, :status => :created, :location => api_location
else
head :no_content
end
end
As you can see api_behavior works for post and get methods but not for put . If an existing resource is modified, either the 200 (OK) or 204 (No Content) response codes SHOULD be sent to indicate successful completion of the request.
head :no_content is what you get.
So reason of this is that rails doesn't understand what are you trying to do. Rails thinks there is no error when you use respond_with in this case.(it's not a bug you just shouldn't use it that way)
I think respond_to
is what you need.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With