Ruby
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
125: Rails Serializers (ActiveModel::Serializer)
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, andsku. - Hide the
internal_warehouse_locationandprice_cents. - Include the associated
category. - Create a custom attribute called
formatted_pricethat converts theprice_centsinto a decimal string with a dollar sign (e.g., ifprice_centsis1999, the output should be"$19.99").
Write the ProductSerializer class implementation below.
There are no comments for now.