Skip to Content
Course content

125: Rails Serializers (ActiveModel::Serializer)

Click on the "Edit" button in the top corner of the screen to edit your slide content.

When you first start building an API in Rails, it feels like magic. You have a model, you have a controller, and you just call render json: @user. Rails handles the conversion to JSON automatically, and for a tiny project, it works perfectly. But as soon as your app grows beyond a prototype, this "magic" starts to feel like a liability. I've seen too many developers realize too late that they've been accidentally leaking hashed passwords or internal admin flags to the public internet because they relied on the default to_json behavior.

The temptation of the simple render

Let's say we're building a library app. We have a Book model with a title, an ISBN, a secret internal_acquisition_cost, and a belongs_to :author relationship. The naive approach looks like this in your controller:

def show
  @book = Book.find(params[:id])
  render json: @book
end

At first glance, this is clean. But here is the problem: the client gets everything. The internal cost is exposed, and the updated_at timestamps—which the frontend usually doesn't care about—are clogging up the payload. If you try to fix this by passing only: or except: options directly into the render call, you're just pushing the problem around. Pretty soon, your controller is littered with massive hashes defining which fields to show, and you're repeating that logic in every single action that returns a Book.

Moving logic into the Serializer layer

This is where ActiveModel::Serializer (AMS) comes in. Instead of letting the model decide how it looks as JSON, we introduce a dedicated layer that sits between the model and the response. Think of it as a "view" for your API. I prefer this because it separates the data (the model) from the presentation (the JSON structure).

Here is how we'd handle that Book model properly. First, we create a serializer file:

class BookSerializer < ActiveModel::Serializer
  attributes :id, :title, :isbn, :summary

  belongs_to :author

  # We can even create "virtual" attributes that don't exist in the DB
  def summary
    "#{object.title} - ISBN: #{object.isbn}"
  end
end

Now, back in the controller, you still call render json: @book. Rails is smart enough to see that a BookSerializer exists and will use it automatically. The internal cost is gone, the timestamps are hidden, and we've added a custom summary field without polluting our database model with API-specific formatting logic.

The cost of adding a layer

I'll be honest with you: adding serializers does add a bit of boilerplate. You're creating a new file for every model you want to expose. When you're in a rush, that feels like a chore. However, the trade-off is worth it for the consistency. If you decide tomorrow that isbn should be renamed to isbn_13 across your entire API, you change it in one serializer file rather than hunting through ten different controllers.

Another huge win is how it handles associations. By declaring belongs_to :author in the serializer, AMS will look for an AuthorSerializer. This creates a recursive, clean chain of data transformation. You get a predictable JSON structure that doesn't change just because you added a new column to your database table during a migration. Your API contract remains stable, which is the kind of thing that keeps your frontend developers from calling you at 2 AM because the app crashed after a backend update.




📋 Practical Task

Building a Price-Formatted Product API

You are working on an e-commerce API. You have a Product model with the following attributes: id, name, price_cents (an integer), sku, and internal_warehouse_location. The Product belongs to a Category.

Your task is to create a ProductSerializer that meets the following requirements:

  • Only expose the id, name, and sku.
  • Hide the internal_warehouse_location and price_cents.
  • Include the associated category.
  • Create a custom attribute called formatted_price that converts the price_cents into a decimal string with a dollar sign (e.g., if price_cents is 1999, the output should be "$19.99").

Write the ProductSerializer class implementation below.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.