使用active_model_serializers序列化深层嵌套关联

我正在使用Rails 4.2.1active_model_serializers 0.10.0.rc2

我是API的新手,并选择了active_model_serializers因为它似乎成为rails的标准(尽管我不反对使用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问题,似乎有一些关于应该如何处理的问题(或者如果这种function甚至实际上得到支持),但是我很难搞清楚当前的情况国家是。

  • 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

建议的解决方案之一是用一个方法来覆盖属性来调用嵌套属性,但这似乎被认为是一个黑客,所以我想尽可能避免它。

无论如何,非常感谢如何进行此API或一般API建议的示例。

根据提交1426: https : //github.com/rails-api/active_model_serializers/pull/1426 – 以及相关讨论,您可以看到jsonattributes序列化的默认嵌套是一个级别。

如果您希望默认深度嵌套,可以在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

如果您使用的是JSONAPI适配器,则可以执行以下操作来呈现嵌套关系:

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

您可以从jsonapi文档中了解更多信息: http ://jsonapi.org/format/#fetching-includes

所以这不是最好的,甚至不是一个好的答案,但这是我需要它的工作方式。

虽然在使用带有AMS的json_api适配器时似乎支持包含嵌套和侧载属性,但我需要支持flat 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_manybelongs_to关联,只是自定义了行为。 我用slice来选择特定的属性。 希望更优雅的解决方案即将推出。

在我的例子中,我在’MyApp / config / initializers’中创建了一个名为’active_model_serializer.rb’的文件,其中包含以下内容:

 ActiveModelSerializers.config.default_includes = '**' 

在此处输入图像描述

别忘了重启服务器:

 $ rails s 

您可以更改ActiveModel::Serializer default_includes

 # 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 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 

结果:

 { "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" } ] } ] } ] } } 

这应该做你想要的。

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

顶级嵌套将自动包含在内,但是任何比这更深的嵌套都需要包含在您的show动作中或您调用它的任何位置。