Spec-Zone.ru › Ruby on Rails 4.1

модуль ActionController::MimeResponds

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

respond_to(*mimes, &block) Показать исходный код

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

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(*resources, &block) Показать исходный код

Для данного действия контроллера #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) —

    1. Если ошибок нет, т.е. ресурс был сохранён успешно, ответ redirect к ресурсу, т.е. его действию show.

    2. Если есть ошибки валидации, рендерится действие по умолчанию, которое является :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 —

  1. :location — переопределяет адрес по умолчанию, используемый после успешного html post запроса.

  2. :action — переопределяет действие отображения по умолчанию после неудачного html post запроса.

# 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.

Spec-Zone.ru

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