Using Active Model Serializer
Tags: ruby, rails
Published: 2022-08-01 18:14:00

## Installing the Active Model Serializer gem

In order to use Active Model Serializer, you will need to include it in your Gemfile. Simply add `gem "active_model_serializers"` to your Gemfile and run `bundle install`. Alternatively, you can run `bundle add active_model_serializers` from the root directory of your app to accomplish the same thing.

## Serializer Basics

As part of my ongoing journey toward becoming a full-stack developer, I have begun developing Rails API. Rails has many great built-in libraries to handle everything from organizing and modifying databases to setting up routes and controller actions to handle requests to those routes. However, using controller actions to handle the _data_ sent back in response to those requests has its limitations. The controller's job is to process the requests sent by the client, which includes but is not limited to authentication, authorization, updating the database in response to the contents of the request body, and actually sending the response. Using the controller to sort through the available data and decide what to send back would grow your controller actions into large, unwieldy, and difficult-to-read blocks of code.

You may be wondering, who cares? Just send it all back and let the client sort it out! That's not too practical though. You may find yourself dealing with large databases with complex many-to-many relationships which require nested data to be sent to the client. Sending everything back in a large chunk for every request doesn't make much sense. Additionally, it may be possible that some of your controller actions require authorization, and if so there's a good chance there's also data that you only want available to authorized users. You wouldn't want, for example, `users#index` to send all of your users' email addresses and phone numbers! That may be fine for other controller actions (for a hypothetical `User` model) that require authorization, but not for one that's available for everyone.

This is where `ActiveModel::Serializer` comes in. The `acitve_model_serializer` gem allows you to move all that code into a separate file, which will be accessed by the controller either implicitly (if the serializer shares the name of the model that it is connected to) or if explicitly specified. The serializer can be easily created with a rails generator, like this: `rails g serializer <serializer_name>`. At its most basic, a serializer will include the table rows, as attributes, that are to be sent back in the response, something like this:

```
class UserSerializer < ActiveModel::Serializer
  attributes :id, :first_name, :last_name, :username, :bio, :avatar_url
end
```
This is telling the controller to _only_ include those attributes in the response. No sending the user's phone number to anyone who wants it! You can also define custom serializers. Let's say, using the above example, we wanted to send the user's full name as one string. we could write out a method that returns the full name, something like this:

```
def full_name
  "#{self.object.first_name} #{self.object.last_name}"
end
```
In this case, `self` is the instance of UserSerializer, and `object` is an attribute that allows you to access the current instance of `User` that you are dealing with. By accessing the user, you can in turn access the data for that user and incorporate it into a method that returns whatever information that you want to be returned. Now all you have to do is add `:full_name` to the list of attributes and it will be sent as part of the JSON data of the response.

What about the example we used before, with certain controller actions which, upon authorization, send data such as personal user information back to the client? The serializer we have here doesn't send that information, which is good, but we do want the user to have access to it once they prove it's them. Our `users#show` action, for example, may need to send the user their profile information, including information private to them. Our `create` and `update` actions, which allow the user to modify their profile, also need to return more detailed and personalized data.

To accomplish this, we can generate a new serializer and explicitly specify in our controller to use this new one. We'll start with generating the new serializer: `rails g serializer user_profile`. Now we can include the attributes we want to send, and specify in the controller to use this serializer, which might look something like this:

```
render json: user, serializer: UserProfileSerializer
```
In this specific case, it might actually make more sense for the custom serializer to be used for the `index` action, as that is the one that is apart from the others. In this case, we'd use `each_serializer`, like this:

```
render json: User.all, each_serializer: UserListSerializer
```
The most powerful use of serializers may be the ability to leverage Active Record associations to send nested data. In its most simple form, including associated data is as simple as sticking a `has_many` (or whatever the relationship is) macro in the serializer (assuming the relationship is set up correctly in your models). You can even specify a separate serializer for the nested data if, and this is often going to be the case, the data you need to send as nested data is not necessarily what you'd want to send in response to a request to the nested resource itself. For example, a book `has_many` many chapters. You may want to send some basic chapter information with the book (such as the names), but you don't necessarily need a comprehensive collection of information about the chapter, such as its word count, subject, etc, although you would want to send that in response to a request to `chapters` itself.

## Displaying Deeply Nested Data, My Way

You can also send data that's nested even deeper, but that's a bit complicated and probably should be avoided if possible. I've found that often when I've felt like I wanted to nest data more than one layer deep, there was usually a better way to do it. Particularly, combining the features of custom serializers and Active Record associations can often get the results you need without resorting to writing complex code to send back complex data. Let's look at an example from my most recent project, [game-center](https://github.com/nkulik94/game-center) (deployed [here](https://gamer-spot.herokuapp.com/)).

This app allows users to browse, like, rate, and review free online games (game information obtained via the Freetogame [API](https://www.freetogame.com/api-doc)). The `Review` model has some pretty complex associations. It `belongs_to` a `User` (which `has_many` reviews), `belongs_to` a `Game` (which `has_many` reviews, and `has_many` users through reviews), and `belongs_to` a `Rating`. I'd like to be able to display a detailed game, which would include a list of reviews, along with some information about the user. A review from this list as displayed in the browser may look something like this:

![Sample Review](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hpzza3r2t51djmdrd1v9.png)
You may see the potential problem here. We need, in addition to the actual review content, information about the user (name, username, avatar) and the rating associated with this review. This is on top of the fact that the review itself is being sent as a nested resource! This could get complicated and impractical quickly. Allow me to demonstrate how a bit of creativity can make this quite simple.

First, add `has_many :reviews` to the `GameSerializer`. This will send whichever attributes are specified in the `ReviewSerializer`, _not including_ nested attributes, as a nested attribute of the `Game`. Now we can use custom serializers and Active Record associations to customize our `Review` objects as needed. Let's start with the basic attributes:

```
class ReviewSerializer < ActiveModel::Serializer
  attributes :id, :content
end
```
Assuming our client doesn't need access to the `game_id` and `user_id`, this is really all that needs to be sent back from the `Review` model itself. We also want the rating, which, as we've mentioned before, is a separate resource that the review `belongs_to`. Instead of trying to work out complicated nested attributes, let's let our association do the work for us in the form of a custom method:

```
def rating
  self.object.rating.rating
end
```
Here we have `self.object`, the review itself, `.rating`, the instance of `Rating` associated with this review, and `.rating`, the attribute that has the actual value of the rating. In short, this method returns the value of the rating associated with this review, without resorting to sending nested and/or unnecessary data. Now we can just add `:rating` to our list of attributes and there we have it! Now let's do the same for the user information that we need:

```
def user_full_name
  "#{self.object.user.first_name} #{self.object.user.last_name}"
end

def user_username
  self.object.user.username
end

def user_avatar
  self.object.user.avatar_url
end

def user_tier
  self.object.user.tier
end
```
Let's see the whole serializer at once:

```
class ReviewSerializer < ActiveModel::Serializer
  attributes :id, :game_id, :user_id, :content, :rating, :user_full_name, :user_username, :user_avatar, :user_tier

  def rating
    self.object.rating.rating
  end

  def user_full_name
    "#{self.object.user.first_name} #{self.object.user.last_name}"
  end

  def user_username
    self.object.user.username
  end

  def user_avatar
    self.object.user.avatar_url
  end

  def user_tier
    self.object.user.tier
  end
end
```
Now, the json response data for a game should look something like this:

```
{
    "id": 19,
    "title": "Game Of Thrones Winter Is Coming",
    "thumbnail": "https://www.freetogame.com/g/340/thumbnail.jpg",
    "description": "Game of Thrones Winter is Coming is a free-to-play browser-based RTS based on the George R.R. Martin novels and popular HBO series. In the game developed by YOOZOOGames, player take on the role of a Westeros lord set on putting a stop to the wars between the Seven Kingdoms.\r\n\r\nThe game is built in Unity and offers players a balanced mix of strategy and RP. Players build bases, grow their kingdom, train armies, and recruit characters from the show, all while living within the story set forth in the TV series.",
    "game_url": "https://www.freetogame.com/open/game-of-thrones-winter-is-coming",
    "genre": "Strategy",
    "platform": "Web Browser",
    "publisher": "GTArcade",
    "developer": "YOOZOO Games ",
    "release_date": "2019-11-14",
    "likes": 4,
    "rating": "3.4",
    "reviews": [
        {
            "id": 602,
            "game_id": 19,
            "user_id": 6,
            "content": "Id quia ut. Quas a accusamus. Non iusto et.",
            "rating": 4,
            "user_full_name": "King Hamill",
            "user_username": "OldRasputinRussianImperialStout",
            "user_avatar": "https://robohash.org/eligendiquaevoluptas.png?size=300x300&set=set1",
            "user_tier": "Hall of Fame"
        },
        {
            "id": 1093,
            "game_id": 19,
            "user_id": 10,
            "content": "Qui ut doloribus. Sapiente ut odit. Nemo placeat sequi.",
            "rating": 3,
            "user_full_name": "Virgilio Larson",
            "user_username": "OrvalTrappistAle",
            "user_avatar": "https://robohash.org/ducimusrepudiandaeet.png?size=300x300&set=set1",
            "user_tier": "Hall of Fame"
        },
        {
            "id": 1197,
            "game_id": 19,
            "user_id": 11,
            "content": "Molestias veniam beatae. Ex alias ut. Mollitia quasi natus.",
            "rating": 5,
            "user_full_name": "Jackson Lamar",
            "user_username": "QB1",
            "user_avatar": "https://robohash.org/facereminimavoluptatem.png?size=300x300&set=set1",
            "user_tier": "Hall of Fame"
        }
    ]
}
```
There we go! Nice, simple, and organized!

I have found that serializers can be an extremely powerful tool when used correctly, and I hope that I've demonstrated that here. Thanks for sticking around!