Using Active Model Serializers
In an API, serialization describes the process of specifying which attributes of a record are returned in a JSON response.
For example, let’s consider a simplified version of the API used for the Bird Watcher web app. The backend is made up of multiple tables, we’ll take a look at three: users, birds, and posts. Let‘s set it up:
Setting Up the Simple API
Step 1: Build a new API using Rails
$ rails new bird_watcher — apiStep 2: Create resources using a Rails generator
$ rails g resource bird name description image_url
$ rails g resource user username password_digest
$ rails g resource post bird:belongs_to user:belongs_to captionUsing the Rails’ resource generator is convienient because it creates migration, model, and controller files for each resource.
Step 3: Migrate
$ rails db:migrateTo create our database, we’ll need to migrate our files to update our schema. After migrating, our schema should look something like this:
ActiveRecord::Schema.define(version: 2022_12_31_001459) do
enable_extension "plpgsql"
create_table "birds", force: :cascade do |t|
t.string "name"
t.string "description"
t.string "image_url"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "posts", force: :cascade do |t|
t.bigint "user_id", null: false
t.bigint "bird_id", null: false
t.string "caption"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "image_url"
t.index ["bird_id"], name: "index_posts_on_bird_id"
t.index ["user_id"], name: "index_posts_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "username"
t.string "password_digest"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.boolean "isAdmin"
end
add_foreign_key "posts", "birds"
add_foreign_key "posts", "users"
endStep 4: Seed data
Within the db/seed.rb file, we’ll create our first user, bird, and post:
User.create(
username: ‘Sasha’,
password_digest: ‘123’
)
Bird.create(
name: 'Pigeon',
description: 'Street bird native to NYC',
image_url: 'https://images.unsplash.com/photo-1549586073-f4c3b7ff044a?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8M3x8cGlnZW9ufGVufDB8fDB8fA%3D%3D&auto=format&fit=crop&w=500&q=60'
)
Post.create(
user: User.find_by_username('Sasha'),
bird: Bird.find_by_name('Pigeon'),
caption: 'These birds are taking over the city!!'
)Then seed the file:
$ rails db:seedStep 5: The resources generator automatically creates routes for each of the resources it creates, but it is best practice to only add necessary routes. For this example we’ll only create one route. Let’s update our config/routes.rb file to look like this:
#config/route.rb
Rails.application.routes.draw do
resources :posts, only: :index
endStep 6: The resource generator has already added the belongs_to macro for :bird and :user in the Post model. We’ll need to manually insert the has_many association macro in the Bird and User models:
#models/bird.rb
class Bird < ApplicationRecord
has_many :posts
end#models/user.rb
class User < ApplicationRecord
has_many :posts
end#models/post.rb
class Post < ApplicationRecord
belongs_to :bird
belongs_to :user
endStep 7: Now we can set up our controllers. Let’s setup the index controller action in our Posts controller:
#controllers/birds_controller.rb
class PostsController < ApplicationController
def index
render json: Post.all
end
endSerialization within the controller
Now that our simple API is set up, let’s take a look at the json that is returned when we make a GET request to localhost://3000/posts. First, you can start the server by running:
$ rails serverThe request returns:
[
{
“id”: 1,
“bird_id”: 1,
“user_id”: 1,
“caption”: “These birds are taking over the city!!”,
“created_at”: “2022–12–30T21:32:46.395Z”,
“updated_at”: “2022–12–30T21:32:46.395Z”
}
]When we set up our controller action to return data, we may not always necessarily need all of the data stored for the record. For instance, our front end doesn’t have any use for the data from the created_at and updated_at attributes. Luckily that’s what serialization is for. Let’s update our Posts Controller to return all of the data except the created_at and updated_at attributes:
class PostsController < ApplicationController
def index
render json: Post.all, except: [:created_at, :updated_at]
end
endNow, the response looks like the following:
[
{
“id”: 1,
“bird_id”: 1,
“user_id”: 1,
“caption”: “These birds are taking over the city!!”
}
]But what if we want to also return associated data? We know that a post belongs to a user and also belongs to a bird. Let’s update the Posts Controller again:
class PostsController < ApplicationController
def index
render json: Post.all, except: [:created_at, :updated_at], include: [:user, :bird]
end
endWhich returns:
[
{
“id”: 1,
“bird_id”: 1,
“user_id”: 1,
“caption”: “These birds are taking over the city!!”,
“user”: {
“id”: 1,
“username”: “Sasha”,
“password_digest”: “123”,
“created_at”: “2022–12–30T21:32:46.359Z”,
“updated_at”: “2022–12–30T21:32:46.359Z”
},
“bird”: {
“id”: 1,
“name”: “Pigeon”,
“description”: “Street bird native to NYC”,
“image_url”: “https://images.unsplash.com/photo-1549586073-f4c3b7ff044a?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8M3x8cGlnZW9ufGVufDB8fDB8fA%3D%3D&auto=format&fit=crop&w=500&q=60",
“created_at”: “2022–12–30T21:32:46.377Z”,
“updated_at”: “2022–12–30T21:32:46.377Z”
}
}
]That works, but now we have all of these attributes for bird and user that we don’t need. Specifically, the user’s password_digest, create_at, and updated_at. So let’s update the controller one more time:
class PostsController < ApplicationController
def index
render json: Post.all, except: [:created_at, :updated_at], include: [ user: {only: [:id, :username ]},
bird: {only: [:id, :name, :image_url, :description]}]
end
endAs you can see this can complicate the readability of the controller. Luckily, there is a better way to dictate which attributes are returned without congesting the bird controller.
Serializing with Active Model Serializers
Active model serializers make this process so much easier and cleaner. AMS is a Ruby gem that enables us to customize the json that is sent in a response. It allows us to pick or exclude what we want returned while keeping the code in the controller focused on its core functionality. To use it, we’ll have to add the gem to our Gemfile:
#Gemfile
gem “active_model_serializers”, “~> 0.10.12”Then we’ll run this code to import it:
$ bundle installBefore moving forward, let’s reset our Posts Controller:
#controllers/birds_controller.rb
class PostsController < ApplicationController
def index
render json: Post.all
end
endNext, we’ll have to create a serializer for our Post resource. We can do this using a Rails generator:
$ rails g serializer postThis generator creates a serializer folder, if none exist, then it creates a post_serializer.rb file inside of it:
How does the Active Model Serializer work? It intercepts the json being returned in the response and filters it based on which attributes you include in the serializer file. To see how this works, first we’ll have to reset our server. To end the server: ‘ctrl + c’ and then $ rails server to start it up again.
Get Luis Vasquez’s stories in your inbox
Join Medium for free to get updates from this writer.
Now, let’s make our GET request to http://localhost:3000/posts. Here is what is returned:
[
{
“id”: 1
}
]Hmmm… What went wrong? Remember, serializers only return the attributes listed in the serializer file. So let’s look at the serializers/post_serializer.rb file:
class PostSerializer < ActiveModel::Serializer
attributes :id
endSince we only have :id listed as an attribute, it makes sense why only the id attribute is shown in the JSON. Let’s add the attributes we want to see in the JSON to the post_serializer.rb file:
class PostSerializer < ActiveModel::Serializer
attributes :id, :caption, :user, :bird
endWhich now yields:
{
“id”: 1,
“caption”: “These birds are taking over the city!!”,
“user”: {
“id”: 1,
“username”: “Sasha”,
“password_digest”: “123”,
“created_at”: “2022–12–30T21:32:46.359Z”,
“updated_at”: “2022–12–30T21:32:46.359Z”
},
“bird”: {
“id”: 1,
“name”: “Pigeon”,
“description”: “Street bird native to NYC”,
“image_url”: “https://images.unsplash.com/photo-1549586073-f4c3b7ff044a?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8M3x8cGlnZW9ufGVufDB8fDB8fA%3D%3D&auto=format&fit=crop&w=500&q=60",
“created_at”: “2022–12–30T21:32:46.377Z”,
“updated_at”: “2022–12–30T21:32:46.377Z”
}
}We’re getting there… But how do we get rid of the unnecessary attributes from the user and bird attribute? We’ll have to remove them from the attributes lists and create serializers for them, but in order to do that we’ll have to add their association macros to the post_serializer. In this case, a post belongs_to a user and a post belongs_to a bird. The post serializer should now look like this:
class PostSerializer < ActiveModel::Serializer
attributes :id, :caption
belongs_to :user
belongs_to :bird
endNext, we’ll create serializers for user and bird:
$ rails g serializer user
$ rails g serializer birdThen, pass in the attributes into each serializer that we want to see in the JSON. The new serializers should look something like this:
#serializers/bird_serializer.rb
class BirdSerializer < ActiveModel::Serializer
attributes :id, :name, :image_url, :description
end#serializers/user_serializer.rb
class UserSerializer < ActiveModel::Serializer
attributes :id, :username
endNow, our GET request only yields the data we need for our front end.
#http://localhost:3000/posts
{
“id”: 1,
“caption”: “These birds are taking over the city!!”,
“user”: {
“id”: 1,
“username”: “Sasha”
},
“bird”: {
“id”: 1,
“name”: “Pigeon”,
“image_url”: “https://images.unsplash.com/photo-1549586073-f4c3b7ff044a?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8M3x8cGlnZW9ufGVufDB8fDB8fA%3D%3D&auto=format&fit=crop&w=500&q=60",
“description”: “Street bird native to NYC”
}
}And our BirdsController.rb file looks squeaky clean:
class BirdsController < ApplicationController
def index
render json: Bird.all
end
endConclusion
Let’s recap the process:
- Add the Active Model gem to your Gemfile
- Run $ bundle install to install Active Model to your app
- Reset your server if it’s already running
- Ctrl + c
- $ rails server
4. Use Rails’ resource generator to create a serializer:
$ rails g serializer serializer_name5. Pass in the attributes that you want sent as json as argument into the attributes method in the new serializer.
6. If you want to serialize associated data, include the association macro in the serializer and create another serializer for that attribute.
For more on AMS, click here
For a closer look at the bird watcher API, click here.






