Требуется ли ActiveModel::Serializer явный вызов рендеринга?

Я знаю, что при использовании шаблонов представления (html, rabl) мне не нужен явный вызов рендеринга в моем действии контроллера, потому что по умолчанию Rails отображает шаблон с именем, соответствующим имени действия контроллера. Мне нравится эта концепция (не заботясь о рендеринге в коде моего контроллера), и поэтому задаюсь вопросом, возможно ли это также при использовании ActiveModel::Serializer?

Пример, это код сгенерированного контроллера (Rails 4.1.0):

class ProductsController < ApplicationController
  before_action :set_product, only: [:show, :edit, :update, :destroy]

  #other actions
  # GET /products/1
  # GET /products/1.json
  def show
  end
end

и это сериализатор:

class ProductSerializer < ActiveModel::Serializer
  attributes :id, :name, :description, :url, :quantity, :price
end

Нажав на /products/1.json, я ожидаю, что произойдут две вещи:

  1. Поля, не перечисленные в сериализаторе, должны быть пропущены,
  2. Весь объект JSON должен быть инкапсулирован в поле верхнего уровня "product".

Однако этого не происходит, весь сериализатор игнорируется. Но тогда, если я изменю метод Show следующим образом:

# GET /products/1
# GET /products/1.json
def show
  @product = Product.find(params[:id])
  respond_to do |format|
    format.html
    format.json { render json: @product }
  end
end

И теперь все хорошо, но я потерял преимущество фильтра before_action (и мне кажется, что у меня есть избыточный код).

Как это действительно должно быть сделано?

2 ответа

"Избыточный код", который мы видим во втором, это только эта строка:

@product = Product.find(params[:id])

И я верю, что это та же логика, что и у вашей before_action. Вам не нужна эта строка, просто удалите ее. Теперь дублирование удалено.

Для оставшейся части. Действие должно знать, что делать. По умолчанию, если действие пустое или отсутствует, соответствующее 'action_name'.html.erb (и другие форматы, указанные respond_to) будет найден и обработан.

Вот почему работает генератор Rails 4: он создает show.html.erb а также show.json.jbuilder которые оказываются.

С ActiveModel::SerializerУ вас нет шаблона. Если вы оставляете действие пустым, оно не имеет ни малейшего представления, что делать. Таким образом, вы должны сказать это, чтобы сделать @product как JSON, либо:

render json: @product

или же

respond_with @product

Без явного render или же respond_with или же respond_to Rails будет искать соответствующий шаблон. Если этот шаблон не существует, Rails выдает ошибку.

Тем не менее, вы можете создать свой собственный распознаватель, чтобы обойти это. Например, предположим, что вы создали app\models\serialize_resolver.rb и поместите это в это:

class SerializeResolver < ActionView::Resolver
  protected
  def find_templates(name, prefix, partial, details)
    if details[:formats].to_a.include?(:json) && prefix !~ /layout/
      instance = prefix.to_s.singularize
      source = "<%= @#{instance}.active_model_serializer.new(@#{instance}).to_json.html_safe %>"
      identifier = "SerializeResolver - #{prefix} - #{name}"
      handler = ActionView::Template.registered_template_handler(:erb)
      details = {
        format: Mime[:json],
        updated_at: Date.today,
        virtual_path: "/#{normalize_path(name, prefix)}"
      }
      [ActionView::Template.new(source, identifier, handler, details)]
    else
      []
    end
  end

  def normalize_path(name, prefix)
    prefix.present? ? "#{prefix}/#{name}" : name
  end
end

И затем, в вашем контроллере приложения (или в отдельном контроллере) место:

  append_view_path ::SerializeResolver.new

При этом вы сможете делать то, что хотите. Если это запрос json, он создаст шаблон erb с правильным содержимым и вернет его.

Ограничения:

  • Это немного неуклюже, потому что оно опирается на erb, который не нужен. Если у меня будет время, я создам простой обработчик шаблонов. Тогда мы можем вызвать это без erb.
  • Это стирает по умолчанию json ответ.
  • Он полагается на имя контроллера, чтобы найти переменную экземпляра (/posts преобразуется в @post.)
  • Я только немного проверил это. Логика, вероятно, может быть умнее.

Заметки:

  • Если шаблон присутствует, он будет использован первым. Это позволяет вам переопределить это поведение.
  • Вы не можете просто создать новый рендерер и зарегистрировать его, потому что процесс по умолчанию не поразит его. Если шаблон не найден, вы получаете сообщение об ошибке. Если файл найден, он переходит к вызову обработчика шаблона.
Другие вопросы по тегам