3

I have two models I link together using a polymorphic has_many through association and I would like to add a counter_cache but it seems Rails/ActiveRecord does not support this feature out of the box.

class Classifiable < ActiveRecord::Base
  has_many :classifications, :as => :classifiable, :foreign_key => :classifiable_id
end

class Taxonomy < ActiveRecord::Base
  has_many :classifications, :as => :taxonomy, :foreign_key => :taxonomy_id
end

class Question < Classifiable
  has_many :categories, :through => :classifications, :as => :classifiable, :source => :taxonomy, :source_type => "Category"
end

class Category < Taxonomy
  has_many :questions, :through => :classifications, :source => :classifiable, :source_type => "Question"
end

class Classification < ActiveRecord::Base
  attr_accessible :classifiable, :classifiable_id, :classifiable_type,
                  :taxonomy, :taxonomy_id, :taxonomy_type

  belongs_to :classifiable, :polymorphic => true
  belongs_to :taxonomy,     :polymorphic => true
end

3 Answers 3

10

Simply modify your Classification model for the following:

class Classification < ActiveRecord::Base
  attr_accessible :classifiable, :classifiable_id, :classifiable_type,
                  :taxonomy, :taxonomy_id, :taxonomy_type

  belongs_to :classifiable, :polymorphic => true
  belongs_to :taxonomy,     :polymorphic => true

  before_create  :increment_counter
  before_destroy :decrement_counter

  private

  # increments the right classifiable counter for the right taxonomy
  def increment_counter
    self.taxonomy_type.constantize.increment_counter("#{self.classifiable_type.downcase.pluralize}_count", self.taxonomy_id)
  end

  # decrements the right classifiable counter for the right taxonomy
  def decrement_counter
    self.taxonomy_type.constantize.decrement_counter("#{self.classifiable_type.downcase.pluralize}_count", self.taxonomy_id)
  end
end

Also, make sure you have the following columns in your taxonomies table:

t.integer :questions_count,           :null => false, :default => 0
t.integer :other_classifiables_count, :null => false, :default => 0
t.integer :other_classifiables_count, :null => false, :default => 0
t.integer :other_classifiables_count, :null => false, :default => 0

Change "other_classifiables_count" to what you need ("answers_count", "users_count", etc.)

0
3

It seems like Rails does not go through the before/after_destroy callbacks when calling delete (what happens when you remove a has many through association).

Instead, you can use the association's callbacks #before_add and #before_remove:

class Question < Classifiable
  has_many :categories, through: :classifications, 
                        as: :classifiable, 
                        source: :taxonomy, 
                        source_type: Category,
                        before_add: :increment_counter

  def increment_counter(category)
    # increment counter, etc.
  end

end
2

To modify Jonathan's answer a bit, you could make it look up the column type to see if it exists before incrementing/decrementing. I also DRYed it up a bit:

def increment_counter(direction=:increment)
  ar_class  = self.taxonomy_type.constantize
  ar_column = "#{self.taxonomy_type.underscore.pluralize}_count"

  if ar_class.columns.include? ar_column
    ar_class.send "#{direction}_counter", ar_column, self.taxonomy_id
  end
end

def decrement_counter
  increment_counter :decrement
end

Oh and it works with MultiWordClassNames. underscore does a downcase so my version omits it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.