Как высушить контроллеры 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, который использует его - вы можете взглянуть на коммиты и посмотреть, что я сделал, чтобы запустить промежуточное программное обеспечение.

https://github.com/rwilcox/rack_jsonp_example

По сравнению с решением для репондеров это немного "низко-технологично", но как насчет создания частного метода в вашем 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.

это было то, что вы спрашивали?

Другие вопросы по тегам