Rails: Nesting Data with Custom Serializers using Active Model Serializers

Daniel Calvin
4 min readJan 26, 2022

--

https://hazelcast.com/glossary/serialization/

What is serialization?

The process of translating a data structure or object state into a format that can be stored or transmitted and reconstructed later. This data structure or object is usually converted to a JSON or XML format. For our purposes we will be serializing our ruby hash into JSON.

How active model serializer works

Active Model Serializers(AMS) uses the same mantra as Rails, convention over configuration. Which means Active Model Serializers follow the same naming conventions when it comes to class names and file names. When setting up your serializers, you will want to have the same singular name as your associated model. With this convention, the serializer that is associated will be used implicitly on serialization. For example, in the case of a chat application, there is a Chatroom model and the respective serializer name will be ChatroomSerializer. When it comes to serializing nested data, we will be breaking some conventions.

Getting started with active model serializers

If you’d like to follow along, go ahead and fork from the branch serializer-blog.

To get started:

#This application assumes that bundler, ruby, rails, and postgresql is installedRun these commands:bundle install
rails db:create db:migrate db:seed

Add Active Model Serializers:

bundle add active_model_serializers

Serializing with conventions

Before we get into the thick of it, let’s look at how active model serializer is used when following conventions by creating a serializer for our Chatroom.

Run the following command:

rails g serializer Chatroom

This command will generate the serializers directory for you if the directory did not exist. The command will also nest the generated serializer within the serializers directory.

Looking at our generated Serializer:

class ChatroomSerializer < ActiveModel::Serializer
attributes :id
end

Here rails has defined an attributes method for us. This attributes method allows us to specify the attributes we would like to be serialized in our response.

Defining our attributes:

Lets start up our rails server with:

rails s

We will be working the with the show and index routes:

Index:
http://127.0.0.1:3000/api/chatrooms
Show:
http://127.0.0.1:3000/api/chatrooms/:id

We will only use these routes to retrieve data. While the server is running, you can either retrieve this information through the browser or something like postman. As we request information with one of the two paths, you’ll notice that each request only contains an id attribute.

Response:

Show: { "id": 1 }Index: 
[
{ "id": 1 },
{ "id": 2 },
{ "id": 3 },
{ "id": 4 },
{ "id": 5 }
]

The reason for this is that we only specified the id attribute to be serialized with our response

Lets now add in some attributes:

class ChatroomSerializer < ActiveModel::Serializer
attributes :id, :name, :image_url, :bio
end

In our response, we will now see: id, name, image_url, and bio attributes. We can also include custom queries from our Model, custom methods from our serializer, and use association methods. The association method will only work if the respective model has defined the association macro.

Using Custom Serializers

From here on on out we will mostly be using our show route:

Show:
http://127.0.0.1:3000/api/chatrooms/:id

Lets generate our custom show serializer with some other serializers:

Run these commands:
rails g serializer ChatroomShow
rails g serializer Message
rails g serializer User
OR these to include the attributes as well:
rails g serializer ChatroomShow name
rails g serializer Message created_at content
rails g serializer User username image_url

Adding a custom serializer:

By breaking conventions we will need to specify our serializer within our show action in the chatrooms controller.

def show
chatroom = Chatroom.find(params[:id])
render json: chatroom, status: :ok,
serializer: ChatroomShowSerializer
end

If you wanted to specify the custom serializer on an array of data we can specify like so:

def index
chatrooms = Chatroom.all
render json: chatrooms, status: :ok,
each_serializer: ChatroomShowSerializer
end

Serializing nested data

Specify attributes and associations in each serializer:

ChatroomShowSerializer:

class ChatroomShowSerializer < ActiveModel::Serializer
attributes :id, :name
has_many :members
has_many :messages
end

MessageSerializer:

class MessageSerializer < ActiveModel::Serializer
attributes :id, :created_at, :content

belongs_to :user
end

UserSerializer

class UserSerializer < ActiveModel::Serializer
attributes :id, :username, :image_url
end

To note:

The members association will use UserSerializer because the source is user

class Chatroom < ApplicationRecordhas_many :chatroom_memberships, dependent: :destroy
has_many :members, through: :chatroom_memberships, source: :user
has_many :messages, dependent: :destroy
end

You can also specify a custom serializer for an association within our serializers like so:

belongs_to :user, serializer: UserSerializer

After requesting from the show route you will notice that the association belongs_to :user within the MessageSerializer is not being serialized. The reason for this is by default, Active Model Serializer only serialize the association one level deep. To get around this we need to include the attributes and their children in the controller.

Including nested associations:

We will need to change how our show route renders our serializer by including the attributes and their association.

def show
chatroom = Chatroom.find(params[:id])
render json: chatroom, status: :ok,
serializer: ChatroomShowSerializer,
include: ['messages', 'messages.user', 'members']
end

Thank you for reading,

If you would like to see how I implemented the serializers further within my chatting application, go ahead and head over to the main branch.

Resources

--

--