ruby-on-rails-3 api versioning respond-with

ruby on rails 3 - rails 3 formato de respuesta y versiones utilizando el tipo MIME del proveedor en el encabezado Accept



ruby-on-rails-3 api (2)

No he visto este truco de tipo de contenido utilizado en ningún otro lugar en un proyecto de Rails antes, así que esto es nuevo para mí. La forma en que normalmente lo he visto hecho es definir un espacio de nombres de ruta (por ejemplo, / api / v1 /) que va a un controlador (por ejemplo, Api :: Version1Controller).

Además, sé que quieres hacer las cosas al estilo de "Rails", y tal vez esto suene mal por parte de un tipo que ha estado con Rails desde 1.3, pero todo el asunto de respond_with / respond_to es bastante mágico para mí. No sabía que respond_to busca un método to_XXX cuando serializa objetos, por ejemplo (tal vez necesito leer sobre eso). Tener que parchear a mono Array así parece bastante tonto. Además, para una API, formatear los datos del modelo es realmente el trabajo de la vista, no el modelo. En este caso, podría buscar algo como rabl . Hay una buena reseña al respecto here .

Preámbulo:

Investigué cómo versionar una API y encontré varias maneras de hacerlo. Decidí probar la sugerencia de peter williams y crear nuevos tipos de mime de proveedor para especificar la versión y el formato. No pude encontrar ningún escrito definitivo para hacer esto siguiendo "el camino de los rieles", así que reuní información de varios lugares. Pude hacer que funcionara, pero hay algunos errores en la forma en que los procesadores manejan la matriz de widgets frente a la instancia de widgets en respond_with .

Pasos básicos y problema:

Registré tipos mime y agregué renderizadores para la versión 1 en xml y json a ApplicationController, los renderizadores llaman a los métodos to_myproj_v1_xml y to_myproj_v1_json en el modelo. respond_with(@widget) funciona bien pero respond_with(@widgets) arroja un HTTP/1.1 500 Internal Server Error diciendo que falta la "Plantilla".

Solución:

"Plantilla faltante" significa que no se invocó ningún renderizado y que no existe una plantilla coincidente. por accidente, descubrí que está buscando un método de clase ... así que se me ocurrió el siguiente código que funciona, pero no estoy muy contento con él. La tontería está principalmente relacionada con xml = obj.to_myproj_v1_xml(obj) y la duplicación en el modelo.

Mi pregunta es: ¿alguien ha hecho algo similar de una manera ligeramente más limpia?

- = código actualizado = -

config / initializers / 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

app / controllers / 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

app / models / 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

app / controllers / 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

config / initializers / 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

ACTUALIZAR:

Encontré otra solución que se siente mejor pero aún un poco extraña (todavía no estoy del todo cómoda con parches de mono), probablemente bien ... básicamente se movió construyendo los datos de respuesta del método de clase to_myproj_v1_json a un parche de mono en Array. De esta forma, cuando hay una Matriz de widgets, llama al método de instancia as_myproj_v1_json en cada widget y devuelve toda la matriz como el formato deseado.

Una nota:

  • as_json no tiene nada que ver con el formato json, solo crea un hash. Agregue formato personalizado a as_myproj_v1_json (o an_json anule si no está usando tipos de mime personalizados), entonces to_json cambiará un hash a una cadena json.

He actualizado el código a continuación para que sea el que se usa actualmente, por lo que la pregunta original puede no tener sentido. si alguien quiere que se muestren la pregunta y el código originales tal como estaban y un código fijo en una respuesta, puedo hacerlo en su lugar.


Para la respuesta: vea la pregunta :-)

En resumen, hay diferentes soluciones, una de las cuales se encuentra en la pregunta anterior:

  • Monkey-patch Array para implementar un método que devolverá el (anterior) v1 JSON