Preloading data with Rails: Includes, Preload, and Eager Load
September 14, 2025
The N+1 query problem is one of the most common performance issues when working with ORMs. Rails' includes method is the go-to solution for this.
Includes
Rails' includes method is smart. it dynamically chooses between different approaches based on our query structure.
Under the hood, includes typically delegates to preload, but this behavior can change depending on the circumstances. This intelligence makes includes the best way to solve the N+1 issues
Preload
When we use preload directly, or when includes chooses it internally, Rails executes separate queries for each association:
books = Book.preload(:authors)
This approach generates distinct queries: one for the primary records and additional queries for each association. For a book-author relationship, we would see something like:
SELECT * FROM books
SELECT * FROM authors WHERE id IN (1, 2, 3, ...)
The beauty of this approach lies in its simplicity and efficiency. Each query is straightforward, optimized, and Rails handles the data stitching in memory.
Eager Load
The eager_load method takes a different approach, combining everything into a single query using LEFT OUTER JOINs:
books = Book.eager_load(:authors)
This generates a more complex query:
SELECT books.*, authors.*
FROM books
LEFT OUTER JOIN authors ON authors.id = books.author_id
While this reduces database round trips, it comes with trade-offs. The query becomes more complex, potentially slower, and can consume significantly more memory with large datasets.
Joins
There's a third method that often causes confusion: joins. This method creates INNER JOINs but critically doesn't load associated records into memory.
books = Book.joins(:authors).where(authors: { published: true })
This is perfect for filtering records based on association attributes, but if we try to access the associated data later, we will trigger the N+1 problem we were trying to avoid.
When Includes Makes the Switch
Here's where includes demonstrates its intelligence. When we add conditions that reference associated tables, includes automatically switches from preload to eager_load:
Book.includes(:authors).where(authors: { name: 'Stephen King' })
Rails makes this switch because it needs the association data available in the same query to apply the WHERE condition effectively.