модуль ActionController::MimeResponds
Публичные методы экземпляра
Без поддержки веб-сервисов действие, собирающее данные для отображения списка людей, может выглядеть так:
def index @people = Person.all end
Вот то же действие с поддержкой веб-сервисов:
def index
@people = Person.all
respond_to do |format|
format.html
format.xml { render xml: @people }
end
end
Это означает: «если клиент хочет HTML в ответ на это действие, просто ответьте, как мы делали раньше, но если клиент хочет XML, верните им список людей в формате XML». (Rails определяет желаемый формат ответа из HTTP-заголовка Accept, отправленного клиентом.)
Предположим, у вас есть действие, которое добавляет нового человека, при необходимости создавая его компанию (по имени), если она еще не существует, без веб-сервисов, оно может выглядеть так:
def create @company = Company.find_or_create_by(name: params[:company][:name]) @person = @company.people.create(params[:person]) redirect_to(person_list_url) end
Вот то же действие с поддержкой веб-сервисов:
def create
company = params[:person].delete(:company)
@company = Company.find_or_create_by(name: company[:name])
@person = @company.people.create(params[:person])
respond_to do |format|
format.html { redirect_to(person_list_url) }
format.js
format.xml { render xml: @person.to_xml(include: @company) }
end
end
Если клиент хочет HTML, мы просто перенаправляем его обратно к списку людей. Если они хотят JavaScript, то это запрос Ajax, и мы рендерим шаблон JavaScript, связанный с этим действием. Наконец, если клиент хочет XML, мы рендерим созданного человека как XML, но с хитростью: мы также включаем компанию человека в рендеренный XML, что-то вроде этого:
<person>
<id>...</id>
...
<company>
<id>...</id>
<name>...</name>
...
</company>
</person> Обратите внимание, однако, на дополнительную часть в начале этого действия:
company = params[:person].delete(:company) @company = Company.find_or_create_by(name: company[:name])
Это связано с тем, что входной XML-документ (если происходит запрос веб-сервиса) может содержать только один корневой узел. Поэтому нам нужно перегруппировать вещи, чтобы запрос выглядел так (кодированный по URL):
person[name]=...&person[company][name]=...&...
И так (кодированный в XML):
<person>
<name>...</name>
<company>
<name>...</name>
</company>
</person> Другими словами, мы формируем запрос, чтобы он оперировал человеком как единым объектом. Затем в действии мы извлекаем данные о компании из запроса, находим или создаём компанию, а затем создаём нового человека с оставшимися данными.
Обратите внимание, что вы можете определить свой собственный парсер XML-параметров, который позволит вам описывать несколько объектов в одном запросе (например, объединив их все в один корневой узел), но если вы просто следуете устоявшимся правилам и принимаете значения по умолчанию Rails, жизнь будет намного проще.
Если вам нужно использовать тип MIME, который не поддерживается по умолчанию, вы можете зарегистрировать свои обработчики в файле config/initializers/mime_types.rb следующим образом.
Mime::Type.register "image/jpg", :jpg
Respond to также позволяет вам указать общий блок для разных форматов, используя any:
def index
@people = Person.all
respond_to do |format|
format.html
format.any(:xml, :json) { render request.format.to_sym => @people }
end
end
В приведенном выше примере, если формат xml, он отобразит:
render xml: @people
Или если формат json:
render json: @people
Поскольку это распространённый шаблон, вы можете использовать метод класса #respond_to с методом #respond_with, чтобы получить те же результаты:
class PeopleController < ApplicationController
respond_to :html, :xml, :json
def index
@people = Person.all
respond_with(@people)
end
end
Форматы могут иметь разные варианты.
Вариант запроса — это специализация формата запроса, например, :tablet, :phone, или :desktop.
Мы часто хотим отображать разные шаблоны html/json/xml для телефонов, планшетов и настольных браузеров. Варианты упрощают это.
Вы можете установить вариант в before_action:
request.variant = :tablet if request.user_agent =~ /iPad/
Обрабатывайте варианты в действии так же, как вы обрабатываете форматы:
respond_to do |format|
format.html do |variant|
variant.tablet # renders app/views/projects/show.html+tablet.erb
variant.phone { extra_setup; render ... }
variant.none { special_setup } # executed only if there is no variant set
end
end Предоставьте отдельные шаблоны для каждого формата и варианта:
app/views/projects/show.html.erb app/views/projects/show.html+tablet.erb app/views/projects/show.html+phone.erb
Когда вы не используете общий код внутри формата, вы можете упростить определение вариантов, используя встроенную синтаксис:
respond_to do |format|
format.js { render "trash" }
format.html.phone { redirect_to progress_path }
format.html.none { render "trash" }
end
Варианты также поддерживают общий блок `any`/`all`, как и форматы.
Он работает как для встроенного:
respond_to do |format|
format.html.any { render text: "any" }
format.html.phone { render text: "phone" }
end
так и для синтаксиса блока:
respond_to do |format|
format.html do |variant|
variant.any(:tablet, :phablet){ render text: "any" }
variant.phone { render text: "phone" }
end
end
Вы также можете установить массив вариантов:
request.variant = [:tablet, :phone]
что будет работать аналогично обработке форматов и типов MIME. Если варианта :tablet не будет, будет выбран вариант :phone:
respond_to do |format| format.html.none format.html.phone # this gets rendered end
Убедитесь, что вы проверили документацию respond_with и ActionController::MimeResponds.respond_to для получения дополнительных примеров.
# File actionpack/lib/action_controller/metal/mime_responds.rb, line 253
def respond_to(*mimes, &block)
raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
if collector = retrieve_collector_from_mimes(mimes, &block)
response = collector.response
response ? response.call : render({})
end
end Для данного действия контроллера #respond_with генерирует соответствующий ответ на основе типа MIME, запрошенного клиентом.
Если метод вызывается только с ресурсом, как в этом примере -
class PeopleController < ApplicationController
respond_to :html, :xml, :json
def index
@people = Person.all
respond_with @people
end
end
то тип MIME ответа обычно выбирается на основе заголовка Accept запроса и набора доступных форматов, объявленных предыдущими вызовами метода класса контроллера respond_to. Альтернативно, тип MIME можно выбрать, явно установив request.format в контроллере.
Если подходящий формат не определён, приложение возвращает статус «406 - не приемлемо». В противном случае, по умолчанию рендерится шаблон с именем текущего действия и выбранного формата, например index.html.erb. Если шаблон не найден, поведение зависит от выбранного формата:
-
для html ответа — если метод запроса
get, возникает исключение, но для других запросов, таких какpost, ответ зависит от того, есть ли у ресурса ошибки валидации (предполагая, что была предпринята попытка сохранить ресурс, например, действиемcreate) —-
Если ошибок нет, т.е. ресурс был сохранён успешно, ответ
redirectк ресурсу, т.е. его действиюshow. -
Если есть ошибки валидации, рендерится действие по умолчанию, которое является
:newдля запросаpostили:editдля запросовpatchилиput.
Таким образом, пример —
respond_to :html, :xml def create @user = User.new(params[:user]) flash[:notice] = 'User was successfully created.' if @user.save respond_with(@user) end
эквивалентен, при отсутствии
create.html.erb, —def create @user = User.new(params[:user]) respond_to do |format| if @user.save flash[:notice] = 'User was successfully created.' format.html { redirect_to(@user) } format.xml { render xml: @user } else format.html { render action: "new" } format.xml { render xml: @user } end end end -
-
для запроса javascript — если шаблон не найден, генерируется исключение.
-
для других запросов — например, форматов данных, таких как xml, json, csv и т.д., если ресурс, переданный в
respond_with, отвечает методуto_<format>, метод пытается отобразить ресурс в запрошенном формате напрямую, например, для запроса xml, ответ эквивалентен вызовуrender xml: resource.
Вложенные ресурсы
Как указано выше, аргумент resources , переданный в respond_with, может выполнять две функции. Он может использоваться для генерации URL перенаправления для успешных html запросов (например, для действий create , когда шаблон отсутствует), в то время как для форматов, отличных от html и javascript, это объект, который отображается, преобразуясь непосредственно в необходимый формат (опять же, при отсутствии шаблона).
Для перенаправления успешных html запросов respond_with также поддерживает использование вложенных ресурсов, которые передаются так же, как и в form_for и polymorphic_url. Например -
def create @project = Project.find(params[:project_id]) @task = @project.comments.build(params[:task]) flash[:notice] = 'Task was successfully created.' if @task.save respond_with(@project, @task) end
Это приведет к тому, что respond_with перенаправит на project_task_url вместо task_url. Для форматов запросов, отличных от html или javascript, если таким образом передано несколько ресурсов, отображается последний указанный.
Настройка поведения ответа
Как и respond_to, respond_with также может вызываться с блоком, который может переопределить любые из стандартных ответов, например -
def create
@user = User.new(params[:user])
flash[:notice] = "User was successfully created." if @user.save
respond_with(@user) do |format|
format.html { render }
end
end
Аргумент, передаваемый в блок, — это объект ActionController::MimeResponds::Collector, который хранит ответы для форматов, определённых внутри блока. Обратите внимание, что форматы с явно определёнными ответами в этом блоке не должны быть предварительно объявлены с помощью метода класса respond_to.
Также, хэш, переданный в respond_with непосредственно после указанных ресурсов(ов), интерпретируется как набор опций, относящихся ко всем форматам. Любая опция, принятая render, может быть использована, например,
respond_with @people, status: 200
Однако обратите внимание, что эти опции игнорируются после неудачной попытки сохранить ресурс, например, при автоматическом отображении :new после запроса POST.
Два дополнительных варианта имеют отношение именно к respond_with —
-
:location— переопределяет адрес по умолчанию, используемый после успешного htmlpostзапроса. -
:action— переопределяет действие отображения по умолчанию после неудачного htmlpostзапроса.
# File actionpack/lib/action_controller/metal/mime_responds.rb, line 390
def respond_with(*resources, &block)
if self.class.mimes_for_respond_to.empty?
raise "In order to use respond_with, first you need to declare the " "formats your controller responds to in the class level."
end
if collector = retrieve_collector_from_mimes(&block)
options = resources.size == 1 ? {} : resources.extract_options!
options = options.clone
options[:default_response] = collector.response
(options.delete(:responder) || self.class.responder).call(self, resources, options)
end
end
© 2004–2016 David Heinemeier Hansson
Licensed under the MIT License.