Reputation: 55323
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
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
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