Cerealizing with Serializers
Tags: ruby, rails, beginners, tutorial
Published: 2021-12-17 23:24:32

If you are reading this, you might, like me, be relatively new to Ruby on Rails, and are still getting familiar with some of the features it has to offer. Personally, when I first heard about serializers, they seemed a bit redundant. We are already handling what we are rendering in our controller, so why add an extra layer? While it may seem like extra work in a small application, as you expand your database, you will start to realize that sorting through your data in the controllers becomes increasingly difficult.

For now, let's just take a look at a smaller application to understand the basics. Let's say we've built out the basics for a database for cereal companies, and we want to show those cereals to users buying them online (for now, we won't worry about creating users, we will just focus on the cereal side). Right now, we aren't using serializers, we just have a *controller* and a *model* for **Company**, **CEO**, **Charity**, and of course, **Cereal**. For our purposes, a **Company** `has_many` *CEOs*, *Charities* and *Cereals*. 

![Model Wireframe](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fsk78szla7qqk1yo14bv.png)

We just have some simple models for now:

```ruby
class Company < ApplicationRecord
  has_many :ceos
  has_many :charities
  has_many :cereals
end

class Ceo < ApplicationRecord
  belongs_to :company
end

class Charity < ApplicationRecord
  belongs_to :company
end

class Cereal < ApplicationRecord
  belongs_to :company
end
```

Alright, now let's take a look at our `routes.rb`.

```ruby
Rails.application.routes.draw do
  resources :cereals
  resources :companies
  resources :charities
  resources :ceos
end
```
Using `resources` will automatically include all [RESTful routes](https://medium.com/podiihq/understanding-rails-routes-and-restful-design-a192d64cbbb5), which means we can define `index` and `show` in our cereal controller. (You should typically only include routes that you intend on using, but for now, we'll leave it as is)

```ruby
class CerealsController < ApplicationController

    def index
        cereals = Cereal.all
        render json: cereals
    end

    def show
        cereal = Cereal.find(params[:id])
        render json: cereal
    end

end
```
**Note:** using a .find in the show route on its own is not best practice, because if an incorrect id is provided, there will be no rescue from the error. If you would like to know more about handling errors, check out the [Rails documentation](https://guides.rubyonrails.org/active_record_validations.html) for info on error handling and validations. Since we are only focused on setting up serializers, we'll skip this for now.

Ok, so as it stands right now, let's take a look at what returns to us on a GET request to '/cereals'.

```
[
    {
        "id": 1,
        "name": "Cheerios",
        "company_id": 1,
        "price": 3.49,
        "ingredients": "*list of ingredients*",
        "time_to_make": 1.3,
        "transportation_schedule": "*list of schedule details*",
        "expiration_date": "Jan 12, 2022",
        "warehouse_location": "Cleveland",
        "created_at": "2021-12-17T17:55:35.618Z",
        "updated_at": "2021-12-17T17:55:35.618Z"
    },
    {
        "id": 2,
        "name": "Chex",
        "company_id": 1,
        "price": 3.99,
        "ingredients": "*list of ingredients*",
        "time_to_make": 0.9,
        "transportation_schedule": "*list of schedule details*",
        "expiration_date": "Jan 10, 2022",
        "warehouse_location": "Des Moines",
        "created_at": "2021-12-17T17:55:35.633Z",
        "updated_at": "2021-12-17T17:55:35.633Z"
    },
    {
        "id": 3,
        "name": "Lucky Charms",
        "company_id": 1,
        "price": 4.49,
        "ingredients": "*list of ingredients*",
        "time_to_make": 1.5,
        "transportation_schedule": "*list of schedule details*",
        "expiration_date": "Jan 02, 2022",
        "warehouse_location": "Minneapolis",
        "created_at": "2021-12-17T17:55:35.648Z",
        "updated_at": "2021-12-17T17:55:35.648Z"
    }
]
```

It works! But that is a lot of data that we don't necessarily need. If the request is being made by a user that is hoping to buy a box of cereal, there is definitely some data that can be left out. When you are buying cereal, you don't need to know the id of that particular box of cereal, how long it took to make, the transportation schedule, the warehouse it is stored in, or when it was added or updated to the database. What we could do is specify what parts of the hash we want rendered in our controller, but we would have to repeat that process on any and every request we make as we continue to build out the application. What we can do instead is use *serializers*. To get started, we have to install the serializers gem.

`bundle add active_model_serializers`

This will add the gem to your gemfile and install it at the same time. Now we can create a serializer for cereal with `rails g serializer cereal`. You will see that this automatically create a serializers folder in your application and add a cereal_serializer to it. I know what you may be thinking, "Generating serializers for all of my models sounds exhausting and time consuming!" And if you do it the way we just did, you'd be right. But, a bonus to the serializer gem is that if you install it at the beginning of your application, before creating any of your migrations, models, or controllers, running `rails g resource cereal` will *also create a serializer* so that you don't have to. By default, it will even include any relationships and attributes that you added to your generation. Since we didn't do that this time around, this is what our serializer looks like right now:

```ruby
class CerealSerializer < ActiveModel::Serializer
  attributes :id
end
```
Because it only includes the attribute `:id`, the only thing that will be rendered it the cereal id. Taking a look at our GET request again, we see: 

```
[
    {
        "id": 1
    },
    {
        "id": 2
    },
    {
        "id": 3
    }
]
```
Pretty cool, but still not the information we want to show our buyers. Looking at all of the data from the previous request, the realistic attributes we might want to include are name, company_id, price, ingredients, and expiration date. So let's do that.

```ruby
class CerealSerializer < ActiveModel::Serializer
  attributes :name, :company_id, :price, :ingredients,
:expiration_date
end
```

And see the new return results:

```
[
    {
        "name": "Cheerios",
        "company_id": 1,
        "price": 3.49,
        "ingredients": "*list of ingredients*",
        "expiration_date": "Jan 12, 2022"
    },
    {
        "name": "Chex",
        "company_id": 1,
        "price": 3.99,
        "ingredients": "*list of ingredients*",
        "expiration_date": "Jan 10, 2022"
    },
    {
        "name": "Lucky Charms",
        "company_id": 1,
        "price": 4.49,
        "ingredients": "*list of ingredients*",
        "expiration_date": "Jan 02, 2022"
    }
]
```

Sweet! Now, without changing anything in our controller, let's look at the results of a Get request to a specific cereal id, triggering the `show` route.

```
{
    "name": "Cheerios",
    "company_id": 1,
    "price": 3.49,
    "ingredients": "*list of ingredients*",
    "expiration_date": "Jan 12, 2022"
}
```

As you can see, the specified attributes of the cereal serializer are being used in both routes from the cereal controller. This way, we can specify what attributes we want in a scope that will affect all of our routes.

But what about that `company_id` that we are getting? That won't be very useful to a buyer. We want to be able to see the actual company name, not just the id. 

####Serializer Associations
Serializers have the ability to show associations through `has_many` and `belongs_to`, just like in our models. You may notice that if you generate a serializer using `resource`, a model that belongs to another will have a serializer with a `has_one` association instead of `belongs_to`. These operate the same, but I find it easier and more consistent to use `belongs_to`. Let's update our cereal serializer with a `belongs_to` and see what we get. We also remove the company_id attribute, since we won't be needing that. 

```ruby
class CerealSerializer < ActiveModel::Serializer
  attributes :name, :company_id, :price, :ingredients, :expiration_date
  belongs_to :company
end
```
On a show route, this will return:

```
{
    "name": "Cheerios",
    "price": 3.49,
    "ingredients": "*list of ingredients*",
    "expiration_date": "Jan 12, 2022",
    "company": {
        "id": 1,
        "name": "General Mills",
        "employees": 40000,
        "healthcare_plan": "*plan description*",
        "revenue": 17000000000.0,
        "created_at": "2021-12-17T19:05:02.950Z",
        "updated_at": "2021-12-17T19:05:02.950Z"
    }
}
```
Great! Now we have the company details from that association. But again, we're getting information that we don't need. There's a few ways we could handle this now. 

The first way we can handle it is to create serializers for our other classes, and use them through our `belongs_to` association. Let's create a serializer for company with `rails g serializer company`, and edit the attributes it shows.

```ruby
class CompanySerializer < ActiveModel::Serializer
  attributes :name
end
```
Here's the result of requesting now:
```
{
    "name": "Cheerios",
    "price": 3.49,
    "ingredients": "*list of ingredients*",
    "expiration_date": "Jan 12, 2022",
    "company": {
        "name": "General Mills"
    }
}
```
Sweet! We now have the company name associated with our cereal. There are a few problems with this solution, however. For one, as you can see, in order to access that company name, we have to read down two levels to the nested data. Not too ideal. Another is that this serializer will be filtering all of the company requests as well. So, a GET request to '/companies' will only give us: 

```
[
    {
        "name": "General Mills"
    },
    {
        "name": "Kellogg's"
    },
    {
        "name": "Quaker Oats"
    },
]
```
If all we are using this database for is to show the company names, this is fine, but we may want to be able to access different attributes of those parent models.

####Serializer Custom Methods
The second solution fixes some of those problems. We can create custom serializer methods and include them in the attributes. First we'll want to remove the `belongs_to :company` from our cereal serializer.

```ruby
class CerealSerializer < ActiveModel::Serializer
  attributes :name, :price, :ingredients, :expiration_date, 
:company_name


  def company_name
    object.company.name
  end
end
```

We can define methods *within* our serializers to perform data handling, then all we need to do is add that method name to our attributes list. `object` is a moniker for the object that is being passed to the serializer, similar to `self` in class models. Here's what we get on this request:

```
{
    "name": "Cheerios",
    "price": 3.49,
    "ingredients": "*list of ingredients*",
    "expiration_date": "Jan 12, 2022",
    "company_name": "General Mills"
}
```

Much cleaner! Using *custom methods* is a pretty slick way to include associated data when we only want certain attributes of that data. Now, as you can see, the company name is on the same level as all of our other data, so no need to dig into nested data.

To show another example of *custom methods*, I am now going to use the **CEO** and **Charity** models that were defined earlier, but never used. Let's say a company wanted to include some details of a charitable cause they are backing and a motto from their fearless CEO on the box they are selling. Obviously, in real life, these would just be mass-printed on the boxes during packing, but since we are working in a digital world, we need to find a way to render that information. Since both are children of **Company**, I will just be using `Ceo.first` and `Charity.first` in reference to each instance I want to use. 

```ruby
class CerealSerializer < ActiveModel::Serializer
attributes :name, :price, :ingredients, :expiration_date,
:company_name, :ceo_name, :ceo_motto, :charity_name,
:charity_cause


  def company_name
    object.company.name
  end

  def ceo_name
    object.company.ceos.first.name
  end

  def ceo_motto
    object.company.ceos.first.motto
  end

  def charity_name
    object.company.charities.first.name
  end

  def charity_cause
    object.company.charities.first.cause
  end

end
```
And here's our render:
```
{
    "name": "Cheerios",
    "price": 3.49,
    "ingredients": "*list of ingredients*",
    "expiration_date": "Jan 12, 2022",
    "company_name": "General Mills",
    "ceo_name": "Jeff Harmening",
    "ceo_motto": "Cereal is our passion.",
    "charity_name": "General Mills Foundation",
    "charity_cause": "Advancing regenerative agriculture"
}
```
The way in which you go about receiving data will differ based on how your database is set up and nested, but hopefully this gives you an idea of how to start that process.

####Custom Serializers
"But wait," you might be thinking, "what if I don't want to show ALL of that data on all of my cereal requests?" Well, there is a simple solution to that problem: **custom serializers**. As of right now, if we were to make a GET request to all of our cereals, the `index` route would return this:

```
[
    {
        "name": "Cheerios",
        "price": 3.49,
        "ingredients": "*list of ingredients*",
        "expiration_date": "Jan 12, 2022",
        "company_name": "General Mills",
        "ceo_name": "Jeff Harmening",
        "ceo_motto": "Cereal is our passion.",
        "charity_name": "General Mills Foundation",
        "charity_cause": "Advancing regenerative agriculture"
    },
    {
        "name": "Chex",
        "price": 3.99,
        "ingredients": "*list of ingredients*",
        "expiration_date": "Jan 10, 2022",
        "company_name": "General Mills",
        "ceo_name": "Jeff Harmening",
        "ceo_motto": "Cereal is our passion.",
        "charity_name": "General Mills Foundation",
        "charity_cause": "Advancing regenerative agriculture"
    },
    {
        "name": "Lucky Charms",
        "price": 4.49,
        "ingredients": "*list of ingredients*",
        "expiration_date": "Jan 02, 2022",
        "company_name": "General Mills",
        "ceo_name": "Jeff Harmening",
        "ceo_motto": "Cereal is our passion.",
        "charity_name": "General Mills Foundation",
        "charity_cause": "Advancing regenerative agriculture"
    }
]
...
```
That's a lot of data. If a user is browsing through cereal options, they do not necessarily need to see all of that information right away. If we wanted to change the data being shown in this request to only return the cereal name, the first thing we might think to do is change our serializer. But then, our `show` route would be missing all of that data we just worked so hard to get. What we can do is generate a new serializer. Let's call it `CustomCerealSerializer`. To do so, we can run `rails g serializer custom_cereal`. Now, let's just copy and paste all of our attributes and custom methods into the new serializer: 

```ruby
class CustomCerealSerializer < ActiveModel::Serializer
  attributes :name, :price, :ingredients, :expiration_date,
:company_name, :ceo_name, :ceo_motto, :charity_name, 
:charity_cause

  def company_name
    object.company.name
  end

  def ceo_name
    object.company.ceos.first.name
  end

  def ceo_motto
    object.company.ceos.first.motto
  end

  def charity_name
    object.company.charities.first.name
  end

  def charity_cause
    object.company.charities.first.cause
  end
end
```
And set our original serializer to only include name:

```ruby
class CerealSerializer < ActiveModel::Serializer
  attributes :name
end
```

Now, let's see what the `index` request will return:

```
[
    {
        "name": "Cheerios"
    },
    {
        "name": "Chex"
    },
    {
        "name": "Lucky Charms"
    }
]
```
That looks like exactly what we wanted. But what happened to our other serializer? Right now, if we run a `show` request, we will also only recieve the name. What we can do is specify what serializer we want to use in our `cereals_controller`. 

```ruby
class CerealsController < ApplicationController

    def index
        cereals = Cereal.all
        render json: cereals, status: :ok
    end

    def show
        cereal = Cereal.find(params[:id])
        render json: cereal, serializer: CustomCerealSerializer, 
status: :ok
    end

end
```
Because the `index` route does not have a specified serializer, it will default to the CerealSerializer, which as we just saw, only has the attribute of name. By specifying our **custom serializer** on the `show` route, our return will render those attributes and methods that we defined within it:

```
{
    "name": "Cheerios",
    "price": 3.49,
    "ingredients": "*list of ingredients*",
    "expiration_date": "Jan 12, 2022",
    "company_name": "General Mills",
    "ceo_name": "Jeff Harmening",
    "ceo_motto": "Cereal is our passion.",
    "charity_name": "General Mills Foundation",
    "charity_cause": "Advancing regenerative agriculture"
}
```

**Custom serializers** and **custom serializer methods** are extremely useful in specifying what data you want to be returned, and when. Using *has_many* and *belongs_to* associations within your serializers can also be very useful for chaining serializers together. Just be sure that all of the data from the serializer being called in that association is pertinent to that render. You should ideally never have to change one model's serializer to adjust the data being returned in an association from another. If you ever find yourself appending loads of excludes or includes to the end of a return to get the data you want, creating a new custom serializer may not be a bad idea.

**Important note:** Throughout the examples above, we never specified the cereal_id in our `attributes`. This was mainly to show the concept of including only information that would be useful to a user. However, in actual practice, you should typically include the id in your attributes, like so: 
```ruby
class SomeSerializer < ActiveModel::Serializer
   attributes :id, :name, #etc...
end
```
Although a user might never need to see the cereal_id, in order to act upon that cereal instance, we need it's id, so including it in your attributes is always a good idea.


####Resources:
[RESTful routes](https://medium.com/podiihq/understanding-rails-routes-and-restful-design-a192d64cbbb5)
[Rails Error Handling](https://guides.rubyonrails.org/active_record_validations.html)
[Active Model Serializers GIT](https://github.com/rails-api/active_model_serializers)
[Rails Serialization Docs](https://api.rubyonrails.org/classes/ActiveModel/Serialization.html)
[Serializer Class](https://www.rubydoc.info/gems/active_model_serializers/0.8.2/ActiveModel/Serializer)