J'utilise Rails 4.2.1
et active_model_serializers 0.10.0.rc2
Je suis nouveau dans les API et j'ai choisi active_model_serializers
car il semble devenir la norme pour Rails (même si je ne suis pas opposé à l'utilisation de RABL
ou d'un autre sérialiseur)
Le problème que j'ai est que je n'arrive pas à inclure divers attributs dans les relations à plusieurs niveaux. Par exemple, j'ai:
Projets
class ProjectSerializer < ActiveModel::Serializer
attributes :id,
:name,
:updated_at
has_many :estimates, include_nested_associations: true
end
et Estimations
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
Propositions
class ProposalSerializer < ActiveModel::Serializer
attributes :id,
:name,
:updated_at,
:estimate_id
belongs_to :estimate
end
Quand j'ai frappé le /projects/1
ce qui précède produit:
{
"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
}
]
}
Cependant, ce que j'aimerais qu'il produise, c'est:
{
"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"
}
]
}
]
}
Idéalement, j'aimerais également pouvoir spécifier quels attributs, associations et attributs de ces associations qui sont inclus dans chaque sérialiseur.
J'ai examiné les problèmes AMS, et il semble y avoir des allers-retours sur la façon dont cela devrait être géré (ou si ce type de fonctionnalité est même réellement pris en charge), mais j'ai du mal à déterminer exactement ce que le courant l'état est.
L'une des solutions proposées était de remplacer l'attribut par une méthode pour appeler les attributs imbriqués, mais cela semble être considéré comme un hack donc je voulais l'éviter si possible.
Quoi qu'il en soit, un exemple de la façon de procéder ou des conseils généraux sur l'API seraient très appréciés.
Donc, ce n'est pas la meilleure ou même une bonne réponse, mais cela fonctionne comme j'en ai besoin.
Tout en incluant les attributs imbriqués et chargés latéralement semble être pris en charge lors de l'utilisation de json_api
adaptateur avec AMS, j'avais besoin de support pour json plat. De plus, cette méthode a bien fonctionné car chaque sérialiseur génère spécifiquement exactement ce dont j'ai besoin pour être indépendant de tout autre sérialiseur et sans avoir à faire quoi que ce soit dans le contrôleur.
Les commentaires/méthodes alternatives sont toujours les bienvenus.
Modèle de projet
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
Résultat
[
{
"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"
}
]
}
]
}
]
J'ai essentiellement ignoré toute tentative d'implémenter has_many
ou belongs_to
associations dans les sérialiseurs et vient de personnaliser le comportement. J'ai utilisé slice
pour sélectionner des attributs spécifiques. Espérons qu'une solution plus élégante sera proposée.
Par commit 1426: https://github.com/Rails-api/active_model_serializers/pull/1426 - et discussion connexe, vous pouvez voir que l'imbrication par défaut pour json
et attributes
la sérialisation est un niveau.
Si vous souhaitez une imbrication approfondie par défaut, vous pouvez définir une propriété de configuration dans un initialiseur active_model_serializer:
ActiveModelSerializers.config.default_includes = '**'
Pour une référence détaillée de v0.10.6 : https://github.com/Rails-api/active_model_serializers/blob/v0.10.6/ docs/general/adapters.md # include-option
Si vous utilisez l'adaptateur JSONAPI, vous pouvez effectuer les opérations suivantes pour rendre les relations imbriquées:
render json: @project, include: ['estimates', 'estimates.project_code', 'estimates.tax_type', 'estimates.proposals']
Vous pouvez lire plus de la documentation jsonapi: http://jsonapi.org/format/#fetching-includes
Vous pouvez changer default_includes
pour le ActiveModel::Serializer
:
# config/initializers/active_model_serializer.rb
ActiveModel::Serializer.config.default_includes = '**' # (default '*')
De plus, afin d'éviter une récursion infinie, vous pouvez contrôler la sérialisation imbriquée comme suit:
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
Résultat:
{
"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"
}
]
}
]
}
]
}
}
Cela devrait faire ce que vous cherchez.
@project.to_json( include: { estimates: { include: {:project, :project_code, :tax_type, :proposals } } } )
L'imbrication de niveau supérieur sera automatiquement incluse, mais tout ce qui est plus profond devra être inclus dans votre action de show ou où que vous l'appeliez.