rails-api / active_model_serializers

ActiveModel::Serializer implementation and Rails hooks
MIT License
5.32k stars 1.39k forks source link

How to format associations to exclude root element? #2426

Open sarah-mica opened 2 years ago

sarah-mica commented 2 years ago

I'm using ActiveModel::Serializer to format my model data as json, but I would like to change the formatting so that the associations of my main model are not nested. I tried setting root: false and that doesn't work

Expected behavior vs actual behavior

I have a model Account with an association belongs_to :account_status and I was able to add this association in the AccountSerializer to get that associated data just fine. But due to my api contract requirements, I need the json to be formatted without the association nesting.

So I'm getting this:

{
   "account_id": 1
   <other account info>
   ...
   "account_status": {
      "status_code": 1
      "desc": "status description"
      ....
   }
}

But I want this:

{
   "account_id": 1
   <other account info>
   ...
   "account_status_status_code": 1
   "account_status_desc": "status description"
   ....
}

Model + Serializer code

How can I achieve the expected behavior without writing each account_status field as an individual attribute in the AccountSerializer ??

Controller

class AccountsController < ActionController::API
  def show
    account = Account.find(params[:account_id])
    render json: account
  end
end

Model

class Account < ActiveRecord::Base
  self.primary_key = :account_id

  belongs_to :account_status, foreign_key: :account_status_code, inverse_of: :accounts

  validates :account_status_code, presence: true
end

Serializer

class AccountSerializer < ActiveModel::Serializer
  attributes(*Account.attribute_names.map(&:to_sym))

  belongs_to :account_status, 
             foreign_key: :account_status_code,
             inverse_of: :accounts
end

Environment

ActiveModelSerializers Version 0.10.0:

Output of ruby -e "puts RUBY_DESCRIPTION": ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-darwin19]

OS Type & Version: macOS Catalina v 10.15.7

Integrated application and version Rails 6.1.4:

wasifhossain commented 2 years ago

as it looks, your expected structure does NOT match any of the available adapters (attributes/json/json-api).

so you have to DIY, by defining the association fields at the top-level serializer like this:

class AccountSerializer < ActiveModel::Serializer
  attributes :account_id, :account_status_status_code, :account_status_desc

  def account_status_status_code
    object.status_code
  end

  def account_status_desc
    object.desc
  end
end

DOC: https://github.com/rails-api/active_model_serializers/blob/0-10-stable/docs/general/serializers.md#attributes-1

sarah-mica commented 2 years ago

Thanks for verifying @wasifhossain. I knew this was an option but I wanted to avoid manually defining each association's trait manually if there was a better way.

I know the json structure is not standard. Unfortunately I'm making an api port from a legacy system so I can't change the contract at this time.