---
title: What's a counter cache?
teaser: Running a separate query to count how many records are associated with another
  record is inefficient. Using a counter cache can save the day!
tags: rails,ruby,web,activerecord,good code
author: Jose Blanco
published_on: 2023-11-21
---

Nowadays users of modern web and mobile applications expect a fast response for each interaction.
As developers, we need to be aware of the performance of our applications and how we can improve it. This involves 
constantly searching for potential bottlenecks, and especially looking for inefficient queries when we are dealing 
with large amounts of data.

[One of the most common performance issues in Rails applications is the N+1 query problem](https://guides.rubyonrails.org/active_record_querying.html#n-1-queries-problem). 
This problem occurs when we are loading a collection of records and for each record we are running a separate query to 
count how many records are associated with that record.

### A common example

Imagine that your web application is about keeping track of how many comic books you have in your collection.
Each collection `has_many :commic_books` and each comic book belongs to a different `Genre` like superhero,  
manga, mystery, horror, and more. 

Now you want to show to the user how many comic books are in each genre.

### Understanding Counter Caches

Continuing with the previous example a naïve approach might look something like this:

```ruby
class Genre < ApplicationRecord
  has_many :comic_books

  def comic_books_count
    comic_books.count
  end
end
```

Somewhere in our application:

```erb
<% @genres.each do |genre| %>
    <%= genre.comic_books_count %>
<% end %>
```

This will lead to an N+1 query problem. For each genre we will run a separate query to count how many comic books are
associated with that genre and this is not efficient.

The below will be the SQL query generated by Active Record:

```sql
SELECT "genres".* FROM "genres"
SELECT COUNT(*) FROM "comic_books" WHERE "comic_books"."genre_id" = $1  [["genre_id", 1]]
SELECT COUNT(*) FROM "comic_books" WHERE "comic_books"."genre_id" = $1  [["genre_id", 2]]
```

As you can see we are running one query for each comic book genre. This is not efficient and it will slow down 
our application. 

### Counter Cache to the rescue

[Rails provides a way to cache the number of associated records](https://guides.rubyonrails.org/association_basics.html#counter-cache)
in a separate column using the `counter_cache` option.

```ruby
class ComicBook < ApplicationRecord
  belongs_to :genre, counter_cache: true
end
```

```ruby
class Genre < ApplicationRecord
  has_many :comic_books
end
```

To do this, we can just add a new column to the `genres` table named `comic_books_count` like this:

```ruby
class AddComicBooksCountToGenres < ActiveRecord::Migration[7.0]
  def change
    add_column :genres, :comic_books_count, :integer, default: 0, null: false
  end
end
```

Rails takes care of updating the `comic_books_count` for you. When a comic book is associated with a genre, it will
increment, and when a comic book record is deleted, it will decrement automatically and everything is
handled efficiently by Rails.

By setting a default value of 0, we can avoid a `nil` value in the database. This is important because it provides a
starting point for those genres that don't have any associated comic books yet.

To display the number of comic books we can just do: 

```erb
 <% @genres.each do |genre| %>
    <%= genre.comic_books_count %>
<% end %>
```

The SQL query generated by ActiveRecord will be:

```sql
SELECT "genres".* FROM "genres";
```

Mission accomplished! 🎉 Because each genre knows how many comic books are associated with it, 
Active Record won't run a separate query for each genre.

### Conclusion

Rails provides simple and efficient solutions to complex issues.
The counter cache option is one of many. I hope this post will help you to improve the performance of your rails
applications!
