ruby on rails - fast_jsonapi - Serialización de asociaciones profundamente anidadas con active_model_serializers
rails serializer (6)
Así que esta no es la mejor o incluso una buena respuesta, pero está funcionando como lo necesito.
Mientras que la inclusión de atributos anidados y cargados lateralmente parece ser compatible cuando se usa el adaptador json_api
con AMS, necesitaba soporte para json plano. Además, este método funcionó bien porque cada serializador genera específicamente exactamente lo que necesito para ser independiente de cualquier otro serializador y sin tener que hacer nada en el controlador.
Comentarios / métodos alternativos son siempre bienvenidos.
Modelo de proyecto
class Project < ActiveRecord::Base
has_many :estimates, autosave: true, dependent: :destroy
end
ProyectosController
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
Resultado
[
{
"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"
}
]
}
]
}
]
Básicamente, ignoré el intento de implementar cualquier has_many
o belongs_to
en los serializadores y solo personalicé el comportamiento. Utilicé slice
para seleccionar atributos específicos. Esperemos que venga una solución más elegante.
Estoy usando Rails 4.2.1
y active_model_serializers 0.10.0.rc2
Soy nuevo en las API y elegí active_model_serializers
porque parece que se está convirtiendo en el estándar para los rieles (aunque no estoy en contra de usar RABL
u otro serializador)
El problema que tengo es que parece que no puedo incluir varios atributos en relaciones de varios niveles. Por ejemplo tengo:
Proyectos
class ProjectSerializer < ActiveModel::Serializer
attributes :id,
:name,
:updated_at
has_many :estimates, include_nested_associations: true
end
y estimaciones
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
Propuestas
class ProposalSerializer < ActiveModel::Serializer
attributes :id,
:name,
:updated_at,
:estimate_id
belongs_to :estimate
end
Cuando llego a los /projects/1
lo anterior produce:
{
"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
}
]
}
Sin embargo, lo que me gustaría producir es:
{
"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"
}
]
}
]
}
Idealmente, también me gustaría poder especificar qué atributos, asociaciones y atributos de esas asociaciones se incluyen en cada serializador.
He estado revisando los problemas de AMS, y parece que hay algo de ida y vuelta sobre cómo se debe manejar esto (o si este tipo de funcionalidad es realmente compatible) pero estoy teniendo dificultades para averiguar exactamente qué es lo actual. estado es.
- https://github.com/rails-api/active_model_serializers/issues/835
- https://github.com/rails-api/active_model_serializers/issues/968
- https://github.com/rails-api/active_model_serializers/issues/414
- https://github.com/rails-api/active_model_serializers/issues/444
Una de las soluciones propuestas fue anular el atributo con un método para llamar a los atributos anidados, pero eso parece ser considerado como un hack, por lo que quería evitarlo si es posible.
De todos modos, se agradecería mucho un ejemplo de qué hacer acerca de esto o el consejo general de la API.
Esto debería hacer lo que estás buscando.
@project.to_json( include: { estimates: { include: {:project, :project_code, :tax_type, :proposals } } } )
El anidamiento de nivel superior se incluirá automáticamente, pero cualquier cosa más profunda que eso deberá incluirse en la acción de su programa o donde sea que llame.
Puede cambiar las default_includes
para ActiveModel::Serializer
:
# config/initializers/active_model_serializer.rb
ActiveModel::Serializer.config.default_includes = ''**'' # (default ''*'')
Además, para evitar la recursión infinita, puede controlar la siguiente serialización anidada:
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 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 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
Resultado:
{
"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"
}
]
}
]
}
]
}
}
Según el compromiso 1426: https://github.com/rails-api/active_model_serializers/pull/1426 - y discusión relacionada, puede ver que el anidamiento predeterminado para la serialización json
y los attributes
es un nivel.
Si desea un anidamiento profundo de forma predeterminada, puede establecer una propiedad de configuración en un inicializador active_model_serializer:
ActiveModelSerializers.config.default_includes = ''**''
Para referencia detallada de v0.10.6 : https://github.com/rails-api/active_model_serializers/blob/v0.10.6/docs/general/adapters.md#include-option
Si está utilizando el adaptador JSONAPI, puede hacer lo siguiente para representar relaciones anidadas:
render json: @project, include: [''estimates'', ''estimates.project_code'', ''estimates.tax_type'', ''estimates.proposals'']
Puede leer más en la documentación de jsonapi: http://jsonapi.org/format/#fetching-includes