[英]Rails: Serializing deeply nested associations with active_model_serializers
I'm using Rails 4.2.1
and active_model_serializers 0.10.0.rc2
我正在使用Rails 4.2.1
和active_model_serializers 0.10.0.rc2
I'm new to API's and chose active_model_serializers
because it seems to be becoming the standard for rails (Although I'm not opposed to using RABL
or another serializer)我是 API 的新手并选择了active_model_serializers
因为它似乎正在成为 rails 的标准(尽管我不反对使用RABL
或其他序列化程序)
The problem I'm having is that I can't seem to include various attributes in multi-level relationships.我遇到的问题是我似乎无法在多级关系中包含各种属性。 For instance, I have:例如,我有:
Projects项目
class ProjectSerializer < ActiveModel::Serializer
attributes :id,
:name,
:updated_at
has_many :estimates, include_nested_associations: true
end
and Estimates和估计
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
Proposals提案
class ProposalSerializer < ActiveModel::Serializer
attributes :id,
:name,
:updated_at,
:estimate_id
belongs_to :estimate
end
When I hit the /projects/1
the above produces:当我点击/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
}
]
}
However, what I'd like it to produce is:但是,我希望它产生的是:
{
"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"
}
]
}
]
}
Ideally, I'd also like to be able to specify which attributes, associations, and attributes of those associations are included in each serializer.理想情况下,我还希望能够指定每个序列化程序中包含哪些属性、关联和这些关联的属性。
I've been looking through the AMS issues, and there does seem to be some back and forth on how this should be handled (or if this kind of functionality is even actually supported) but I'm having difficulty figuring out exactly what the current state is.我一直在查看 AMS 问题,并且似乎在如何处理这方面存在一些来回(或者是否实际上支持这种功能),但我很难弄清楚当前到底是什么状态是。
One of the proposed solutions was to override the attribute with a method to call the nested attributes, but that seems to be regarded as a hack so I wanted to avoid it if possible.建议的解决方案之一是使用调用嵌套属性的方法覆盖该属性,但这似乎被视为一种黑客行为,因此我想尽可能避免它。
Anyway, an example of what of how to go about this or general API advice would be much appreciated.无论如何,非常感谢如何处理此或一般 API 建议的示例。
Per commit 1426:https://github.com/rails-api/active_model_serializers/pull/1426 - and related discussion, you can see that the default nesting for json
and attributes
serialization is one level.每个提交 1426:https ://github.com/rails-api/active_model_serializers/pull/1426 - 和相关讨论,您可以看到json
和attributes
序列化的默认嵌套是一级。
If you want deep nesting by default, you can set a configuration property in an active_model_serializer initializer:如果你想要默认深度嵌套,你可以在 active_model_serializer 初始值设定项中设置一个配置属性:
ActiveModelSerializers.config.default_includes = '**'
For detailed reference from v0.10.6 : https://github.com/rails-api/active_model_serializers/blob/v0.10.6/docs/general/adapters.md#include-option有关v0.10.6 的详细参考: https : //github.com/rails-api/active_model_serializers/blob/v0.10.6/docs/general/adapters.md#include-option
So this my not be the best or even a good answer, but this is working how I need it to.所以这不是最好的,甚至不是一个好的答案,但这是我需要的。
While including nested and side-loaded attributes appears to be supported when using the json_api
adapter with AMS, I needed support for flat json.虽然在将json_api
适配器与 AMS 一起使用时似乎支持包含嵌套和侧载属性,但我需要支持平面 json。 In addition, this method worked well because each serializer is specifically generating exactly what I need it to independent of any other serializer and without having to do anything in the controller.此外,这种方法运行良好,因为每个序列化器都专门生成我需要的内容,独立于任何其他序列化器,而无需在控制器中执行任何操作。
Comments / alternate methods are always welcome.始终欢迎评论/替代方法。
Project Model项目模型
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
Result结果
[
{
"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"
}
]
}
]
}
]
I basically disregarded trying to implement any has_many
or belongs_to
associations in the serializers and just customized the behavior.我基本上忽略了尝试在序列化程序中实现任何has_many
或belongs_to
关联,而只是自定义了行为。 I used slice
to select specific attributes.我使用slice
来选择特定的属性。 Hopefully a more elegant solution will be forth coming.希望一个更优雅的解决方案即将到来。
If you are using the JSONAPI adapter you can do the following to render nested relationships:如果您使用 JSONAPI 适配器,您可以执行以下操作来呈现嵌套关系:
render json: @project, include: ['estimates', 'estimates.project_code', 'estimates.tax_type', 'estimates.proposals']
You can read more from the jsonapi documentation: http://jsonapi.org/format/#fetching-includes您可以从 jsonapi 文档中阅读更多内容: http ://jsonapi.org/format/#fetching-includes
You can change default_includes
for the ActiveModel::Serializer
:您可以更改ActiveModel::Serializer
default_includes
:
# config/initializers/active_model_serializer.rb
ActiveModel::Serializer.config.default_includes = '**' # (default '*')
In addition, in order to avoid infinite recursion, you can control the nested serialization follows:另外,为了避免无限递归,可以控制嵌套序列化如下:
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
Result:结果:
{
"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"
}
]
}
]
}
]
}
}
In my case I created a file called 'active_model_serializer.rb' placed at 'MyApp/config/initializers' with the following content:在我的例子中,我创建了一个名为“active_model_serializer.rb”的文件,放置在“MyApp/config/initializers”中,内容如下:
ActiveModelSerializers.config.default_includes = '**'
Do not forget to restart the server:不要忘记重新启动服务器:
$ rails s
This should do what you're looking for.这应该做你正在寻找的。
@project.to_json( include: { estimates: { include: {:project, :project_code, :tax_type, :proposals } } } )
The top level nesting will be automatically included, but anything deeper than that will need to be included in your show action or wherever you are calling this.顶级嵌套将自动包含在内,但任何比这更深的嵌套都需要包含在您的显示操作中或您调用它的任何地方。
Just to buttress on Eric Norcross' answer , I added the following answer.只是为了支持Eric Norcross 的回答,我添加了以下答案。
I was using the jsonapi-serializer gem for serilization in my rails application.我在 Rails 应用程序中使用jsonapi-serializer gem进行序列化。 I didn't find including nested in the controllers and side-loaded attributes convenient for me.我没有发现包含嵌套在控制器和侧载属性中对我来说很方便。 I just wanted a better separation of concerns.我只是想要更好地分离关注点。 So anything that has to do with serialization should only be inside the serialization files and they should have nothing to do with the controller files.所以任何与序列化有关的东西都应该只在序列化文件中,它们应该与控制器文件无关。
So in my case, I had the following associations:所以就我而言,我有以下关联:
School Model学校模式
module Baserecord
class School < ApplicationRecord
has_many :programs, class_name: Baserecord.program_class, dependent: :destroy
has_many :faculties, class_name: Baserecord.faculty_class, through: :programs, dependent: :destroy
end
Program Model程序模型
module Baserecord
class Faculty < ApplicationRecord
belongs_to :program, class_name: Baserecord.program_class
has_many :departments, class_name: Baserecord.department_class, dependent: :destroy
has_many :program_of_studies, class_name: Baserecord.program_of_study_class, through: :departments,
dependent: :destroy
end
end
Here's how I structured my serializer file :这是我构建序列化程序文件的方式:
School Serializer学校序列化器
module Baserecord
class SchoolSerializer
include JSONAPI::Serializer
attributes :id, :name, :code, :description, :school_logo, :motto, :address
attribute :programs do |object|
# Create an empty array
customized_programs = []
object.programs.each do |program|
# Assign object attributes (returns a hash)
custom_program = program.attributes
# Create custom nested and side-loaded attributes
custom_program[:faculties] = program.faculties
# Push the created custom nested and side-loaded attributes into the empty array
customized_programs.push(custom_program)
end
# Return the new array
customized_programs
end
cache_options store: Rails.cache, namespace: 'jsonapi-serializer', expires_in: 1.hour
end
end
That's all.就这样。
I hope this helps我希望这有帮助
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.