/ rails-4

Automatic counter-cache issue with has-many association in Rails 4.2.x

Recently while working on one of our project there was an requirement of tracking count of records associated with has-many association. But also there was "is_hidden" flag set on some of the records which I do not suppose to count in counter as those were hidden records. So default counter_cache option of rails active-record was not appropriate in this case which automatically counts and caches the number of associated records and keeps cache updated. Refer following model code for this.

class Order < ActiveRecord::Base
  belongs_to :customer, counter_cache: true
end

class Customer < ActiveRecord::Base
  has_many :orders
end

In above case @customer.orders.size will return the current count of customer orders without hitting database but for this you also need to have orders_count column in Customer model which will be used by active-record to maintain counter value.

But as I didn't want to count hidden records, I added custom code in after_save and after_destroy callbacks of Topic model to increment and decrement associated topics count for category if record is having is_hidden flag as FALSE. I used column name as topics_count in Category model to store/increment/decrement this count.

My models were as follows:

class Topic < ActiveRecord::Base
  belongs_to :category    
end

class Category < ActiveRecord::Base
  has_many :topics
end

And my custom code to increment and decrement topics_count was:

class Topic < ActiveRecord::Base

  after_save :update_topics_count_for_category
  after_destroy :decrement_topics_count_for_category

  def decrement_topics_count_for_category 
    category.decrement!(:topics_count) unless is_hidden 
  end

  def update_topics_count_for_category 
    if (id_changed? ? !is_hidden : is_hidden_changed?) 
      is_hidden ? category.decrement!(:topics_count) : category.increment!(:topics_count) 
    end 
  end 
end 

This solution was maintaining topics count as expected but then in one scenario I created topic using has many association method

i.e. @category.topics.create(topic_params)

This was leading to double increment in value of topics_count and I was confused due to this random issue as creating topic
as Topic.create(..., category_id: category.id) was not leading to this issue. Obviously first doubt was on counter_cache feature of active-record. So while debugging it that direction I found that in Rails 4.2.x, though counter_cache option is not set to true on belongs_to association, using has-many association method for creating associated record, automatically increments the column value if column such as "#{table_name}_count" (in this case topics_count) is found in belonging model (in this case "Category").

And as I was incrementing topics_count value in after_save callback also, it was getting incremented twice.
Finally renaming topics_count to topic_counter fixed this issue.

For more information you can refer this issue details here: automatic counter_cache issue in Rails 4.2.x

Tushar Titame

I am software enthusiast. I like to work on web frameworks, services and trending things in JS world. As a hobby I love visiting beautiful places and capturing them through eyes...

Read More