Как высушить контроллеры 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

Есть несколько способов, которые я рассматривал для решения этой проблемы, но я не могу понять, как заставить их работать:

  • Переопределите 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'), чтобы решить проблему. Или, возможно, я мог бы включить этот код в ответчик приложения, используя гем ответчиков платформы, чтобы respond_with(@users) самого по себе было достаточно. .

person evanrmurphy    schedule 17.12.2010    source источник


Ответы (5)


Обратите внимание, что технически неправильно отображать 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.

person Ruben Verborgh    schedule 10.03.2011

Есть жемчужина, которая может сделать это с: rack-jsonp-middleware.

Инструкций по установке на сайте довольно мало, но я создал небольшой проект Rails, в котором он используется — вы можете взглянуть на коммиты и посмотреть, что я сделал для запуска промежуточного программного обеспечения.

https://github.com/rwilcox/rack_jsonp_example

person RyanWilcox    schedule 20.04.2011
comment
Это потрясающий драгоценный камень, практически не требующий интеграции. Очень чистое и элегантное решение. Не могу отблагодарить вас достаточно. - person plainjimbo; 02.05.2012

Это немного «низкотехнологично» по сравнению с решением для ответчика, но как насчет того, чтобы просто создать частный метод в вашем 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
person johnmcaliley    schedule 17.12.2010

Спасибо samuelkadolph за помощь в 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], чтобы заставить его работать.

person evanrmurphy    schedule 17.12.2010

Вы также можете проверить этот ответ. в основном вы можете создать response_to «по умолчанию» для своего контроллера, чтобы вы могли просто сделать все свои действия по умолчанию ответными на json.

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

person corroded    schedule 20.04.2011