Подтвердить что ты не робот

Сериализация глубоко вложенных ассоциаций с active_model_serializers

Я использую Rails 4.2.1 и active_model_serializers 0.10.0.rc2

Я новичок в API и выбрал active_model_serializers, потому что он, похоже, становится стандартом для рельсов (хотя я не против использования RABL или другого сериализатора)

Проблема, с которой я сталкиваюсь, заключается в том, что я не могу включить различные атрибуты в многоуровневые отношения. Например, у меня есть:

Проекты

class ProjectSerializer < ActiveModel::Serializer
  attributes                      :id, 
                                  :name,
                                  :updated_at

  has_many                        :estimates, include_nested_associations: true

end

и Оценки

class EstimateSerializer < ActiveModel::Serializer
  attributes                      :id, 
                                  :name, 
                                  :release_version, 
                                  :exchange_rate, 
                                  :updated_at,

                                  :project_id, 
                                  :project_code_id, 
                                  :tax_type_id 

  belongs_to                      :project
  belongs_to                      :project_code
  belongs_to                      :tax_type

  has_many                        :proposals

end

Предложения

class ProposalSerializer < ActiveModel::Serializer
  attributes                      :id, 
                                  :name, 
                                  :updated_at,

                                  :estimate_id

  belongs_to                      :estimate
end

Когда я нажму на /projects/1, вы получите:

{
  "id": 1,
  "name": "123 Park Ave.",
  "updated_at": "2015-08-09T02:36:23.950Z",
  "estimates": [
    {
      "id": 1,
      "name": "E1",
      "release_version": "v1.0",
      "exchange_rate": "0.0",
      "updated_at": "2015-08-12T04:23:38.183Z",
      "project_id": 1,
      "project_code_id": 8,
      "tax_type_id": 1
    }
  ]
}

Однако, я бы хотел, чтобы он производил:

{
  "id": 1,
  "name": "123 Park Ave.",
  "updated_at": "2015-08-09T02:36:23.950Z",
  "estimates": [
    {
      "id": 1,
      "name": "E1",
      "release_version": "v1.0",
      "exchange_rate": "0.0",
      "updated_at": "2015-08-12T04:23:38.183Z",
      "project": { 
        "id": 1,
        "name": "123 Park Ave."
      },
      "project_code": {
        "id": 8,
        "valuation": 30
      },
      "tax_type": {
        "id": 1,
        "name": "no-tax"
      },
      "proposals": [
        {
          "id": 1,
          "name": "P1",
          "updated_at": "2015-08-12T04:23:38.183Z"
        },
        {
          "id": 2,
          "name": "P2",
          "updated_at": "2015-10-12T04:23:38.183Z"
        }
      ]
    }
  ]
}

В идеале я также хотел бы указать, какие атрибуты, ассоциации и атрибуты этих ассоциаций включены в каждый сериализатор.

Я просматривал проблемы AMS, и, похоже, некоторые из них обращаются к тому, как это следует обрабатывать (или если эта функциональность даже поддерживается на самом деле), но мне трудно понять, что именно каково текущее состояние.

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

В любом случае, пример того, как об этом или общем совете API, заслуживает высокой оценки.

4b9b3361

Ответ 1

Так что это мой не лучший или даже хороший ответ, но он работает, как мне это нужно.

Несмотря на то, что при использовании адаптера json_api с AMS поддерживаются вложенные и загруженные в боковую атрибуты атрибуты, мне нужна поддержка для плоского json. Кроме того, этот метод работал хорошо, потому что каждый сериализатор специально генерирует именно то, что мне нужно, независимо от любого другого сериализатора и без необходимости ничего делать в контроллере.

Комментарии/альтернативные методы всегда приветствуются.

Модель проекта

class Project < ActiveRecord::Base      
  has_many  :estimates, autosave: true, dependent: :destroy
end

ProjectsController

def index
  @projects = Project.all
  render json: @projects
end

ProjectSerializer

class ProjectSerializer < ActiveModel::Serializer
  attributes  :id, 
              :name,
              :updated_at,

              # has_many
              :estimates



  def estimates
    customized_estimates = []

    object.estimates.each do |estimate|
      # Assign object attributes (returns a hash)
      # ===========================================================
      custom_estimate = estimate.attributes


      # Custom nested and side-loaded attributes
      # ===========================================================
      # belongs_to
      custom_estimate[:project] = estimate.project.slice(:id, :name) # get only :id and :name for the project
      custom_estimate[:project_code] = estimate.project_code
      custom_estimate[:tax_type] = estimate.tax_type

      # has_many w/only specified attributes
      custom_estimate[:proposals] = estimate.proposals.collect{|proposal| proposal.slice(:id, :name, :updated_at)}

      # ===========================================================
      customized_estimates.push(custom_estimate)
    end

    return customized_estimates
  end
end

Результат

[
  {
    "id": 1,
    "name": "123 Park Ave.",
    "updated_at": "2015-08-09T02:36:23.950Z",
    "estimates": [
      {
        "id": 1,
        "name": "E1",
        "release_version": "v1.0",
        "exchange_rate": "0.0",
        "created_at": "2015-08-12T04:23:38.183Z",
        "updated_at": "2015-08-12T04:23:38.183Z",
        "project": {
          "id": 1,
          "name": "123 Park Ave."
        },
        "project_code": {
          "id": 8,
          "valuation": 30,
          "created_at": "2015-08-09T18:02:42.079Z",
          "updated_at": "2015-08-09T18:02:42.079Z"
        },
        "tax_type": {
          "id": 1,
          "name": "No Tax",
          "created_at": "2015-08-09T18:02:42.079Z",
          "updated_at": "2015-08-09T18:02:42.079Z"
        },
        "proposals": [
          {
            "id": 1,
            "name": "P1",
            "updated_at": "2015-08-12T04:23:38.183Z"
          },
          {
            "id": 2,
            "name": "P2",
            "updated_at": "2015-10-12T04:23:38.183Z"
          }
        ]
      }
    ]
  }
]

В основном я проигнорировал попытки реализовать любые ассоциации has_many или belongs_to в сериализаторах и просто настроил поведение. Я использовал slice для выбора определенных атрибутов. Надеюсь, будет более элегантное решение.

Ответ 2

За фиксацию 1426: https://github.com/rails-api/active_model_serializers/pull/1426 - и связанное с этим обсуждение, вы можете видеть, что развертывание по умолчанию для сериализации json и attributes является одним уровнем.

Если вы хотите глубокое вложение по умолчанию, вы можете установить свойство конфигурации в инициализаторе active_model_serializer:

ActiveModelSerializers.config.default_includes = '**'

Подробнее см. v0.10.6: https://github.com/rails-api/active_model_serializers/blob/v0.10.6/docs/general/adapters.md#include-option

Ответ 3

В моем случае я создал файл с именем "active_model_serializer.rb", помещенный в "MyApp/config/initializers" со следующим содержимым:

ActiveModelSerializers.config.default_includes = '**'

введите описание изображения здесь

Не забудьте перезапустить сервер:

$ rails s

Ответ 4

Если вы используете адаптер JSONAPI, вы можете сделать следующее для визуализации вложенных отношений:

render json: @project, include: ['estimates', 'estimates.project_code', 'estimates.tax_type', 'estimates.proposals']

Вы можете прочитать больше из документации jsonapi: http://jsonapi.org/format/#fetching-includes

Ответ 5

Вы можете изменить default_includes для ActiveModel::Serializer:

# config/initializers/active_model_serializer.rb
ActiveModel::Serializer.config.default_includes = '**' # (default '*')

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

class UserSerializer < ActiveModel::Serializer
  include Rails.application.routes.url_helpers

  attributes :id, :phone_number, :links, :current_team_id

  # Using serializer from app/serializers/profile_serializer.rb
  has_one :profile
  # Using serializer using serializer described below:
  # UserSerializer::TeamSerializer
  has_many :teams

  def links
    {
      self: user_path(object.id),
      api: api_v1_user_path(id: object.id, format: :json)
    }
  end

  def current_team_id
    object.teams&.first&.id
  end

  class TeamSerializer < ActiveModel::Serializer
    attributes :id, :name, :image_url, :user_id

    # Using serializer using serializer described below:
    # UserSerializer::TeamSerializer::GameSerializer
    has_many :games

    class GameSerializer < ActiveModel::Serializer
      attributes :id, :kind, :address, :date_at

      # Using serializer from app/serializers/gamers_serializer.rb
      has_many :gamers
    end
  end
end

Результат:

{
   "user":{
      "id":1,
      "phone_number":"79202700000",
      "links":{
         "self":"/users/1",
         "api":"/api/v1/users/1.json"
      },
      "current_team_id":1,
      "profile":{
         "id":1,
         "name":"Alexander Kalinichev",
         "username":"Blackchestnut",
         "birthday_on":"1982-11-19",
         "avatar_url":null
      },
      "teams":[
         {
            "id":1,
            "name":"Agile Season",
            "image_url":null,
            "user_id":1,
            "games":[
               {
                  "id":13,
                  "kind":"training",
                  "address":"",
                  "date_at":"2016-12-21T10:05:00.000Z",
                  "gamers":[
                     {
                        "id":17,
                        "user_id":1,
                        "game_id":13,
                        "line":1,
                        "created_at":"2016-11-21T10:05:54.653Z",
                        "updated_at":"2016-11-21T10:05:54.653Z"
                     }
                  ]
               }
            ]
         }
      ]
   }
}

Ответ 6

Это должно делать то, что вы ищете.

@project.to_json( include: { estimates: { include: {:project, :project_code, :tax_type, :proposals } } } )

Вложенность верхнего уровня будет автоматически включаться, но что-то более глубокое, чем это нужно будет включать в ваше действие show или где бы вы это ни называли.