25

前言:

我研究了如何对 API 进行版本控制,并找到了几种方法。我决定尝试peter williams 的建议并创建新的供应商 mime 类型来指定版本和格式。我找不到任何关于按照“铁轨方式”执行此操作的明确说明,因此我将多个地方的信息拼凑在一起。我能够让它工作,但是渲染器处理 Widget 数组与respond_with.

基本步骤和问题:

我注册了 mime 类型并将 xml 和 json 中版本 1 的渲染器添加到 ApplicationController、渲染器调用to_myproj_v1_xmlto_myproj_v1_json模型中的方法。 respond_with(@widget)工作正常,但respond_with(@widgets)抛出HTTP/1.1 500 Internal Server Error“模板丢失”的说法。

解决方法:

“模板丢失”意味着没有调用渲染并且不存在匹配的模板。偶然地,我发现它正在寻找一个类方法......所以我想出了下面的代码,但我对它并不满意。愚蠢主要存在于模型中并与xml = obj.to_myproj_v1_xml(obj)模型中的重复有关。

我的问题是 - 有没有人以稍微干净的方式做过类似的事情?

-= 更新代码 =-

配置/初始化程序/mime_types.rb

Mime::Type.register 'application/vnd.com.mydomain.myproj-v1+xml', :myproj_v1_xml
Mime::Type.register 'application/vnd.com.mydomain.myproj-v1+json', :myproj_v1_json

应用程序/控制器/application_controller.rb

class ApplicationController < ActionController::Base
  protect_from_forgery
  before_filter :authenticate

  ActionController.add_renderer :myproj_v1_xml do |obj, options|
    xml = obj.to_myproj_v1_xml
    self.content_type ||= Mime::Type.lookup('application/vnd.com.mydomain.myproj-v1+xml')
    self.response_body = xml
  end

  ActionController.add_renderer :myproj_v1_json do |obj, options|
    json = obj.to_myproj_v1_json
    self.content_type ||= Mime::Type.lookup('application/vnd.com.mydomain.myproj-v1+json')
    self.response_body  = json
  end
end

应用程序/模型/widget.rb

class Widget < ActiveRecord::Base
  belongs_to :user
  V1_FIELDS = [:version, :model, :description, :name, :id]

  def to_myproj_v1_xml
    self.to_xml(:only => V1_FIELDS)
  end

  def to_myproj_v1_json
    self.to_json(:only => V1_FIELDS)
  end

  def as_myproj_v1_json
    self.as_json(:only => V1_FIELDS)
  end
end

应用程序/控制器/widgets_controller.rb

class WidgetsController < ApplicationController

  respond_to :myproj_v1_xml, :myproj_v1_json

  def index
    @widgets = @user.widgets
    respond_with(@widgets)
  end

  def create
    @widget = @user.widgets.create(params[:widget])
    respond_with(@widget)
  end

  def destroy
    @widget = @user.widgets.find(params[:id])
    respond_with(@widget.destroy)
  end

  def show
    respond_with(@widget = @user.widgets.find(params[:id]))
  end

...

end

配置/初始化程序/monkey_array.rb

class Array

  def to_myproj_v1_json(options = {})
    a = []
    self.each { |obj| a.push obj.as_myproj_v1_json }
    a.to_json()
  end

  def to_myproj_v1_xml(options = {})
    a = []
    self.each { |obj| a.push obj.as_myproj_v1_json } # yes this is json instead of xml.  as_json returns a hash
    a.to_xml()
  end

end

更新:

找到了另一个感觉更好但仍然有点奇怪的解决方案(我仍然对猴子补丁并不完全满意),虽然可能还可以......基本上将构建响应数据从类方法to_myproj_v1_json移动到 Array 上的猴子补丁。这样,当有一个 Widget 数组时,它会调用as_myproj_v1_json每个 Widget 上的实例方法并以所需格式返回整个 Array。

一注:

  • as_json 与 json 格式无关,只是创建一个哈希。将自定义格式添加到 as_myproj_v1_json (如果您不使用自定义 mime 类型,则添加 as_json 覆盖),然后 to_json 会将散列更改为 json 字符串。

我已将下面的代码更新为当前使用的代码,因此最初的问题可能没有意义。如果有人想要原样显示的原始问题和代码以及响应中的固定代码,我可以这样做。

4

2 回答 2

0

答案:见问题:-)

简而言之,有不同的解决方案,其中一个在上面的问题中:

  • Monkey-patch Array 以实现一种方法,该方法将返回(旧)v1 JSON
于 2011-08-23T19:51:21.177 回答
0

我之前没有在 Rails 项目的任何地方看到过这种内容类型技巧,所以这对我来说是新的。我通常看到它完成的方式是定义一个路由命名空间(例如/api/v1/),它转到一个控制器(例如,Api::Version1Controller)。

另外,我知道你想以“Rails 方式”做事,也许这听起来很古怪,来自一个从 1.3 开始就使用 Rails 的人,但整个respond_with/respond_to东西对我来说相当神奇。例如,我不知道它在序列化对象时会respond_to寻找一种to_XXX方法(也许我需要阅读它)。不得不像这样对 Array 进行猴子补丁似乎很愚蠢。此外,对于 API,格式化模型数据实际上是视图的工作,而不是模型的工作。在这种情况下,我可能会研究类似rabl的东西。这里有一篇很好的文章。

于 2011-08-26T12:32:45.327 回答