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
123: Building a JSON API with Rails
So, you've got your Rails app running, your models are set up, and you're comfortable with the standard MVC flow. But lately, most of the apps we build aren't just serving HTML pages—they're serving data to a frontend framework like React or a mobile app. We need a JSON API.
I've already got a Book model in my app with title, isbn, and summary. Let's see what happens when we try to expose this to the world without overthinking it.
Just throwing data at the screen
My first instinct is usually to just create a controller and tell Rails to render the object as JSON. I'll set up a BooksController with an index action:
class BooksController < ApplicationController
def index
@books = Book.all
render json: @books
end
end
I'll hit that endpoint in my browser or via Curl, and it works! It returns an array of book objects. But as I look at the output, I notice a problem. Rails is dumping everything. I'm seeing id, created_at, and updated_at. My API consumer doesn't need to know exactly what millisecond a record was updated in my database; that's internal noise.
Trimming the fat
I want to be explicit about what I'm sending. I remember that render json: actually calls as_json under the hood. I'll try to pass some options to that call right in the controller to see if I can filter the fields.
def index
@books = Book.all
render json: @books.as_json(only: [:title, :isbn])
end
That's better. The timestamps are gone. But here's where it gets annoying: what if I want to include the author? I have a Book belongs_to :author relationship. If I just add :author to the only list, Rails will just give me the author's ID or a weird string, not the actual author object.
Bringing in the Author
I need to nest the data. Let's try using the include option. I'll tell Rails to include the author, but I probably don't want the author's password hash or email address in a public book list.
def index
@books = Book.all
render json: @books.as_json(
only: [:title, :isbn],
include: { author: { only: [:name, :bio] } }
)
end
Now the output looks like a professional API response. However, looking at this controller, I'm starting to cringe. If I have five different actions (show, index, update, etc.) and I'm manually defining these only and include hashes everywhere, I'm going to have a maintenance nightmare. If I rename bio to biography in the Author model, I have to hunt down every single JSON hash in my controllers.
Moving the logic out of the controller
This is the "aha!" moment where I realize the controller shouldn't be responsible for the shape of the JSON. We need a presentation layer. While there are gems like ActiveModelSerializers or Panko, for a lot of projects, a simple Jbuilder template (which comes with Rails) is the cleanest way to handle this. It lets us write Ruby code to build the JSON structure in a separate file.
I'll remove the render json: line from my controller and just let it look for a view. Then I'll create app/views/books/index.json.jbuilder:
json.array! @books do |book|
json.title book.title
json.isbn book.isbn
json.author do
json.name book.author.name
json.bio book.author.bio
end
end
I just moved the "how it looks" logic out of the "how it works" logic. Now, if I want to add a virtual field—like a full_description that combines the summary and the author's bio—I can just do it right here in the Jbuilder file without cluttering my controller. This feels much more scalable.
📋 Practical Task
Building a Public Book Search Endpoint
You have a Book model and a Genre model (Book belongs_to Genre). Create a SearchController with a results action that takes a query parameter q to search for books by title.
Your task is to implement this using a Jbuilder template. The JSON response for each book must include:
- The book's
title. - The book's
publication_year. - A nested
genreobject containing only the genre'sname. - A custom field called
search_termthat echoes back the query parameterqused for the search.
Ensure your controller does not contain any as_json or only: [...] logic; all formatting must happen in the .json.jbuilder file.
There are no comments for now.