Как высушить контроллеры Rails 3 путем переопределения таких методов, как response_with?
Я пытаюсь создать JSONP API для моего приложения на Rails 3. Прямо сейчас в моих контроллерах у меня есть много действий, которые следуют этой схеме:
# This is from my users_controller.rb, as an example
def index
@users = User.all
respond_with(@users, :callback => params[:callback])
end
Хотя это работает как есть, я хотел бы высушить его, не повторяя :callback => params[:callback]
в призыве каждого действия к respond_with
, Как я могу это сделать?
Обновление: одна вещь, которую я понял, что уродливо в моем коде выше, это то, что :callback => params[:callback]
Опция будет передана для любого формата ответа, а не только для JSON. Следующий код, вероятно, более правильный:
def index
@users = User.all
respond_with(@users) do |format|
format.json { render :json => @users, :callback => params[:callback]}
end
end
Есть несколько способов решения этой проблемы, но я не могу понять, как заставить их работать:
- Override
render
(возможно, в контроллере приложения), так что он принимает:jsonp
опция, которая автоматически включает:callback => params[:callback]
параметр. Таким образом, я мог бы изменить приведенный выше код на следующий, что несколько короче:
def index
@users = User.all
respond_with(@users) do |format|
format.json { render :jsonp => @users}
end
end
- Создать респондент, который переопределяет
to_json
чтобы решить мою проблему. Таким образом, я мог оставить блок и просто позвонитьrespond_with(@users, :responder => 'MyResponder')
решить вопрос. Или, возможно, я мог бы включить этот код в респондент приложения, используя gem респондентов plataformatec, чтобыrespond_with(@users)
само по себе было бы достаточно.
5 ответов
Спасибо Самуэлькадольфу за помощь, оказанную мне сегодня на IRC-канале #rubyonrails. Он представил решение в этой сути, скопировано ниже для удобства:
def custom_respond_with(*resources, &block)
options = resources.extract_options!
if options[:callback]
old_block = block
block = lambda do |format|
old_block.call(format) if block_given?
format.json { render :json => [] }
end
end
respond_with(*(resources << options), &block)
end
Я еще не пробовал это в своем приложении, но я вижу, что это должно работать. Он также подтвердил, что я могу аналогичным образом переопределить respond_with
сам метод, просто изменив имя этого метода и изменив последнюю строку определения на super(*(resources << options), &block)
,
Я думаю, что это будет работать для меня. Тем не менее, мне все еще интересно знать, как написать собственный респондент для выполнения этой работы. (Это было бы более элегантное решение, ИМХО.)
Обновление: я попробовал это в моем приложении, и оно работает с некоторыми незначительными изменениями. Вот версия, которую я использую сейчас в private
раздел моего ApplicationController, предназначенный для автоматического предоставления :callback => params[:callback]
опция для запросов JSON:
def custom_respond_with(*resources, &block)
options = resources.extract_options!
if params[:callback]
old_block = block
block = lambda do |format|
old_block.call(format) if block_given?
format.json { render :json => resources, :callback => params[:callback] }
end
end
respond_with(*(resources << options), &block)
end
Обратите внимание, что я должен был изменить if options[:callback]
в if params[:callback]
чтобы заставить его работать.
Обратите внимание, что технически некорректно отображать JSON с параметром обратного вызова, поскольку вы получаете ответ JavaScript (вызов функции для обратного вызова JSON-P), а не результат JSON. Так что если у вас есть
render :json => my_object, :callback => params[:callback]
и запрос на /users?callback=func
приходит, Rails ответит
func({…})
с типом контента application/json
, что неверно, поскольку приведенный выше ответ явно не JSON, а JavaScript.
Я использую решение
def respond_with_json(item)
respond_with do |format|
format.json { render :json => item }
format.js { render :json => item, :callback => params[:callback] }
end
end
который отвечает правильно с или без обратного вызова. Применяя это к вышеупомянутому решению, мы получаем:
def custom_respond_with(*resources, &block)
options = resources.extract_options!
if params[:callback]
old_block = block
block = lambda do |format|
old_block.call(format) if block_given?
format.js { render :json => resources[0], :callback => params[:callback] }
end
end
respond_with(*(resources << options), &block)
end
Также обратите внимание на исправление resources[0]
в противном случае вы в конечном итоге упаковка resources
в дополнительном массиве в результате оператора splat.
Это драгоценный камень, который может сделать это для: rack-jsonp-middleware.
Инструкции по установке довольно скудны на сайте, но я создал небольшой проект Rails, который использует его - вы можете взглянуть на коммиты и посмотреть, что я сделал, чтобы запустить промежуточное программное обеспечение.
По сравнению с решением для репондеров это немного "низко-технологично", но как насчет создания частного метода в вашем appliation_controller.rb для этого? Для него будет доступна переменная params, и вы можете передать ей объект @users.
#application_controller.rb
private
def jsonp(my_object)
render :json => my_object, :callback => params[:callback]
end
#controller
def index
@users = User.all
respond_with(@users) do |format|
format.json { jsonp(@users)}
end
end
Вы также можете проверить этот ответ. в основном вы можете создать "по умолчанию" response_to для вашего контроллера, так что вы можете просто сделать все ваши действия по умолчанию отвечающими на json.
это было то, что вы спрашивали?