ALL ARTICLES
SHARE

ActiveStorage N+1 Queries: has_one_attached & has_many_attached

Christian Paez
Development
3 min read
ActiveStorage N+1 Queries: has_one_attached & has_many_attached

One of the greatest additions to recent versions of Ruby On Rails is the introduction of Active Storage, which helps us manage file uploads for different storage services and handles Active Record associations and migrations out of the box. A common method from the Active Storage module is has_one_attached, used to establish a one to one mapping between a model and a file (a user’s avatar in our example):

class User < ApplicationRecord
  has_one_attached: :avatar, dependent: :destroy
end
class User < ApplicationRecord
  has_one_attached: :avatar, dependent: :destroy
end

When you install and configure Active Storage, a number of database tables and columns will be created. These tables are polymorphic, so when you need to add ActiveStorage to another model in the future, no database migrations are needed. Thus, we do not need to define the avatar column, Active Storage will handle this under the hood by creating a has_one association to a ActiveStorage::Attachment record and a has_one: through: association to an ActiveStorage::Blob record. There is no need to access these associations directly, we can just call the association name to get a user’s avatar. Here’s an example of accessing the AvtiveStorage relationship:

user = User.last
user.avatar
user = User.last
user.avatar

The Problem: ActiveStorage N+1 Queries

A very common feature to implement in an `index` action would be to display a list of resources and their respective avatar:

<% @users.each do |user| %>
  <% if user.avatar.attached? %>
    <%= image_tag(user.avatar(resize_to_limit: [100, 100])) %>
  <% else %>
    <%= image_tag "placeholder.png" %>
  <% end %>
<% end %>
<% @users.each do |user| %>
  <% if user.avatar.attached? %>
    <%= image_tag(user.avatar(resize_to_limit: [100, 100])) %>
  <% else %>
    <%= image_tag "placeholder.png" %>
  <% end %>
<% end %>

The Solution: Eager Loading

When you call methods from within a loop, you should always be aware of N+1 database queries. An N+1 query is one that hits the database in every iteration of your loop. Doing this can cause very poor performance issues and can even result in request timeouts in some cases. The example above contains an N+1 query. This is because user.avatar is an ActiveStorage has_one relationship. Our model (User), will call its ActiveStorage::Attachment (avatar) relationship on every iteration of the loop in this example code.

So, how do we prevent this N+1 query from occurring? The solution to this is to eager load users’ attachments, just like you would do for any ActiveRecord association. ActiveStorage provides the with_attached_x method to accomplish this out of the box:

<% @users.with_attached_avatar.each do |user| %>
  <% if user.avatar.attached? %>
    <%= image_tag(user.avatar(resize_to_limit: [100, 100])) %>
   <% else %>
     <%= image_tag "placeholder.png" %>
   <% end %>
 <% end %>
<% @users.with_attached_avatar.each do |user| %>
  <% if user.avatar.attached? %>
    <%= image_tag(user.avatar(resize_to_limit: [100, 100])) %>
   <% else %>
     <%= image_tag "placeholder.png" %>
   <% end %>
 <% end %>

It is possible to set up a one-to-many relationship between models and files using the `has_many_attached` macro:

class User < ApplicationRecord
  has_many_attached: :images, dependent: :destroy
end
class User < ApplicationRecord
  has_many_attached: :images, dependent: :destroy
end

N+1 problems related to ActiveStorage can be handled using the with_attached_attachments helper:

<% @users.with_attached_images.each do |user| %>
  <% if user.images.attached? %>
    <% user.images.each do |image| %>
      <%= image_tag(image(resize_to_limit: [100, 100])) %>
    <% end %>
  <% end %>
<% end %>
<% @users.with_attached_images.each do |user| %>
  <% if user.images.attached? %>
    <% user.images.each do |image| %>
      <%= image_tag(image(resize_to_limit: [100, 100])) %>
    <% end %>
  <% end %>
<% end %>

The result is that loading images will take just one query, instead of nested database queries occurring within a loop.

Christian Paez
More ideas.
grpc vs rest
Development

gRPC vs. REST: Navigating API Communication Standards

Flatirons

Jul 26, 2024
yarn vs npm
Development

Yarn vs. npm: Choosing the Best Package Manager

Flatirons

Jul 22, 2024
process analysis
Development

Mastering Process Analysis in Business

Flatirons

Jul 18, 2024
product development life cycle
Development

Navigating the Product Development Life Cycle

Flatirons

Jul 11, 2024
Kotlin vs Java
Development

Kotlin vs. Java: Choosing the Right Language for Your Project

Flatirons

Jul 08, 2024
OpenShift vs Kubernetes: 10 Differences
Business

OpenShift vs Kubernetes: 10 Differences

Flatirons

Jul 06, 2024
grpc vs rest
Development

gRPC vs. REST: Navigating API Communication Standards

Flatirons

Jul 26, 2024
yarn vs npm
Development

Yarn vs. npm: Choosing the Best Package Manager

Flatirons

Jul 22, 2024
process analysis
Development

Mastering Process Analysis in Business

Flatirons

Jul 18, 2024
product development life cycle
Development

Navigating the Product Development Life Cycle

Flatirons

Jul 11, 2024
Kotlin vs Java
Development

Kotlin vs. Java: Choosing the Right Language for Your Project

Flatirons

Jul 08, 2024
OpenShift vs Kubernetes: 10 Differences
Business

OpenShift vs Kubernetes: 10 Differences

Flatirons

Jul 06, 2024
grpc vs rest
Development

gRPC vs. REST: Navigating API Communication Standards

Flatirons

Jul 26, 2024
yarn vs npm
Development

Yarn vs. npm: Choosing the Best Package Manager

Flatirons

Jul 22, 2024
process analysis
Development

Mastering Process Analysis in Business

Flatirons

Jul 18, 2024
product development life cycle
Development

Navigating the Product Development Life Cycle

Flatirons

Jul 11, 2024
Kotlin vs Java
Development

Kotlin vs. Java: Choosing the Right Language for Your Project

Flatirons

Jul 08, 2024
OpenShift vs Kubernetes: 10 Differences
Business

OpenShift vs Kubernetes: 10 Differences

Flatirons

Jul 06, 2024
grpc vs rest
Development

gRPC vs. REST: Navigating API Communication Standards

Flatirons

Jul 26, 2024
yarn vs npm
Development

Yarn vs. npm: Choosing the Best Package Manager

Flatirons

Jul 22, 2024
process analysis
Development

Mastering Process Analysis in Business

Flatirons

Jul 18, 2024
product development life cycle
Development

Navigating the Product Development Life Cycle

Flatirons

Jul 11, 2024
Kotlin vs Java
Development

Kotlin vs. Java: Choosing the Right Language for Your Project

Flatirons

Jul 08, 2024
OpenShift vs Kubernetes: 10 Differences
Business

OpenShift vs Kubernetes: 10 Differences

Flatirons

Jul 06, 2024