Ruby-on-rails 使用 active_model_serializers 序列化深度嵌套的关联

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/32079897/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 19:02:55  来源:igfitidea点击:

Serializing deeply nested associations with active_model_serializers

ruby-on-railsjsonactive-model-serializers

提问by Eric Norcross

I'm using Rails 4.2.1and 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_serializersbecause it seems to be becoming the standard for rails (Although I'm not opposed to using RABLor 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/1the 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 that 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 建议的示例。

采纳答案by Eric Norcross

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_apiadapter with AMS, I needed support for flat 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.

虽然在将json_api适配器与 AMS 一起使用时似乎支持包含嵌套和侧载属性,但我需要支持平面 json。此外,这种方法运行良好,因为每个序列化器都专门生成我需要的内容,独立于任何其他序列化器,而无需在控制器中执行任何操作。

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_manyor belongs_toassociations in the serializers and just customized the behavior. I used sliceto select specific attributes. Hopefully a more elegant solution will be forth coming.

我基本上没有尝试在序列化程序中实现任何has_manybelongs_to关联,而只是自定义了行为。我曾经slice选择特定的属性。希望一个更优雅的解决方案即将到来。

回答by Daniel D

Per commit 1426: https://github.com/rails-api/active_model_serializers/pull/1426- and related discussion, you can see that the default nesting for jsonand attributesserialization 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 = '**'

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

回答by Brandon Patram

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

回答by blackchestnut

You can change default_includesfor the ActiveModel::Serializer:

您可以更改default_includesActiveModel::Serializer

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

回答by Javier Calatrava Llavería

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 = '**'

enter image description here

在此处输入图片说明

Do not forget to restart the server:

不要忘记重新启动服务器:

$ rails s

回答by Colton Fent

This should do what you're looking for.

这应该做你正在寻找的。

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

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

顶级嵌套将自动包含在内,但任何比这更深的嵌套都需要包含在您的显示操作中或您调用它的任何地方。