Rails Serializers & Some Tricks
Tags: ruby, rails, serializers, ams
Published: 2020-06-12 19:26:12

A recent project of mine (React frontend, Rails backend) needed some extra spice when it came to serialization, so I thought I'd put together a quick post on the serializers that are out there and a couple nice features to make your life easier. 

**NOTE:** Increasingly, it seems like a folks are avoiding the entire subject by using GraphQL. That's a whole other can of worms though, so this post is just meant to address REST frameworks.

##What Are Serializers?##
The backend is responsible for storing all of our app's data, and (it goes without saying) one of the jobs that goes along with that responsibility is making that data accessible to the frontend. The importance of this job, past the obvious, is that we can make our lives a lot easier by sending the data in a format that requires the frontend to do very little work to use or store that data. This is where serializers come in. 

Serializers take our database records then distill and format them for consumption by the frontend. A simple example: if our database has a user with 25 fields, and on login we want to set the username, avatar, and a short bio in state, wouldn't it be nicer if our frontend just received those three fields in the proper format rather than having to pick them out of the 25? Plus think of how much less data would need to be sent back and forth.

##The Serializers:##
[active_model_serializers](https://github.com/rails-api/active_model_serializers/tree/v0.10.6): Far and away the most popular (33mm total DLs). Out of the box this one is probably the most user friendly--there's not much setup, and the basic syntax is straightforward. Not the fastest out there if that's something you're in the market for.

[fast_jsonapi](https://github.com/Netflix/fast_jsonapi/): Released by Netflix back in 2018, this is the second most popular option (while total downloads are far lower than AMS, the most recent version DLs are 2.1mm vs. 2.35 for AMS recent version). As you can imagine, Netflix is interested in SPEED and they claim 25x faster than AMS. Surprisingly, the syntax on this one is pretty comparable to AMS for basics.

[jsonapi_rb](http://jsonapi-rb.org/): The only other option that seems to come up, but there haven't been any new versions since 2017 and the docs aren't great so not worth spending time on.

**Note:** The remainder of this post will describe features using AMS syntax since it's the most used. These features also exist in FJA, so if you're using that instead you just check the docs at the link above.

##Inheritance##
Let's say you have a User model and 7/10 times you only want half the user fields: id, username, avatar, short_bio. Those other three times you want all the fields and the User's pets. Since we specify which serializer to use in the controller's render, we can easily create a second serializer to handle this, but it's kind of annoying to duplicate the same fields. Since, after all, serializers are just a regular Ruby class that inherits from something, we can solve this with inheritance.

```
class UserSerializer < ActiveModel::Serializer
  attributes :id, :username, :avatar, :short_bio
end

class ExpandedUserSerializer < UserSerializer
  attributes :location, :pronouns, :age, :favorite_food
  has_many :pets
end
```

The net effect is that our expanded serializer will output this:

```
  attributes :id, :username, :avatar, :short_bio, :location, :pronouns, :age, :favorite_food
  has_many :pets
```

###Transforming Keys###
One of the little annoyances of using Rails & React is that in Rails we tend to snake case fields while in React we camel case them. One nice small feature of serializers is handling the conversion from snake to camel. It's a small trick, but it really sets my OCD tendencies at ease.

```
render json: {user: UserSerializer.new(@user, key_transform: :camel)}
```

##Parameters##
Now let's imagine that those 'Pets' that belong to the user also have many-many relationship with the model 'Species'. If we want to get a list of the User's pet's of one species we can't just call `SpeciesController has_many :pets` because there are a lot of other pets of that Species that don't belong to that user. What we need to do is pass the user's ID as a parameter to the Species controller. We'll do that using the @instance_options method as well as .


Model:
```
class Species < ApplicationRecord
   has_many :pet_species
   has_many :pets, through: :pet_species
end
```

Serializer:
```
class MovieSerializer < ActiveModel::Serializer
   attributes :id, :name, :user_pets

   def user_pets
      User.find(@instance_options[:user_id]).pets & self.object.pets
   end
end
```

Controller:
```
class SpeciesController < ApplicationController

   def show
      @user_id = species_params[:user_id]
      @species = Species.find(species_params[:id])
      if @user_id && @species
         render json: {species: SpeciesSerializer.new(@species, user_id: @user_id)}, status: 200
      end
   end
end
```

These are just a few of the tricks that I've used to make my working with API data in my frontend easier. If you know other ways to achieve the same results, or have other tricks you love drop them in the comments--I always welcome the opportunity to learn more! Happy coding!