rails中的PUT请求不会更新respond_with调用的状态

在rails中给出以下控制器:

class AccountsController  "Example for this question", :parameter => 42 } end end 

我希望对/ accounts / update /的PUT请求执行以下操作

  1. 如果id存在,并且update_attributes调用成功,则传递204 (No Content)成功消息。 (我已经将它设置为返回@account,这将是很好的,但没什么大不了的。这里的204很好。)
  2. 如果id存在但数据不正确,则提供422 (Unprocessable Entity)错误消息以及xml / json以表示错误。
  3. 如果id不存在,则提供404 (Not Found)错误消息以及xml / json以表示错误。

实际发生的是:

  1. 交付204没有身体。
  2. 交付204没有身体。
  3. 交付204没有身体。

为什么它忽略了我的身份和身体? 我有一个类似的GET请求设置,运行得很好(正确的状态,正确的正文)。

示例CURL请求(对于不存在的ID):

PUT请求

 curl -i --header“Accept:application / xml”--header“Content-type:application / json”-X PUT -d'{“name”:“whoop”}'http:// localhost:3000 / api /帐号/ 3d2cc5d0653911e2aaadc82a14fffee9
 HTTP / 1.1 204无内容 
位置:http:// localhost:3000 / api / accounts
 X-Ua兼容:IE = Edge
缓存控制:无缓存
 X-Request-Id:bf0a02f452fbace65576aab6d2bd7c1e
 X-Runtime:0.029193
服务器:WEBrick / 1.3.1(Ruby / 1.9.3 / 2013-01-15)
日期:星期四,2013年1月24日08:01:31 GMT
连接:关闭
 Set-Cookie:_bankshare_session = BAh7BkkiD3Nlc3Npb25faWQGOgZFRkkiJWFmNmI2MmU0MzViMmE3N2YzMDIzOTdjMDJmZDhiMzEwBjsAVA%3D%3D  -  133e394eb760a7fce07f1fd51349dc46c2d51626; 路径= /; 仅Http

GET请求

 curl -i --header“Accept:application / json”--header“Content-type:application / json”-X GET http:// localhost:3000 / api / accounts / 3d2cc5d0653911e2aaadc82a14fffee9
找不到HTTP / 1.1 404 
 Content-Type:application / json; 字符集= utf-8的
 X-Ua兼容:IE = Edge
缓存控制:无缓存
 X-Request-Id:9cc0d1cdfb27bb86a206cbc38cd75473
 X-Runtime:0.005118
服务器:WEBrick / 1.3.1(Ruby / 1.9.3 / 2013-01-15)
日期:星期四,2013年1月24日08:19:45 GMT
内容长度:116
连接:保持活力

 {“friendly-status”:“not-found”,“status”:404,“message”:“找不到ID为'3d2cc5d0653911e2aaadc82a14fffee9'的帐户”}

根据该讨论,这种相当不直观的行为是由于希望保持与支架的相容性。

通常,我们将响应者保持与脚手架相同的实现。 这允许我们说:用respond_with替换respond_to,一切都将完全相同。

约瑟瓦利姆

您有两种选择来覆盖默认行为。

A)传递一个块来响应

 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 

遗憾的是,我们必须为每种格式返回复制,但这似乎是目前针对Rails 4.0的建议; 看到这里 。

如果要返回更新的对象,或者不返回任何内容并让客户端代码“获取”更新的对象,则应返回200 – OK ,而不是204 – No Content 。 :位置在api上下文中没有意义,它用于重定向html响应。

B)创建自定义响应者

 respond_with @account, status: :ok, responder: MyResponder 

我自己也没有这样做,所以我不能举一个例子,但无论如何这似乎有些过分。

查看Railscasts Episode:224 ,了解respond_with的一些讨论,包括自定义响应者。

你看到ActionController :: Responder类了吗? 以下是一些需要考虑的方法

  # 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 

 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 

正如您所看到的,api_behavior适用于post和get方法,但不适用于put。 如果修改了现有资源,则应该发送200(OK)或204(No Content)响应代码以指示请求成功完成。

head:no_content就是你得到的。

这样做的原因是rails不明白你想做什么。 Rails认为在这种情况下使用respond_with时没有错误。(这不是一个错误,你不应该这样使用它)

我认为respond_to就是你所需要的。

恕我直言,我会先试试这个。 第一个.first! 如果找不到记录,将使rails发出404。 如果成功将呈现204.如果在保存时出错,则将从模型错误对象中提取错误。

 class AccountsController < ApplicationController respond_to :json, :xml def update @account = Account.where(uuid: params[:id]).first! @account.update_attributes params[:account] respond_with @account, location: account_url(@account) end end 

如果模型validation消息不够,则需要有条件地发出结果。 成功路径将如上所述,如果失败,您将呈现您需要的内容。

 class AccountsController < ApplicationController respond_to :json, :xml def update @account = Account.where(uuid: params[:id]).first! if @account.update_attributes params[:account] respond_with @account, location: account_url(@account) else respond_to error_hash do |format| format.json { render json: error_hash, status: :unprocessable_entity } format.xml { render xml: error_hash, status: :unprocessable_entity } end end end def error_hash { :example => "Example for this question", :parameter => 42 } end end