Skip to content

Instantly share code, notes, and snippets.

@marcamillion
Created March 27, 2013 15:50
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save marcamillion/5255300 to your computer and use it in GitHub Desktop.
Save marcamillion/5255300 to your computer and use it in GitHub Desktop.
Implementing HABTM counter_cache - between 2 models (Tag, Question)
## I imagine this could be done in the migration, but it was giving me problems so I moved it to the model
## and just did it on the command line
> Tag.reset_questions_count
class AddTagCounterToQuestion < ActiveRecord::Migration
def up
add_column :tags, :questions_count, :integer, default: 0, null: false
end
def down
remove_column :tags, :questions_count
end
end
class Question < ActiveRecord::Base
## the validates_tag method is just to prevent duplicate tags being added to the db - not fully necessary for this exercise,
## but I figured I would leave it just in case
has_and_belongs_to_many :tags, before_add: :validates_tag
after_create :increment_tags_counter
after_destroy :decrement_tags_counter
def validates_tag(tag)
self.tags.include? tag
end
def increment_tags_counter
self.tags.each do |t|
Tag.find_by_name(t.name).questions_count += 1
end
end
## I haven't tested the callback on this, but it should work. What I am not 100% sure of is if the record is saved even
## a destroy, such that we can call `self.tags` on that same record and it works. If it doesn't work, then this can just
## be moved to a 'before_destroy'.
def decrement_tags_counter
self.tags.each do |t|
Tag.find_by_name(t.name).questions_count -= 1
end
end
end
class Tag < ActiveRecord::Base
attr_accessible :name, :questions_count
has_and_belongs_to_many :questions, uniq: true
def self.reset_questions_count
Tag.all.each do |t|
question_count = t.questions.count
t.update_attributes(:questions_count => question_count)
end
end
end
@romalopes
Copy link

Hi, thank you for your code.
Does it work if I remove the association without deleting a Tag?

@coderhs
Copy link

coderhs commented May 6, 2014

i would suggest adding the reset_questions_count function to the migration itself, as that code needs to be used only need to be used once.

@leemour
Copy link

leemour commented Jun 3, 2014

You can do:

def increment_tags_counter
  self.tags.each do |t|
    t.increment!(:questions_count)
  end
end

you already have tags so no need to find them, and finding by name is slow because it doesn't use index. If you need to find records, use id or other indexed column.

@leemour
Copy link

leemour commented Jun 3, 2014

also :after_destroy doesn't work, using :before_destroy worked for me.

@asiniy
Copy link

asiniy commented Jan 12, 2015

This isn't a good code. Rails should do it automatically

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment