【问题标题】:How to generate custom response for REST API with Ruby on Rails?如何使用 Ruby on Rails 为 REST API 生成自定义响应?
【发布时间】:2011-04-21 19:15:48
【问题描述】:

我正在 Rails 3 中实现一个 REST API。我们允许 JSON 和 XML 作为响应格式。

默认respond_with 可以正常工作,只要您只想返回请求的资源,例如:

def show
  respond_with User.find(params[:id])
end

GET /users/30.xml

<?xml version="1.0" encoding="UTF-8"?>
<user>
  <birthday type="date">2010-01-01</birthday>
  <company-name>Company</company-name>
  <email>email@test.com</email>
  <id type="integer">30</id>
</user>

但是,我希望得到以下标准化回复:

<?xml version="1.0" encoding="UTF-8"?>
<response>
  <status>
    <success type="boolean">true</success>
  </status>
  <result>
    <user>
      <birthday type="date">2010-01-01</birthday>
      <company-name>Company</company-name>
      <email>email@test.com</email>
      <id type="integer">30</id>
    </user>
  </result>
</response>

我怎样才能达到这个结果?

我尝试了以下方法,使用自定义响应类

class Response

  STATUS_CODES = {
    :success => 0,
  }

  extend ActiveModel::Naming
  include ActiveModel::Serializers::Xml
  include ActiveModel::Serializers::JSON

  attr_accessor :status
  attr_accessor :result

  def initialize(result = nil, status_code = :success)
    @status = {
      :success => (status_code == :success),
    }
    @result = result
  end

  def attributes
    @attributes ||= { 'status' => nil, 'result' => nil }
  end

end

并在我的ApplicationController 中重新定义respond_with 方法:

  def respond_with_with_api_responder(*resources, &block)
    respond_with_without_api_responder(Response.new(resources), &block)
  end

  alias_method_chain :respond_with, :api_responder

但是,这并没有产生预期的结果:

<?xml version="1.0" encoding="UTF-8"?>
<response>
  <status>
    <success type="boolean">true</success>
  </status>
  <result type="array">
    <result>
      <birthday type="date">2010-01-01</birthday>
      <company-name>Company</company-name>
      <email>email@test.com</email>
      <id type="integer">30</id>
    </result>
  </result>
</response>

应该是&lt;user&gt; 现在又是&lt;result&gt;。当我返回一个数组作为结果时,情况会变得更糟,然后我会得到另一个&lt;result&gt; 层。如果我查看 JSON 响应,它看起来几乎没有问题——但请注意,有一个数组 [] 过多地包装了用户资源。

GET /users/30.json

{"response":{"result":[{"user":{"birthday":"2010-01-01","company_name":"Company","email":"email@test.com"}}],"status":{"success":true}}}

有什么线索吗?如何获得所需的响应格式?我也尝试过编写一个自定义的Responder 类,但归结为在ActionController:Responder 中重写display 方法,这给了我完全相同的问题:

  def display(resource, given_options={})
    controller.render given_options.merge!(options).merge!(format => Response.new(resource))
  end

我相信问题在某种程度上隐藏在ActiveModel 的序列化代码中,但我似乎无法弄清楚如何将资源包装在容器标签中,并且仍然实现包装的资源被正确序列化。

有什么想法或想法吗?

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-3 api rest


    【解决方案1】:

    这是我最后所做的:

    1. 我摆脱了 Response 类。

    2. 我为所有模型添加了 to_json 和 to_xml 方法:

      [:to_json, :to_xml].each do |method_name|
        define_method(method_name) do |options = {}|
          options ||= {}
          options[:only] ||= # some filtering
          super(options)
        end
      end
      
    3. 我在 ApplicationController 中重新定义了 respond_with 方法:

      def api_respond_with(resources, &block)
        default_respond_with do |format|
          format.json { render :json => resources, :skip_types => true, :status => :ok }
          format.xml { render :xml => resources, :skip_types => true, :status => :ok }
        end
      end
      
      alias_method :default_respond_with, :respond_with
      alias_method :respond_with, :api_respond_with
      
    4. 我用适当的方法编写了一个自定义中间件来添加所需的包装:

      class StandardizedResponseFilter
      
        def _call(env)
          status, headers, response = @app.call(env)
          if headers['Content-Type'].include? 'application/json'
            response.body = standardized_json_wrapping(response.body, env)
          elsif headers['Content-Type'].include? 'application/xml'
            response.body = standardized_xml_wrapping(response.body, env)
          end
          [status, headers, response]
        end
      
      end
      

    如果有人知道更好的方法,请随时发表评论。

    【讨论】:

      【解决方案2】:

      在这种情况下,我通常会覆盖 ActiveModel#to_xml 和 ActiveModel#to_json 方法。 #to_xml 上的文档描述了可能的选项。你可以让你的Request 对象继承自ActiveModel,然后用这样的模式覆盖#to_xml 方法:

      def to_xml(options = {})
        # muck with options such as :only, :except, :methods
        options[:methods] ||= []
        [:status, :result].each { |m| options[:methods] << m }
      
        super(options)
      end
      

      特别是我认为您会发现 options[:methods] 很有用,因为它允许您定义返回属性并包含在输出中的任意方法。

      【讨论】:

      • 感谢您的评论,非常感谢!事实上,添加 to_json 和 to_xml 方法是答案的一部分。此外,我还编写了一个自定义 respond_with 方法来强制执行这些格式并添加一个自定义中间件,该中间件重新格式化 JSON(或 XML)输出以添加所需的标准化包装。我还没有找到任何其他方法来摆脱标签命名的怪癖。
      • super(options) 调用只返回 XML...您可以比在您建议的答案中更容易地包装它。无需使用 Rack 中间件、内容类型检查等。
      猜你喜欢
      • 1970-01-01
      • 2020-05-15
      • 2019-09-13
      • 2012-12-27
      • 2023-03-30
      • 2011-07-05
      • 2018-11-08
      • 2020-10-28
      • 2014-07-27
      相关资源
      最近更新 更多