wyc
wyc

Reputation: 55323

How to fix these increment/decrement_tag_counter_cache methods?

I added a posts_count column to the Tag model:

  create_table "tags", :force => true do |t|
    t.string   "name"
    t.datetime "created_at",                 :null => false
    t.datetime "updated_at",                 :null => false
    t.integer  "posts_count", :default => 0, :null => false
  end

Now I'm trying to build a counter cache for them based on this question (creating a counter cache for a many-to-many association): Counter cache for a model with a many-to-many association

post.rb:

private

after_create  :increment_tag_counter_cache
after_destroy :decrement_tag_counter_cache

def increment_tag_counter_cache
  Tag.increment_counter(:posts_count, self.taggings.tag.id)
end

def decrement_tag_counter_cache
  Tag.decrement_counter(:posts_count, self.taggings.tag.id)
end

But I'm getting this when I create a Post:

 undefined method `tag' for []:ActiveRecord::Relation

I think there is something wrong with this part: self.taggings.tag.id But I'm not very sure how to fix it.

Any suggestions?

Models:

**post.rb:**

     has_many :taggings, dependent: :destroy  
     has_many :tags, through: :taggings

**tag.rb:**

    has_many :taggings, :dependent => :destroy  
    has_many :posts, :through => :taggings

**tagging:**

    attr_accessible :tag_id, :post_id

    belongs_to :post
    belongs_to :tag

EDIT

post.rb:

  before_save :publish_post

  protected

    def publish_post
      if self.status == "Published" && self.published_at.nil?
       self.published_at = Time.now
      end
    end

tagging.rb:

 private

    def increment_tag_counter_cache
      if self.post.status == "Published" && self.post.published_at.nil?
        Tag.increment_counter(:posts_count, self.tag.id)
      end
    end

Upvotes: 0

Views: 147

Answers (2)

Valery Kvon
Valery Kvon

Reputation: 4496

HOWTO increment the counter for only posts that have :status = "Published"

Post model:

after_save :increment_tag_counters # covers :save, :create, :update methods
before_destroy :correct_tag_counters

def published?
  self.status == "Published"
end

def increment_tag_counters? # if changes provided and previous status wasn't "published"
  status_changed? && changed_attributes["status"] != "Published"
end

def decrement_tag_counters? # if changes provides and previous status was "published"
  status_changed? && changed_attributes["status"] == "Published"
end

def increment_tag_counters
  if published? && increment_tag_counters?
    taggings.each {|tagging| tagging.increment_tag_counter_cache}
  elsif decrement_tag_counters?
    taggings.each {|tagging| tagging.decrement_tag_counter_cache}
  end
end

def correct_tag_counters
  if published?
    taggings.each {|tagging| tagging.decrement_tag_counter_cache}
  end
end

Tagging model:

after_create  :increment_tag_counter_cache
after_destroy :decrement_tag_counter_cache

def increment_tag_counter_cache
  Tag.increment_counter(:posts_count, self.tag.id) if post.published?
end

def decrement_tag_counter_cache
  Tag.decrement_counter(:posts_count, self.tag.id) if !post.published? || post.decrement_tag_counters? 
end

Upvotes: 1

Valery Kvon
Valery Kvon

Reputation: 4496

This example is a bit misleading...

You have to place callbacks and counting codes to the Tagging model:

private

after_create  :increment_tag_counter_cache
after_destroy :decrement_tag_counter_cache

def increment_tag_counter_cache
  Tag.increment_counter(:posts_count, self.tag.id)  #
end

def decrement_tag_counter_cache
  Tag.decrement_counter(:posts_count, self.tag.id)
end

You can leave it in Post, but in that case you have to increment counter for All assigned tags. And this code must be executed when the post model already knows its tags (depends on app logic):

def increment_tag_counter_cache
  tags.each do |tag|  # if .tags contain Tag objects... or has_many :tags, :through => :tagggings
    Tag.increment_counter(:posts_count, tag.id)
  end
end

But leave this code in Post - bad idea. What will you do when updating post? According to a code it will increment counters again. What will you do when you add new tags or remove out: you have to write special code for each case like that. It sucks.

Upvotes: 1

Related Questions