【问题标题】:Rails respond_with -- why does POST return a URL instead of the data?Rails respond_with -- 为什么 POST 返回 URL 而不是数据?
【发布时间】:2012-06-19 20:25:34
【问题描述】:

这是一个“为什么它会这样工作”的问题,而不是“我如何才能做到这一点”。

我的应用正在调用返回 JSON 的第三方 REST API,并将结果作为我的自己的 JSON API 的一部分返回。

我使用的是 Rails 3 respond_torespond_with 方法;在 GET 请求的情况下,这可以按我的预期工作,只是通过 JSON。

POST 的情况下,它做的更多,包括从返回的对象创建一个URL 以传递:location 选项。但由于我的对象只是 JSON(不是 ActiveRecord),我得到一个错误。

例如...

# POST /api/products.json with params id=:id
def create
  query_string = "#{user_id}&id=#{params[:id]}"
  @products = third_party_api_wrapper.products(query_string, 'POST')
  respond_with @products
end 

我的第 3 方 API 包装器发出一个 POST 请求,该请求返回正常,然后 Rails 返回一个 500 错误,记录如下:

NoMethodError (undefined method `{"response":{"message":"product 4e1712d9ec0f257c510013f8 selected"}}_url' for #<MyController> 

Rails 希望我的 @products 对象知道如何创建位置 URL。

澄清:第三方 API 返回的 @products 对象是纯 JSON —— 一个字符串,您可以在上面的错误日志消息中看到嵌入的字符串。发生此错误是因为 Rails 似乎希望该对象更多——在 Rails 内部 API 支持中,它是一个 ActiveRecord 对象。

如果我用旧样式的 sytax 替换新的 respond_with

respond_to do |format|
  format.json { render :json => @products }  # note, no :location or :status options
end

然后一切正常。这就是我所做的,所以我没有“如何”的问题,而是有一个“为什么”的问题。

简介中的Ryan Daigle's post 解释说正在发生的事情是意料之中的。

我的问题是:为什么respond_with 期望除数据(和 HTTP 状态?)以外的任何东西,而且显然只针对POST

我并不是说这是错误的,只是试图理解 Rails 实现的基本原理。

【问题讨论】:

  • 我不确定你在这里问/说什么。你能澄清一下吗?你是... (1) 问为什么respond_with 不适合你? (2) 说您使用的第 3 方 API 不返回“仅”数据和状态码? (3) 问“为什么 API 应该返回数据以外的任何东西?”
  • 如果您的主要问题是“为什么 API 应该返回数据以外的任何内容(以及 HTTP 状态?)。我并不是说这是错误的,只是想了解其基本原理。”也许它会帮助我们提供有关 API 的更多细节。
  • 请输出@products并告诉我们结果。

标签: ruby-on-rails-3 json rest respond-to


【解决方案1】:

总结:Rails 的基本原理来自 HTTP 和 REST。

(感谢您更新的问题。现在我理解了您的核心问题:“我并不是说这是错误的,只是想了解 Rails 实施的基本原理。”)

现在解释一下。 Rails 行为方式的基本原理在于接受 HTTP 和 REST 约定。

为了从您所读到的内容与我将要详细说明的内容进行衔接,我想提一下Ryan Daigle's article on Default RESTful Rendering的相关部分:

如果请求的是 :html 格式:

[删除了一些文字]

  • [在 PUT 或 POST 之后且没有验证错误] 重定向到资源位置(即 user_url)

([括号中的文字]是我添加的。)

如果请求了其他格式,(即 :xml 或 :json)

[删除了一些文字]

  • 如果是 POST 请求,请在资源上调用 :to_format 方法并将其与 :created 状态和新创建资源的 :location 一起发送回"

让我用我的话说,Rails 认为什么是好的做法:

  • 对于人类内容(例如 HTML),在 POST 或 PUT 之后,服务器应该告诉浏览器通过 303 重定向到新创建的资源。这是一种常见的做法——非常有用,因为用户希望看到他们的编辑产生的更新。

  • 对于机器内容(例如 JSON、XML),在 PUT 之后,服务器应该只呈现一个 201。客户端,在这种情况下,是一个使用 API 的程序,可能决定停在那里。 (毕竟,客户端指定了请求并得到了 201,所以一切都是 honky dory。)这就是为什么使用 201(成功)而不是 303(重定向)的原因。如果客户端想要请求新创建的资源,它可以使用 Location 标头查找它——但不应强制重定向。

无论哪种情况,请注意新创建资源的位置是必需的。这就是为什么上面示例中的@products 需要同时包含数据和位置。

作为背景,我从W3C Page on 201 Created分享了一点:

10.2.2 201 创建

请求已完成并导致创建新资源。新创建的资源可以被响应实体中返回的 URI 引用,资源的最具体的 URI 由 Location 头字段给出。响应应该包括一个实体,其中包含资源特征和位置列表,用户或用户代理可以从中选择最合适的一个。实体格式由 Content-Type 标头字段中给出的媒体类型指定。源服务器必须在返回 201 状态码之前创建资源。如果无法立即执行该操作,则服务器应该以 202 (Accepted) 响应来响应。

我希望这有助于解释基本原理。我的(天真的?)理解是这个基本原理在 Web 框架中被广泛接受。从历史上看,我怀疑 Rails 是许多狂热的 REST 和面向资源的架构支持者的狂热实施基础(新词警告!)。

【讨论】:

  • 啊,现在我明白了其中的原理了。是的,这是有道理的,一旦你创建了一些东西,你就会想要引用它。我不确定,但我认为respond_with 实现假定对象的to_s 方法将返回一个与Rails-route 兼容的名称(我将检查一个Rails 对象并在我得到一分钟时找出)这样它可以对位置做一些事情。当您处于以 Rails 为中心的世界中时,这可以正常工作,但在我想要从 API 获得的只是数据并且正在以任何 API 无法合理预测的方式处理导航的情况下就不行了。感谢您的帮助!
  • 对于人类内容,重定向还会强制浏览器执行 GET 请求以显示结果,这将阻止用户在刷新浏览器时重新发布他的数据。
【解决方案2】:

@david-james 很好地回答了“为什么”。这只是通过respond_with 回答的简短“方法”:

class Api::V1::UsersController < ApplicationController

  respond_to :json

  def create
    @user = User.create(...)
    respond_with @user, location: url_for([:api, :v1, @user])
  end

end

【讨论】:

    【解决方案3】:

    回答这个问题:“为什么 API 应该返回数据以外的任何内容(以及 HTTP 状态?)。我并不是说这是错误的,只是想了解其中的原理。”

    我想不出什么好的理由。更重要的是,我看不到 API 可以返回任何除了数据结构的任何方式! (这个问题对我来说没有意义!)

    根据定义,API 调用必须返回一个数据结构。 (它可能像字符串一样简单。它可能是 JSON。它可能是 XML。)它可以使用内容协商来决定格式。它可能是也可能不是严格的模式,但至少客户端库必须能够解析它。在任何情况下,API 文档都应该清楚地说明这一点并坚持下去。客户端库还能如何互操作?

    我想我在这里忽略了重点,这似乎太明显了。 (我怀疑您在上面的代码中遇到了另一个问题。)

    【讨论】:

    • 感谢您的所有回复。我试图澄清这个问题,以明确我理解调用respond_with 时发生的情况。我有一个解决方案。 Rails 应该以这种方式工作对我来说没有意义,所以我想了解更多。
    • 感谢您的澄清。我现在要保持这个答案不变。我单独回答了你澄清的问题。
    • @DavidJames,您说:“根据定义,API 调用必须返回数据结构”。不是这样。您在回答 201 响应时引用的 RFC 规范说“应该”返回,而不是“必须返回,一个实体。在某些用例中,没有正文的 201 是可以接受的(尽管有些人会争辩使用 204 响应所以它是明确的,没有正文)。我的观点是 API 调用并不总是必须返回数据结构。有趣的讨论在这里:codeschool.com/discuss/t/…
    猜你喜欢
    • 2015-01-03
    • 2011-04-07
    • 2019-05-31
    • 2019-06-15
    • 1970-01-01
    • 2022-11-02
    • 2013-07-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多