/ RAILS

Rails counter cache associations

Rails provides an ability to cache count of associated table records. This gives an edge that avoids querying to get number of records of associated table.

This is useful for having count of has many association records. It is available on belongs_to options on model.

Let’s take an example.

We have a users table. User model given below.

class Users < ApplicationRecord
  has_many :posts
end

And a user has many posts. Post model given below

class Post < ApplicationRecord
  belongs_to :user
end

Without counter_cache in Rails

Let’s say we want to list users with a count of posts by a user. First, we will fetch posts to be displayed in controller action.

class PostsController < ApplicationController
  def index
    # let's display details of last 10 users  
    @users = User.last(10)
  end
end

Then, we will display them in view erb as given below.

<!-- app/views/users/index.html.erb -->

<h1>Users</h1>

<table>
  <thead>
    <th>
      <td>ID</td>
      <td>Name</td>
      <td>Email</td>
      <td>Number of Posts</td>
    </th>
  </thead>
  <tbody>
    <% @users.each do |user| %>
      <td><% user.id %></td>
      <td><% user.name %></td>
      <td><% user.email %></td>
      <td><% user.posts.size %></td>
    <% end %>
  </tbody>
</table>

Now, this works well to render count of posts per user.

<td><% user.posts.size %></td>

But, this will query posts for every user object being looped over.

With counter_cache

1. Add a new column

Add a field on users to keep the track of number of posts by the user. We will need to add the field with following naming convention.

<association_name>_count

In the case discussed above, we will use column name given below.

posts_count

2. Mention counter_cache on association

We will need to add counter_cache option on post belongs_to user association.

class Post < ApplicationRecord
  belongs_to :user, counter_cache: true
end

That’s all. This will make sure that posts_count on users has updated value of number of posts for the user.

3. Access cached counter value

We don’t need to change the way we access number of records of associated table.

<% user.posts.size %>

This will work and use value from newly added column.

4. Populate count value of existing records

To populate values for the newly added column, Rails provides a method reset_counters.

We can populate counter with a migration with looping over all users as given below.

User.find_each do |user|
  user.reset_counters(user.id, :posts)
end

Reference