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

时间:2013-01-24 08:21:01

标签: ruby-on-rails json

在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

我希望{/ 1}}请求/ accounts / update /执行以下操作

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

    1. 交付204没有身体。
    2. 交付204没有身体。
    3. 交付204没有身体。
    4. 为什么它忽略了我的身份和身体?我对404 (Not Found)请求进行了类似的设置,结果很好(正确的状态,正确的正文)。

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

      CURL请求

      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
      

      PUT请求

      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"}
      

3 个答案:

答案 0 :(得分:4)

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

  

一般情况下,我们会让响应者保持与实现相同的实现   脚手架。这允许我们说:用respond_with替换respond_to   一切都会完全一样。

     

- josevalim

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

A)将一个块传递给respond_with

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的当前建议;见here

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

B)创建自定义响应者

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

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

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

答案 1 :(得分:3)

您是否看到了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就是您所需要的。

答案 2 :(得分:0)

恕我直言,我会先试试这个。 如果找不到记录,.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

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

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