简体   繁体   English

Rails:使用 active_model_serializers 序列化深度嵌套的关联

[英]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.1active_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 - 和相关讨论,您可以看到jsonattributes序列化的默认嵌套是一级。

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_manybelongs_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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM