How to Serialize Your Rails Backend
Using ActiveModel::Serializer
What is serialization?
The main idea: the process of organizing, or re-organizing, data to suit your needs
Why?
- It will make your life easier when building the frontend
- It keeps your code DRY! (Don’t Repeat Yourself) Instead of writing the same code 5 different times in each Controller action, write it once in your Serializer
- Reduces the number of fetch requests needed
- Allows you to nest resources more easily
How?
One of my favorite aspects of programming is that there are infinite ways to accomplish any goal. With that in mind, I’m going to walk through how I used ActiveModel::Serializer
to organize data for my Rails backend.
Before we get started, take a few moments to think about these four models and their relationships.
1. Create your app from the terminal.
rails new my-app-backend --api --database=postgresql
I am using a Postgres database so I can later deploy my application on Heroku. While Postgres is not required here, --api
is. This tells Rails that your ApplicationController
should inherit from ActionController::API
instead of the usual ActionController::Base
. It also lets Rails know you don’t need to create Views, just Models and Controllers.
Note: when using Postgres, you will have to open the Postgres app, ensure a server is running, then run rails db:create
in your terminal before you can run any migrations. Once the Postgres server is running, it will display any tables previously created. As long as you leave the Postgres server running in the background, you can now minimize its window. No further actions will be required.
2. Add ActiveModel::Serializer
to your Gemfile and run bundle install
in your terminal.
gem 'active_model_serializers', '~> 0.10.0'
3. Create models, controllers, routes, and serializers all at once by running rails g resource
in your terminal:
rails g resource User email first_name last_name avatar
This will create the following files:
app/models/user.rb
app/controllers/users_controller.rb
app/serializers/user_serializer.rb
db/migrate/<timestamp>_create_users.rb
as well asresources :users
in config/routes.rb
Create resources for the rest of your models:
rails g resource Project title subtitle user_id:integerrails g resource Prompt content img prompt_type project_id:integerrails g resource Answer prompt_id:integer content correct:boolean
After checking that your schemas are correct, run rails:db:migrate
and add relationships to your models
4. Seed your database with some fake data! I created 1 user with 2 projects. Each project had 10 prompts. Each prompt had 4 answers (1 correct, 3 incorrect).
5. Set up Controllers. To ensure I could play around with my data structure from any route, I wrote index
and show
actions for all of my models. I won’t actually need both for every model, but I can go back and delete them later. In the planning stages, it’s better to have something and not need it, than to need it and not have it.
Start up your server!
Here is a screenshot of each index page currently:
This is great, and definitely better than the structure we get without serializers, which would include all of our timestamp
columns.
However, this will still require us to make 4 seperate fetches just to get all the data related to a single project.
6. Decide how you want your data to be structured. This is the most difficult part of the process. Think about when you will make your fetches from your front end. What models should be grouped together to reduce the number of fetches needed?
To start I added relationships to my Serializers. ActiveModel::Serializer
supports has_many
and belongs_to
relationships.
Like magic, we can now see all of our related data nested in each index!
7. Make it your own! For this specific app, I want my data to fit a very specific structure. Let’s go through our User
model.
I want to be able to see a user’s projects, but currently it is showing me the user_id
within each project, even though it’s nested within a user
object. Adding relationships to the Serializers
results in Rails calling on the ProjectSerializer
from within the UserSerializer
. To avoid this default behavior, I’m going to comment out my relationships for now and write custom attributes. Just like any Ruby class, we can write instance methods
! I’m going to write one called projects
. I can then add :projects
as an attribute of my UserSerializer
.
When I go to a User
show page now, I should see a key of projects
with an array of that user’s projects. Each Project
should only have keys for its’ id, title,
and subtitle
.
These instance methods
can be written to show anything, not just related models.
For example, let’s add something random to our UserSerializer
.
So, make your API your own! Happy Coding!
If you would like to see how I chose to serialize the rest of my data, check out the screenshots below.