11

给定 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

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

  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 /accounts/3d2cc5d0653911e2aaadc82a14ffee9
HTTP/1.1 204 无内容
位置:http://localhost:3000/api/accounts
X-Ua 兼容:IE=Edge
缓存控制:无缓存
X 请求 ID:bf0a02f452fbace65576aab6d2bd7c1e
X 运行时:0.029193
服务器:WEBrick/1.3.1 (Ruby/1.9.3/2013-01-15)
日期:2013 年 1 月 24 日星期四 08:01:31 GMT
连接:关闭
设置 Cookie:_bankshare_session=BAh7BkkiD3Nlc3Npb25faWQGOgZFRkkiJWFmNmI2MmU0MzViMmE3N2YzMDIzOTdjMDJmZDhiMzEwBjsAVA%3D%3D--133e394eb760a7fce07f1fd51349dc46c2d51626;路径=/; HttpOnly

GET要求

curl -i --header "Accept: application/json" --header "Content-type: application/json" -X GET http://localhost:3000/api/accounts/3d2cc5d0653911e2aaadc82a14ffee9
HTTP/1.1 404 未找到
内容类型:应用程序/json;字符集=utf-8
X-Ua 兼容:IE=Edge
缓存控制:无缓存
X 请求 ID:9cc0d1cdfb27bb86a206cbc38cd75473
X 运行时: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 为 '3d2cc5d0653911e2aaadc82a14ffee9' 的帐户"}
4

3 回答 3

4

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

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

——约瑟瓦林

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

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 的当前建议;看这里

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

B) 创建自定义响应者

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

我自己没有这样做,所以我不能举个例子,但无论如何这里似乎有点矫枉过正。

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

于 2013-09-21T05:30:26.837 回答
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是你需要的。

于 2013-06-13T21:22:37.887 回答
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
于 2017-08-22T02:16:31.463 回答