Using Custom Ruby Methods in Rails Serializer

Sean LaFlam
The Startup
Published in
4 min readMar 4, 2021

Recently I was working on an app thats purpose was to log a bunch of different users git activity for a certain development server.

The backend of this project was a Rails API with multiple endpoints. Each endpoint required the JSON object returned to be formatted in a very specific way, and required some custom behavior thats not inherently included in the RESTful API flow.

I figured this would be the perfect opportunity for a crash course on how to use Serializers in Ruby, and how to write your own custom methods within them to return whatever you want.

What is a Serializer?

In the Computer Science sense, serialization is the process of turning an object in memory into a stream of bytes so you can do stuff like store it on disk or send it over the network.

For our purpose, serialization just refers to the ability to format the shape and content of the data returned by our Rails API. So how do we do it?

First, we need to add the Active Model Serializer gem to our Gemfile

gem 'active_model_serializers'

After this is pasted into your Gemfile run ‘bundle install’ to install it

Now we have the ability to create new Serializers using:

rails generate serializer <model_name>

This command will follow the same format as ‘rails generate model <model_name>’ where the model name will be singular. It should match the name of the model for which you want to configure the the JSON data, as well as the controller for that model.

(ex - ‘rails g serializer event’ would create EventSerializer for the corresponding Event Model and EventsController)

This command will create the Serializer and place it in a new folder located at app/serializers. Lets take a peek at what it will look like:

class EventSerializer < ActiveModel::Serializer  attributes :idend

Any symbol that comes after attributes will be displayed to our user when they make a request to the corresponding route. For example, let’s say our Event class has the following properties:

create_table "events", force: :cascade do |t|  t.string "type"  t.datetime "created_at"  t.datetime "updated_at"  t.integer "actor_id"  t.integer "repo_id"end

We may want to expose the “id”, “type” and “created_at” information, but hide the rest. In that case we would add those columns to our attributes.

class EventSerializer < ActiveModel::Serializer  attributes :id, :type, :created_atend

Now when we run our server and navigate to ‘http://localhost:3000/events’ we can see our data looks like this:

The actor_id, repo_id, and updated_at info are all hidden

Exposing nested info

Now the real beauty of Serializers is that we can expose information for other related models all in the same nested object. This lets us reduce the number of endpoints we need to make calls to by letting us fetch a bunch of different related info all in one request.

Sticking with the same example, let’s say we no longer want to hide the actor and repo information. In fact we want to expose information about the repo and actor each event belongs to. Well, that couldn’t be simpler! We just need to add actor and repo to the attributes in our Event Serializer:

class EventSerializer < ActiveModel::Serializer  attributes :id, :type, :created_at, :repo, :actorend
And just like the our object contains nested hashes for each repo and actor associated with a specific event

Custom Methods

We haven’t even gotten into the coolest part of Serializers yet. The way the attributes callout works is that it treats each symbol listed after it as a method that it then calls on each instance of that class.

For example, in our Event Serializer from the last section, it literally is calling event.id, event.type. event.created_at , etc. and then displaying the return value of each method in JSON format.

So if thats all it’s doing, you won’t be surprised to learn we can add our own method definitions to the Serializer and, and it will call those as well.

In the last example you may have noticed that our created_at date contains a time zone and goes all the way down to milliseconds. In my application I didn’t need that level of detail displayed to my user, but I still wanted it to exist in my database. I wanted the JSON to include the day, month, and year of each event.

The simplest way to do this is to create a custom definition, also called created_at, that the Serializer will call BEFORE it uses the created_at attr_accessor method.

class EventSerializer < ActiveModel::Serializerattributes :id, :type, :actor, :repo, :created_atdef created_at
object.created_at.strftime("%Y-%m-%d ")
end

Now when our EventSerializer gets to the created_at attribute, it will call our created_at method on the event instance. As a result, our data now looks like this:

Just simple Year-Month-Date format

When the attribute doesn’t find a property with a matching name, it will first look and see if there is a matching method in the serializer, and then if there is a matching method in the model it can call.

Property -> Serializer -> Model

If there isn’t a matching property or method in any of these places it will throw an error.

And that’s going to do it for our crash course in Serializers today. Remember whatever you put after the attributes is what will get shown to our user.

--

--