# TODO N+1

<https://code.tutsplus.com/articles/improving-the-performance-of-your-rails-app-with-eager-loading--cms-25018>

## Introduction

Ruby on Rails ORM has lazy loading enabled.

## Example

```ruby
#  Post Model
class Post < ActiveRecord::Base
    belongs_to :author
end

#  Author Model
class Author < ActiveRecord::Base
    has_many :posts
end
```

```ruby
# Controller
class PostsController < ApplicationController
    def index
        @posts = Post.order(created_at: :desc)
    end
end
```

```ruby
# View
<% @posts.each do |post| %>
    <td><%= post.title %></td>
    <td><%= post.author.name %></td>
<% end %>
```

```
authors = Author.create([{ name: 'John' }, { name: 'Doe' }, { name: 'Manish' }])
Post.create(title: 'I love Tuts+', body: '', author: authors.first)
Post.create(title: 'Tuts+ is Awesome', body: '', author: authors.second)
Post.create(title: 'Long Live Tuts+', body: '', author: authors.last)
```

## Case 1: Default - lazy loading

```ruby
@posts = Post.order(created_at: :desc)
```

N+1

```ruby
Post Load (0.6ms)  SELECT "posts".* FROM "posts"  ORDER BY "posts"."created_at" DESC
Author Load (0.5ms)  SELECT  "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT 1  [["id", 3]]
Author Load (0.1ms)  SELECT  "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT 1  [["id", 2]]
Author Load (0.1ms)  SELECT  "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT 1  [["id", 1]]
```

## Case 2.1: `preload` - eager loading / separated queries

```ruby
@posts = Post.order(created_at: :desc).preload(:author)
```

```ruby
SELECT "posts".* FROM "posts"  ORDER BY "posts"."created_at" DESC
SELECT "authors".* FROM "authors" WHERE "authors"."id" IN (3, 2, 1)
```

1. No N+1
2. Fail @ order posts by author name
3. Fail @ find posts by author name

## Case 2.2: `eager_load` - eager loading / one query

```ruby
@posts = Post.order(created_at: :desc).eager_load(:author)
```

```ruby
SELECT "posts"."id" AS t0_r0, "posts"."title" AS t0_r1, "posts"."body" AS t0_r2, "posts"."author_id" AS t0_r3, "posts"."created_at" AS t0_r4, "posts"."updated_at" AS t0_r5, "authors"."id" AS t1_r0, "authors"."name" AS t1_r1, "authors"."created_at" AS t1_r2, "authors"."updated_at" AS t1_r3 
FROM "posts"
LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id" 
ORDER BY "posts"."created_at" DESC
```

order posts by author name

```ruby
@posts = Post.order("authors.name").eager_load(:author)
```

```ruby
SELECT "posts"."id" AS t0_r0, "posts"."title" AS t0_r1, "posts"."body" AS t0_r2, "posts"."author_id" AS t0_r3, "posts"."created_at" AS t0_r4, "posts"."updated_at" AS t0_r5, "authors"."id" AS t1_r0, "authors"."name" AS t1_r1, "authors"."created_at" AS t1_r2, "authors"."updated_at" AS t1_r3 
FROM "posts"
LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
ORDER BY authors.name
```

find posts by author name

```ruby
@posts = Post.order(created_at: :desc).eager_load(:author).where("authors.name = ?", "Manish")
```

```ruby
SELECT "posts"."id" AS t0_r0, "posts"."title" AS t0_r1, "posts"."body" AS t0_r2, "posts"."author_id" AS t0_r3, "posts"."created_at" AS t0_r4, "posts"."updated_at" AS t0_r5, "authors"."id" AS t1_r0, "authors"."name" AS t1_r1, "authors"."created_at" AS t1_r2, "authors"."updated_at" AS t1_r3 
FROM "posts"
LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
WHERE (authors.name = 'Manish')  
ORDER BY "posts"."created_at" DESC
```

**single query and** `LEFT OUTER JOIN`**can also be very expensive.**

## Case 2.3 `includes` **- smarter**

(same as preload)

```ruby
@posts = Post.order(created_at: :desc).includes(:author)
```

```ruby
SELECT "posts".* FROM "posts"  ORDER BY "posts"."created_at" DESC
SELECT "authors".* FROM "authors" WHERE "authors"."id" IN (3, 2, 1)
```

order posts by author name (same as eager\_load)

```ruby
@posts = Post.order("authors.name").includes(:author)
```

```ruby
SELECT "posts"."id" AS t0_r0, "posts"."title" AS t0_r1, "posts"."body" AS t0_r2, "posts"."author_id" AS t0_r3, "posts"."created_at" AS t0_r4, "posts"."updated_at" AS t0_r5, "authors"."id" AS t1_r0, "authors"."name" AS t1_r1, "authors"."created_at" AS t1_r2, "authors"."updated_at" AS t1_r3 
FROM "posts"
LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
ORDER BY authors.name
```

find posts by author name (same as eager\_load)

```ruby
@posts = Post.order(created_at: :desc).includes(:author).where("authors.name = ?", "Manish").references(:author)
```

```ruby
SELECT "posts"."id" AS t0_r0, "posts"."title" AS t0_r1, "posts"."body" AS t0_r2, "posts"."author_id" AS t0_r3, "posts"."created_at" AS t0_r4, "posts"."updated_at" AS t0_r5, "authors"."id" AS t1_r0, "authors"."name" AS t1_r1, "authors"."created_at" AS t1_r2, "authors"."updated_at" AS t1_r3 
FROM "posts"
LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
WHERE (authors.name = 'Manish')  
ORDER BY "posts"."created_at" DESC
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://huang-jason.gitbook.io/ruby-rails-syntax/todo-n+1.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
