0

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
Community
  • 1
  • 1
alexchenco
  • 53,565
  • 76
  • 241
  • 413

2 Answers2

1

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.

Valery Kvon
  • 4,438
  • 1
  • 20
  • 15
1

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
Valery Kvon
  • 4,438
  • 1
  • 20
  • 15
  • Thanks again, you've really helped me a lot. I did something similar: `if self.status == "Published" && self.published_at.nil?` (so the counter don't increment each time the post is saved) But for some reason the counter doesn't increment because of the `self.published_at.nil?` part. I added the related code in my **EDIT** (maybe because I'm adding a callback inside a callback?) – alexchenco Dec 29 '12 at 01:15
  • I will try your first code snippet. I think it is accomplishing the same as mine? (preventing the counter to increase when the post is edited and saved. – alexchenco Dec 29 '12 at 01:18
  • 1
    Be careful. Remember, you can update post which wasn't published before, and counter has to be incremented, or you can make it unpublished and counter has to be decremented, you can just assign tag (create Tagging model) and Tagging model has to know whatever published post or not... Cover all cases! – Valery Kvon Dec 29 '12 at 02:30
  • Thanks! I don't completely understand the code but I will study it. One question: if I use what's in `post.rb` I also need to add the code in `tagging.rb` right? – alexchenco Dec 29 '12 at 03:27
  • Not sure if I misunderstood, but I don't want to increment the counter if the post is "Draft." – alexchenco Dec 29 '12 at 03:31
  • yes, my conception of post's code uses tagging's code, you have to put it everywhere. About second question. Which part of code are you talking about? – Valery Kvon Dec 29 '12 at 03:34
  • Oh, not the code but your comment: `you can update post which wasn't published before, and counter has to be incremented, ` the `status` can be `Draft` and `Published`. The counter shouldn't increment if the post is `Draft`. I think your code did't cover that? – alexchenco Dec 29 '12 at 03:40
  • Thats right. "which wasn't published before". Imagine you have post in draft. Counters hasn't to be changed in this case. Then you have edited post and made it public! What now? Since it public it has to change counters of tags. Right? – Valery Kvon Dec 29 '12 at 03:53
  • Yes, ah sorry, I'ma bit confused now, so your code covered that? – alexchenco Dec 29 '12 at 03:55
  • Hey thanks the code worked. The only problem is that the counter doesn't decrement when I change the status from "Published" to "Draft". – alexchenco Dec 29 '12 at 04:07
  • 1
    updated with if !post.published? || post.decrement_tag_counters? – Valery Kvon Dec 29 '12 at 04:14
  • check there http://stackoverflow.com/questions/14078546/why-is-this-callback-with-if-statement-failing-when-i-check-for-nilness )))) – Valery Kvon Dec 29 '12 at 04:20