---
title: Avoiding N+1 queries the Railsy way with strict loading
teaser: |-
  Need to catch N+1 queries without adding any extra dependencies to your project?
  Strict Loading makes it effortless, but your configurations need to be set up correctly to avoid unexpected behaviors.
tags: ruby,rails,refactoring,development
author: Trésor Bireke
published_on: 2025-04-07
---

When working with Rails, one of the easiest ways to improve performance is to avoid N+1 queries as much as possible.

But what is an N+1 query? Here’s a quick example: let’s say you are rendering a list of 20 books, and you want to show author details on each line. If you don’t preload the authors, accessing `book.author.favorite_ice_cream` will trigger one additional query per book, making a total of 21 queries.

Strict loading is a Rails feature that allows avoiding N+1 queries without adding any extra dependencies to the project.

## How does strict loading help avoid N+1 queries?

Rails lazy loads resources by default, which means that when accessing a resource that hasn't been preloaded, Rails queries the database on demand, which can lead to N+1 queries.

Example

```ruby
class Movie < ApplicationRecord
  has_many :reviews
end

movies = Movie.all
movies.each do |movie|
  puts movie.reviews.size  # This triggers an N+1 query for each movie
end
```

In the above example, we are performing an extra query to fetch the reviews for each movie, which creates an N+1 query problem.

To prevent this issue, we can enable strict loading on the `reviews` association in the `Movie` model. This will raise an error whenever a review is accessed without being preloaded, forcing us to use eager loading instead of lazy loading.

Here's how that looks in practice.

```ruby
class Movie < ApplicationRecord
  has_many :reviews, strict_loading: true
end
```

Now accessing the `movie.reviews` will raise a `ActiveRecord::StrictLoadingViolationError` if the reviews are not preloaded.

```ruby
movie = Movie.first
movie.reviews # Raises ActiveRecord::StrictLoadingViolationError
```

To avoid the error, all we need to do is include or preload the reviews in the initial query.

```ruby
movie = Movie.first.includes(:reviews)
movie.reviews
```

## Enabling strict loading

Strict loading can be enabled on multiple levels such as:

### Model level strict loading

It is possible to enable strict loading for a model and all of its associations by setting the `strict_loading_by_default` attribute to `true`.

```ruby
class Movie < ApplicationRecord
  self.strict_loading_by_default = true
  has_many :reviews
end

class Review < ApplicationRecord
  belongs_to :movie
end
```

In this case, calling `Movie.first.reviews` without including the `reviews` in the initial query will raise an error.

### Association level strict loading

In cases where you want strict loading only on a specific association, you can do so by marking the setting `strict_loading: true` on the targeted association.

```ruby
class Movie < ApplicationRecord
  has_many :reviews, strict_loading: true
end
```

## Global strict loading configuration options

Rails offers the following global configuration options for strict loading:

### Configuring strict_loading_mode

By default `config.active_record.strict_loading_mode` is set to `:all`, meaning that any lazy loading attempt will raise an error. However, you can change it to `:n_plus_one_only`, which will only raise errors when associations are loaded in a way that leads to N+1 queries like this:

```ruby
# config/environments/development.rb
config.active_record.strict_loading_mode = :n_plus_one_only
```

I recommend using `:n_plus_one_only` since an error will only be raised when the association is loaded in a way that leads to N+1 queries.

### Showing strict loading violation only in logs

By default, strict loading will raise an error if there's a violation, but it can be turned off by explicitly logging the violations.

```ruby
# config/environments/production.rb
config.active_record.action_on_strict_loading_violation = :log
```

It's recommended to use this option in production environments to avoid crashing the application when strict loading is violated.

### Enabling strict loading validations for all models

Strict loading can be enabled for all the models via configuration:

```ruby
# config/application.rb
config.active_record.strict_loading_by_default = true
```

You can also disable it in the console via configuration:

```ruby
# config/application.rb
console do
  ActiveRecord::Base.strict_loading_by_default = false
end
```

### Disabling strict loading during test setups for FactoryBot

By default, if strict_loading is enabled for a model, any test setup that accesses an association without eager loading will fail.

This can be annoying because factories are just setting up data, and strict loading is primarily meant to catch issues in controllers/views.

You can disable strict loading via configuration:

```ruby
# spec/factories.rb

FactoryBot.define do
  after :build do |record|
    if record.is_a? ActiveRecord::Base
      record.strict_loading! false
    end
  end

  after :stub do |record|
    if record.is_a? ActiveRecord::Base
      record.strict_loading! false
    end
  end
end
```

Without this configuration, your test setup could fail due to strict loading violations before the actual test runs,
which is not what we want.

## Conclusion

Using strict loading in your Rails app can help prevent N+1 queries and improve application performance without adding any extra dependency to your project, but when using it, setting the proper configurations is crucial to avoid unexpected behavior.

### Additional resources:

- [Strict Loading Documentation](https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html?#method-i-strict_loading)
- [Release notes](https://rubyonrails.org/2020/2/21/this-week-in-rails-strict-loading-in-active-record-and-more)

---
