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