web-dev-qa-db-ja.com

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

および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

提案

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の問題を調べてきましたが、これをどのように扱うべきか(またはこの種の機能が実際にサポートされているかどうか)についていくつかのやり取りがあるようですが、現在の状況を正確に把握するのは困難です状態です。

提案された解決策の1つは、ネストされた属性を呼び出すメソッドで属性をオーバーライドすることでしたが、それはハックと見なされるようなので、可能であればそれを避けたいと思いました。

とにかく、このアドバイスや一般的なAPIアドバイスをどのように実行するかの例は大歓迎です。

31
Eric Norcross

だから、これは最良の答えでも良い答えでもありませんが、これは私がそれを必要とする方法で機能しています。

ネストされた属性とサイドロードされた属性を含めると、json_api AMS付きアダプター、フラット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_manyまたはbelongs_toシリアライザーの関連付けと、動作のカスタマイズ。 sliceを使用して特定の属性を選択しました。よりエレガントなソリューションが近々登場することを願っています。

12
Eric Norcross

コミット1426ごと: https://github.com/Rails-api/active_model_serializers/pull/1426 -および関連する説明から、jsonおよびattributesシリアル化のデフォルトのネストは1レベルであることがわかります。

デフォルトで深いネストが必要な場合は、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

48
Daniel D

JSONAPIアダプターを使用している場合、以下を実行してネストされた関係をレンダリングできます。

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

Jsonapiドキュメントから詳細を読むことができます: http://jsonapi.org/format/#fetching-includes

11
Brandon Patram

私の場合、次の内容の「MyApp/config/initializers」に配置された「active_model_serializer.rb」というファイルを作成しました。

ActiveModelSerializers.config.default_includes = '**'

enter image description here

サーバーを再起動することを忘れないでください:

$ Rails s

あなたは変えられる default_includes のために ActiveModel::Serializer

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

これはあなたが探していることをするはずです。

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

最上位のネストは自動的に含まれますが、それよりも深いものは、ショーアクションまたはこれを呼び出す場所に含める必要があります。

2
Colton Fent