开发者

How can I invoke the after_save callback when using 'counter_cache'?

I have a model that has counter_cache enabled for an association:

class Post
  belongs_to :author, :counter_cache => true
end

class Author
  has_many :posts
end

I am also using a cache fragment for each 'author' and I want to expire that cache whenever @author.posts_count is updated since that value is showing in th开发者_运维技巧e UI. The problem is that the internals of counter_cache (increment_counter and decrement_counter) don't appear to invoke the callbacks on Author, so there's no way for me to know when it happens except to expire the cache from within a Post observer (or cache sweeper) which just doesn't seem as clean.

Any ideas?


I had a similar requirement to do something on a counter update, in my case I needed to do something if the counter_cache count exceeded a certain value, my solution was to override the update_counters method like so:

class Post < ApplicationRecord
  belongs_to :author, :counter_cache => true
end

class Author < ApplicationRecord
  has_many :posts

  def self.update_counters(id, counters)
    author = Author.find(id)
    author.do_something! if author.posts_count + counters['posts_count'] >= some_value
    super(id, counters) # continue on with the normal update_counters flow.
  end
end

See update_counters documentation for more info.


I couldn't get it to work either. In the end, I gave up and wrote my own cache_counter-like method and call it from the after_save callback.


I ended up keeping the cache_counter as it was, but then forcing the cache expiry through the Post's after_create callback, like this:

class Post
  belongs_to :author, :counter_cache => true
  after_create :force_author_cache_expiry

  def force_author_cache_expiry
    author.force_cache_expiry!
  end
end

class Author
  has_many :posts

  def force_cache_expiry!
    notify :force_expire_cache
  end
end

then force_expire_cache(author) is a method in my AuthorSweeper class that expires the cache fragment.


Well, I was having the same problem and ended up in your post, but I discovered that, since the "after_" and "before_" callbacks are public methods, you can do the following:

class Author < ActiveRecord::Base
  has_many :posts

  Post.after_create do
    # Do whatever you want, but...
    self.class == Post # Beware of this
  end
end

I don't know how much standard is to do this, but the methods are public, so I guess is ok.

If you want to keep cache and models separated you can use Sweepers.


How can I invoke the after_save callback when using 'counter_cache'?

I also have requirement to watch counter's change. after digging rails source code, counter_column is changed via direct SQL update. In other words, it will not trigger any callback(in your case, it will not trigger any callback in Author model when Post update).

from rails source code, counter_column was also changed by after_update callback.

My approach is give rails's way up, update counter_column by myself:

class Post
  belongs_to :author
  after_update :update_author_posts_counter

  def update_author_posts_counter
    # need to update for both previous author and new author

    # find_by will not raise exception if there isn't any record
    author_was = Author.find_by(id: author_id_was) 

    if author_was
      author_was.update_posts_count!
    end
    if author
      author.update_posts_count!
    end
  end
end

class Author
  has_many :posts
  after_update :expires_cache, if: :posts_count_changed? 

  def expires_cache
    # do whatever you want
  end

  def update_posts_count!
    update(posts_count: posts.count)
  end
end
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜