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
136: Eager Loading Associations
A few years ago, I was reviewing a pull request for a junior developer who had built a "Project Overview" dashboard. In the staging environment, with maybe twenty records, the page felt snappy. But the moment it hit production with thousands of projects, the site practically ground to a halt. When I looked at the production logs, I didn't see one or two queries; I saw a literal wall of text—thousands of nearly identical SELECT * FROM tasks WHERE project_id = ? queries scrolling past in a blur. He had accidentally built an N+1 query, and it was killing the database.
This is the classic performance trap in ActiveRecord. An N+1 query happens when you fetch a collection of records (the "1" query) and then, while iterating through them, you trigger another query for each individual record to fetch its associations (the "N" queries). If you have 100 projects, you end up making 101 database calls. That's an incredibly expensive way to get data.
Stopping the Bleeding with .includes
The most common way to fix this is by using the .includes method. Instead of waiting until the loop to fetch the associations, you tell ActiveRecord upfront that you're going to need that data. It’s essentially saying, "Hey, I'm getting these projects, and I know I'm going to need their tasks, so just grab them all now."
# The slow way (The N+1 trap)
projects = Project.limit(10)
projects.each do |project|
puts project.tasks.map(&:name) # Hits the DB for every single project
end
# The fast way (Eager Loading)
projects = Project.includes(:tasks).limit(10)
projects.each do |project|
puts project.tasks.map(&:name) # Data is already in memory; no extra queries
end
When you use .includes, ActiveRecord is smart. By default, it usually performs two queries: one for the main records and one for all associated records linked to those IDs. This is vastly more efficient than hitting the database 101 times.
Choosing Between Preload and Eager Load
Under the hood, .includes decides whether to use .preload or .eager_load. While you can usually stick with .includes, there are times when you need to be explicit about how the data is fetched, especially when you start filtering your results based on the associated table.
.preload uses a separate query to fetch the associations. It's clean and fast, but it has a major limitation: you cannot reference the preloaded table in a where clause because that table isn't part of the initial SQL query.
.eager_load, on the other hand, uses a massive LEFT OUTER JOIN to pull everything into one single, giant result set. This is your go-to when you need to filter the primary records based on a value in the associated table.
# This will crash with .preload because 'tasks' isn't in the main query
Project.preload(:tasks).where("tasks.priority = 'High'")
# This works because it forces a JOIN
Project.eager_load(:tasks).where("tasks.priority = 'High'")
I generally recommend starting with .includes. If you find yourself needing to filter by the association, switch to .eager_load. Just keep an eye on your memory usage; joining massive tables can sometimes create a result set that's too large for Ruby to handle comfortably.
📋 Practical Task
Optimizing the Invoice Line Item Report
You have been handed a piece of legacy code that generates a summary report for invoices. Currently, the code is causing a massive performance bottleneck because it triggers an N+1 query for every invoice's line items and the client associated with those line items.
The current slow implementation:
# This code is killing the server!
invoices = Invoice.where(status: 'pending').limit(50)
invoices.each do |invoice|
puts "Invoice ##{invoice.id}"
invoice.line_items.each do |item|
puts "- #{item.description}: $#{item.price} (Product Category: #{item.product.category})"
end
end
Your Task: Rewrite the first line (the query) to eager load both the line_items and the product associated with each line item. You will need to use a nested hash syntax to reach the product through the line items. Ensure that the entire loop can execute without triggering any additional database queries after the initial fetch.
There are no comments for now.