Сериализация глубоко вложенных ассоциаций с 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, заслуживает высокой оценки.
Ответы
Ответ 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 или где бы вы это ни называли.