Eliminate N+1 queries and improve Rails app performance with this effective Eager Loading tip
Tags: rails, eagerload, refactoring, programming
Published: 2023-01-12 19:55:33

_Originally posted on [ahmednadar.com](https://www.ahmednadar.com/posts/eliminate-n-1-queries-and-improve-rails-app-performance-with-this-effective-eager-loading-tip)_


In Rails applications, N+1 queries can cause significant performance issues. They occur when you have a list of items and perform a separate query to retrieve associated data for each item. This can result in poor performance, and even more so if the list contains a large number of items.

**A problem**

I worked on an application, and initially, I wrote the following `index` method with a list of items and for each item, it make a separate query to retrieve associated data, as you can see this method represents an example of vulnerable code to the N+1 query problem:

```
def index
    @companies = Company.most_recent

    @open_companies = Company.with_active_status.includes(:_members, :users).most_recent

    @previous_companies = Company.without_active_status.includes(:_members, :users).most_recent.limit(50)

    @archived_companies = Company.with_archived_status.includes(:_members, :users).most_recent
  end
```
Here we have separate queries are run for each company to retrieve the associated `_members`, `users`, and `user` needed data. This can result in poor performance, not to mention if a large number of companies are displayed.

I need to avoid the N+1 query issue. How? Eager loading can be used to include the associated data in the initial query. In Active Record, this is accomplished by using the `includes` method.

**A Solution**

Here is a modified version of the index method that avoids the N+1 query problem by using the includes method:

```
def index
  # Eager load _members, users, and user associations for @companies
  @companies = Company.includes(:_members, :users, _members: [:user]).most_recent

  # Eager load _members and users associations for @open_companies
  @open_companies = Company.with_active_status.includes(:_members, :users).most_recent

  # Eager load _members and users associations for @previous_companies
  @previous_companies = Company.without_active_status.includes(:_members, :users).most_recent.limit(50)

  # Eager load _members and users associations for @archived_companies
  @archived_companies = Company.with_archived_status.includes(:_members, :users).most_recent
end
```

That’s much better as you can tell. The new index method employs the includes method to eagerly load the associations for each of the variables @companies, @open_companies, @previous_companies, and @archived_companies. This reduces the number of queries made and improves code performance, assisting in avoiding the N+1 query issue.

Using eager loading can improve the performance of your application and avoid the N+1 query issue. You can improve the performance of your application and avoid the N+1 query issue by using eager loading. Use it as much as possible to keep your code running smoothly and efficiently.

**Refactor**

While we solved the N+1 issue, the [code smells](https://medium.com/planet-arkency/the-biggest-rails-code-smell-you-should-avoid-to-keep-your-app-healthy-a61fd75ab2d3) because it breaks the [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) principle of software development. And we can avoid that by refactoring the ‘index’ method.

The refactored method below, combines the queries for the variables `@open_companies`, `@previous_companies`, and `@archived_companies` into a single query, reducing the number of queries and improving overall code performance as we continue to use the includes method, isn’t that beautiful:

```
def index
  # Eager load _members, users, and user associations for @companies
  @companies = Company.includes(:_members, :users, _members: [:user]).most_recent

  # Eager load _members and users associations for @open_companies, @previous_companies, and @archived_companies
  companies = Company.includes(:_members, :users)

  # Filter companies by active, inactive, and archived status
  @open_companies = companies.with_active_status.most_recent
  @previous_companies = companies.without_active_status.most_recent.limit(50)
  @archived_companies = companies.with_archived_status.most_recent
end
```

The last three instant methods call the Company object that includes `_members` attribute three times. And this could be extracted into a variable to call different scopes.

As you have seen, following the DRY principle and using Eager Load can improve the performance of your application and avoid the N+1 query issues. Be sure to use it whenever possible to keep your code running smoothly and efficiently.

And as usual, Happy Coding 😀 💻