The following blog post contains material either currently found or soon to be incorporated into my new book, "Easy Active Record for Rails Developers". Among many other topics, you'll learn about model generation, migrations, validations, associations, scopes, joins, includes, forms integration, nested forms, and model testing with RSpec and FactoryGirl. The book is now available, head over to this website's home page to learn more.


It is often useful to know how many records are associated with a model identified as being the parent in a belongs_to relationship. This information could be used when presenting a list of the U.S. states in a list group, akin to how http://arcadenomad.com/categories works by presenting each category name and the number of associated locations. The easiest way to retrieve this count is by formalizing the other end of the belongs_to relationship by indicating that a state has_many locations:

class State < ActiveRecord::Base

  has_many :locations                                                       

end                                                                         

With this in place, you can easily determine how many locations are associated with a particular state:

>> state = State.find_by_abbreviation("OH")                                 
>> state.locations.size                                                     
   (0.3ms)  SELECT COUNT(*) FROM `locations`  WHERE `locations`.`state_id` = 36
=> 84                                                                       

However, as you can see by the above example, determining this number requires execution of a COUNT(*) query, something we’d like to avoid if possible. Using the counter_cache option, you can! When enabled, this option will update a field in the parent model’s table every time the number of associated records changes, and then whenever size is called, Active Record will instead retrieve the value found in that field rather than perform the costly COUNT(*) query.

Enabling this option is actually a two step process. First you’ll want to add the counter_cache option to the belongs_to declaration:

class Location < ActiveRecord::Base                                                                        

  belongs_to :state, counter_cache: true                                    

end                                                                         

Then, you’ll want to create a migration affecting the parent model’s table, adding an integer-based column named locations_count:

$ rails g migration add_locations_count_to_states locations_count:integer   

This will generate the following migration:

class AddLocationsCountToStates < ActiveRecord::Migration                   
  def change                                                                
    add_column :states, :locations_count, :integer                          
  end                                                                       
end                                                                         

Note that the field name (locations_count) is adapted to the name of the child model’s table. You’ll need to modify the name accordingly for your own application’s purposes.

Run this migration and Active Record will begin updating the locations_count field every time the count changes due to the addition , modification, or deletion of a relevant association. But it does not take into account the number of associations that might already be in place. Therefore if you add this feature after data has already been loaded into the application, you’ll need to take additional steps to ensure the count is correct. This is easily accomplished by adding code similar to the following to seeds.rb and executing the rake db:seed task within the appropriate environment:

states = State.all                                                          
states.each do |state|                                                      
  state.locations_count = state.locations.count                             
  state.save                                                                
end

Like what you read? There’s plenty more where this came from in my new book, “Easy Active Record for Rails Developers”!

Comments