Sarah Mica
Sarah Mica

Reputation: 1

How to format Json to not nest associations with ActiveModel::Serializer

I'm using ActiveModel::Serializer in a rails application to format my model data as a json response, 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 do 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

OS Type & Version: macOS Catalina v 10.15.7 Rails 6.1.4:

ActiveModelSerializers Version 0.10.0:

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

Upvotes: 0

Views: 172

Answers (2)

max
max

Reputation: 102222

Instead of using an assocation in your serializer you can setup delegation in your model:

class Account < ApplicationRecord
  delegate :desc, ..., 
    to: :account_status,
    prefix: true
end

This will create a account_status_desc method which you can simply call from your serializer:

class AccountSerializer < ActiveModel::Serializer
  attributes(
    :foo, 
    :bar, 
    :baz,
    :account_status_code, # this is already a attribute of Account
    :account_status_desc
    # ...
  )
end

Another way of doing this is by simply adding methods to your serializer:

class AccountSerializer < ActiveModel::Serializer
  attributes :foo

  def foo
    object.bar.do_something
  end
end

This is a good alternative when the result of serialization does not align with the internal representation in the model.

Upvotes: 0

Lam Phan
Lam Phan

Reputation: 3811

you could replace belongs_to :account_status,... by below code

class AccountSerializer < ActiveModel::Serializer
  attributes(*Account.attribute_names.map(&:to_sym))
  
  AccountStatus.attribute_names.each do |attr_name|
    key = "account_status_#{attr_name}"
    define_method(key) do
     # i checked and see that `object.account_status` just call one time
     object.account_status.send(attr_name)
    end
    attribute key.to_sym   
  end
end

Upvotes: 0

Related Questions