Active Model Serializers in Rails API: Clean JSON Responses Made Simple
Learn how Active Model Serializers improves JSON serialization in Rails. Build cleaner APIs, control response structure, and simplify your JSON API design.

Gokul P
Lead Technical Consultant

Building a clean and predictable Rails API can quickly become challenging, especially when every endpoint starts returning inconsistent data or leaking fields that should’ve stayed internal.
And that’s when Active Model Serializers comes to the rescue. With the Active Model Serializers gem, you can transform messy model dumps into clear, maintainable, and structured JSON output.
Whether you're trying to streamline JSON serialization in Rails or simply want a better way to control the format of your API responses, Active Model Serializers gives you a powerful abstraction layer. It sits neatly between your Rails models and your API, letting you define exactly what gets exposed and how it’s delivered, making it easier than ever to implement Active Model Serializers in a Rails API and deliver predictable, frontend-friendly JSON.
What is Active Model Serializers?
Active Model Serializers (AMS) is a gem that controls how your Rails models convert to JSON.
Instead of exposing raw model data, you define exactly what is included in each response. You control attribute names, relationships, and data formats.
Think of it as a view layer for your API. Just like Rails views format HTML, serializers format JSON.
Why does it matter?
Raw model JSON causes real problems.
You expose sensitive data by accident. Password hashes, internal IDs, and timestamps that the frontend doesn't need all leak through.
Your API responses become coupled to the database structure. Changing a column name breaks every client.
You write the same formatting logic repeatedly. Date formatting, nested associations, and computed values are all duplicated across controllers.
Active Model Serializers solves these issues. You define the structure once. Every endpoint using that model follows the same format.
Your API becomes predictable. Frontend developers know exactly what to expect. Documentation stays accurate because the serializer defines the contract.
How to implement Active Model Serializers?
Start by adding the gem and building your first serializer.
Step 1: Install the gem
Add AMS to your Gemfile:
gem 'active_model_serializers', '~> 0.10.0'
Run bundle install:
bundle install
Step 2: Generate a serializer
Rails generators make this quick:
rails g serializer User
This creates
app/serializers/user_serializer.rb:
class UserSerializer < ActiveModel::Serializer
attributes :id, :email, :name
end
Step 3: Choose your attributes
Edit the serializer to include only what you need:
class UserSerializer < ActiveModel::Serializer
attributes :id, :email, :full_name, :created_at
def full_name
"#{object.first_name} #{object.last_name}"
end
def created_at
object.created_at.strftime('%Y-%m-%d')
end
end
The object variable refers to your model instance. You can call any model method or define custom ones.
Step 4: Handle associations
Add relationships between models:
class UserSerializer < ActiveModel::Serializer
attributes :id, :email, :full_name
has_many :posts
belongs_to :company
end
Create serializers for associated models:
rails g serializer Post
rails g serializer Company
Define what each association shows:
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, :published_at
belongs_to :user
end
Step 5: Use in controllers
Your controller stays clean:
class Api::V1::UsersController < ApplicationController
def index
users = User.all
render json: users
end
def show
user = User.find(params[:id])
render json: user
end
end
AMS automatically uses the serializer. No extra code needed.
Step 6: Customize per endpoint
Sometimes you need different data for different endpoints:
def show
user = User.find(params[:id])
render json: user, include: ['posts', 'company']
end
def index
users = User.all
render json: users, fields: { users: [:id, :email] }
end
The include parameter controls associations. The fields parameter limits attributes.
Step 7: Add conditional attributes
Show data based on permissions:
class UserSerializer < ActiveModel::Serializer
attributes :id, :email, :full_name
attribute :phone_number, if: :show_phone?
attribute :ssn, if: :admin_user?
def show_phone?
current_user&.premium?
end
def admin_user?
current_user&.admin?
end
def current_user
scope
end
end
Pass the current user when rendering:
render json: user, scope: current_user
Common mistakes to avoid
Not creating serializers for associations. If User has_many :posts but you don't create PostSerializer, you get raw model JSON for posts.
Calling database queries inside serializers. This creates N+1 problems. Use eager loading in your controller instead:
users = User.includes(:posts, :company).all
Over-serializing associations. Loading every nested relationship kills performance. Be selective:
render json: users, include: ['posts'] # Good
render json: users, include: ['**'] # Bad - loads everything
Forgetting to handle nil associations. Add guards in custom methods:
def company_name
object.company&.name || 'No company'
end
Using serializers for input validation. Serializers format output only. Use strong parameters for input.
Testing your changes
Test serializers like any Ruby class:
# spec/serializers/user_serializer_spec.rb
require 'rails_helper'
RSpec.describe UserSerializer do
let(:user) { create(:user, first_name: 'John', last_name: 'Doe') }
let(:serializer) { UserSerializer.new(user) }
let(:serialization) { ActiveModelSerializers::Adapter.create(serializer) }
it 'includes expected attributes' do
json = serialization.as_json
expect(json[:user][:full_name]).to eq('John Doe')
expect(json[:user]).to have_key(:email)
expect(json[:user]).not_to have_key(:password_digest)
end
end
Test in controllers with request specs:
# spec/requests/api/v1/users_spec.rb
require 'rails_helper'
RSpec.describe 'Users API' do
it 'returns serialized user data' do
user = create(:user)
get "/api/v1/users/#{user.id}"
json = JSON.parse(response.body)
expect(json['user']['full_name']).to be_present
expect(json['user']['password_digest']).to be_nil
end
end
Check your API responses manually:
curl http://localhost:3000/api/v1/users/1 | jq
The jg tool formats JSON for easy reading.
Final words
In the end, what makes an API great isn’t the JSON it sends, it is all about how meaningful and well-structured that JSON is. Using a Rails serializer through the Active Model Serializers gem ensures your API stays organized, consistent, and easy for clients to consume. Instead of scattering formatting logic across controllers, you define your data contract once and reuse it everywhere.
If you're aiming to improve JSON API using Active Model Serializers with Rails, AMS offers the structure, flexibility, and clarity needed to keep your code clean and scalable. For teams looking to implement Active Model Serializers with Rails API the right way or untangle messy JSON responses, RailsFactory can help you build an API that’s easier to maintain and far more enjoyable to work with.
Or if you simply need expert guidance on any Ruby on Rails or broader software development needs, contact our team. We are always here to help.
FAQs
1. What's the difference between Active Model Serializers and Jbuilder?
Active Model Serializers uses Ruby classes to define JSON structure. Jbuilder uses template files similar to views. AMS tends to be faster and more object-oriented. Jbuilder gives more flexibility for complex, one-off formats. Choose AMS for consistent API responses. Choose Jbuilder when you need template-style control.
2. How do I version my API with serializers?
Create separate serializers for each version:
# app/serializers/api/v1/user_serializer.rb
class Api::V1::UserSerializer < ActiveModel::Serializer
attributes :id, :name
end
# app/serializers/api/v2/user_serializer.rb
class Api::V2::UserSerializer < ActiveModel::Serializer
attributes :id, :full_name, :email
end
Specify the serializer explicitly in controllers:
render json: user, serializer: Api::V2::UserSerializer
3. Can I use Active Model Serializers with GraphQL?
Not directly. GraphQL has its own type system. The gem works best with REST APIs. For GraphQL in Rails, use the graphql-ruby gem instead. It provides similar control over data structure but follows GraphQL patterns.
4. How do I handle pagination with serializers?
AMS works with pagination gems like Kaminari or will_paginate:
users = User.page(params[:page]).per_page(20)
render json: users, meta: {
current_page: users.current_page,
total_pages: users.total_pages,
total_count: users.total_count
}
The meta data appears in your JSON response alongside the serialized users.



