rails 3响应格式和使用Accept标头中的供应商MIME类型进行版本控制

时间:2011-01-20 23:50:01

标签: ruby-on-rails-3 api versioning respond-with

序言:

我研究了如何对API进行版本控制,并找到了几种方法。我决定尝试peter williams'建议并创建新的供应商mime类型来指定版本和格式。我无法在“导轨方式”之后找到这样做的明确说明,所以我拼凑了几个地方的信息。我能够让它工作,但渲染器处理respond_with中的Widget数组与Widget实例的方式有些愚蠢。

基本步骤&问题:

我在xml和json中为ApplicationController注册了mime类型并为版本1添加了渲染器,渲染器在模型中调用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中的响应数据构建到a Array上的猴子补丁。这样,当存在一个Widgets数组时,它会在每个Widget上调用实例方法as_myproj_v1_json,并以所需的格式返回整个数组。

一个注意事项:

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

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

2 个答案:

答案 0 :(得分:0)

答案:见问题: - )

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

  • Monkey-patch Array实现一个将(旧的)v1 JSON返回的方法

答案 1 :(得分:0)

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

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