Spec-Zone.ru › Ruby on Rails 7.0

модуль ActionController::MimeResponds

Публичные методы экземпляра

respond_to(*mimes) { |collector| ... } Показать исходный код
# File actionpack/lib/action_controller/metal/mime_responds.rb, line 201
def respond_to(*mimes)
  raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?

  collector = Collector.new(mimes, request.variant)
  yield collector if block_given?

  if format = collector.negotiate_format(request)
    if media_type && media_type != format
      raise ActionController::RespondToMismatchError
    end
    _process_format(format)
    _set_rendered_content_type(format) unless collector.any_response?
    response = collector.response
    response.call if response
  else
    raise ActionController::UnknownFormat
  end
end

Без поддержки веб-сервисов действие, собирающее данные для отображения списка людей, может выглядеть примерно так:

def index
  @people = Person.all
end

Это действие неявно отвечает на все форматы, но форматы также можно перечислить явно:

def index
  @people = Person.all
  respond_to :html, :js
end

Вот то же действие с встроенной поддержкой веб-сервисов:

def index
  @people = Person.all

  respond_to do |format|
    format.html
    format.js
    format.xml { render xml: @people }
  end
end

Это означает: «если клиент хочет HTML или JS в ответ на это действие, просто ответьте так, как мы делали раньше, но если клиент хочет 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/jpeg", :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

any также может использоваться без аргументов, в этом случае он будет использоваться для любого запрошенного пользователем формата:

respond_to do |format|
  format.html
  format.any { redirect_to support_path }
end

Форматы могут иметь разные варианты.

Вариант запроса — это специализация формата запроса, например :tablet, :phone, или :desktop.

Мы часто хотим отображать разные html/json/xml шаблоны для телефонов, планшетов и настольных браузеров. Варианты упрощают это.

Вы можете задать вариант в before_action:

request.variant = :tablet if /iPad/.match?(request.user_agent)

Отвечайте на варианты в действии так же, как вы отвечаете на форматы:

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 html: "any"   }
  format.html.phone { render html: "phone" }
end

так и для синтаксиса блока:

respond_to do |format|
  format.html do |variant|
    variant.any(:tablet, :phablet){ render html: "any" }
    variant.phone { render html: "phone" }
  end
end

Вы также можете установить массив вариантов:

request.variant = [:tablet, :phone]

Это будет работать аналогично согласованию форматов и типов MIME. Если объявлен не :tablet вариант, будет использован :phone вариант:

respond_to do |format|
  format.html.none
  format.html.phone # this gets rendered
end

© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API