środa, 23 marca 2011

Rails3: respond_with i pusty json/xml/yaml przy update

W Rails3 możemy zamiast (bardzo "nie-DRY") bloku respond_to:

class ArticlesController < ApplicationController
# GET /articles.json
# GET /articles.xml
def index
@articles = Article.all
respond_to do |format|
format.json { render json: @articles }
format.xml { render xml: @articles }
end
end
end

możemy ustawić raz w jaki sposób chcemy odpowiadać na request:

class ArticlesController < ApplicationController
respond_to :json, :xml
# GET /articles.json
# GET /articlel.xml
def index
respond_with @articles = Article.all
end
end
dużo ładniej prawda? :)

Jest jednak jeden problem - przy akcji update (czy każdej innej do której dostajemy się poprzez PUT) Rails zwraca status OK (200) zamiast zmienionego obiektu. Może czasem to dobrze, możliwe, że to nawet bardziej zgodne ze specyfikacją REST, tego nie wiem. Wiem tyle, że potrzebowałem aby update zwracało obiekt. Można zrobić to na 3 sposoby - dobry, okropny, jeszcze gorszy :) Zacznijmy od końca. Możemy w każdej akcji używać:

# GET /articles.json
# GET /articles.xml
def index
@articles = Article.all
render json: @articles
end
view raw index.rb hosted with ❤ by GitHub

Skazujemy się jednak na jeden format zwracanych danych. Możemy wrócić znowu do bloku respond_to (to jest opcja nr.2 - okropna), lub nadpisać responder.

Tworzymy plik application_responder.rb w katalogu lib/

class ApplicationResponder < ActionController::Responder
def api_behavior(error)
raise error unless resourceful?
if get?
display resource
elsif has_errors?
display resource.errors, :status => :unprocessable_entity
elsif post?
display resource, :status => :created, :location => api_location
elsif put?
display resource, status: :ok, location: api_location
elsif has_empty_resource_definition?
display empty_resource, :status => :ok
else
display head(:ok)
end
end
end

Następnie zmieniamy ApplicationController (app/controllers/application_controller.rb):

require 'application_responder'
class ApplicationController < ActionController::Base
protect_from_forgery
self.responder = ApplicationResponder
end

Dzięki temu możemy w akcji update używać respond_with:

# PUT /articles/1.json
# PUT /articles/1.xml
def update
article = Article.find(params[:id])
article.update_attributes(params[:article])
respond_with article
end

1 komentarz:

  1. Fajne rozwiązanie. Też miałem dziś ten sam problem.

    Chętnie widziałbym ten blok "elsif put?" w ActionControllerze, albo przynajmniej wysłuchał opinii Rails core teamu dlaczego dla update jest tylko "head :ok".

    OdpowiedzUsuń