Как я могу вызвать обратный вызов after_save при использовании ‘counter_cache’?

#ruby-on-rails #callback #counter-cache #after-save

#ruby-on-rails #обратный вызов #счетчик-кэш #после сохранения

Вопрос:

У меня есть модель, в которой включен counter_cache для ассоциации:

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

class Author
  has_many :posts
end
  

Я также использую фрагмент кэша для каждого ‘автора’, и я хочу, чтобы срок действия этого кэша истекал всякий раз, когда @author.posts_count обновляется, поскольку это значение отображается в пользовательском интерфейсе. Проблема в том, что внутренние компоненты counter_cache (increment_counter и decrement_counter), похоже, не вызывают обратные вызовы для Author, поэтому у меня нет способа узнать, когда это произойдет, за исключением истечения срока действия кэша внутри Post observer (или cache sweeper), который просто не кажется таким чистым.

Есть идеи?

Ответ №1:

У меня было аналогичное требование что-то сделать при обновлении счетчика, в моем случае мне нужно было что-то сделать, если счетчик counter_cache превысил определенное значение, моим решением было переопределить update_counters метод следующим образом:

 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
  

Смотрите документацию update_counters для получения дополнительной информации.

Ответ №2:

Я тоже не смог заставить это работать. В конце концов, я сдался и написал свой собственный метод, подобный cache_counter, и вызываю его из after_save обратного вызова.

Комментарии:

1. Спасибо, Dex, я также опубликую решение, которое я придумал

Ответ №3:

В итоге я оставил cache_counter таким, каким он был, но затем принудительно увеличил срок действия кэша с помощью обратного вызова Post после создания, вот так:

 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
  

тогда force_expire_cache(author) есть метод в моем классе AuthorSweeper, срок действия фрагмента кэша которого истекает.

Ответ №4:

Что ж, у меня была такая же проблема, и я попал в ваш пост, но я обнаружил, что, поскольку обратные вызовы «after_» и «before_» являются общедоступными методами, вы можете сделать следующее:

 class Author < ActiveRecord::Base
  has_many :posts

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

Я не знаю, насколько стандартно это делается, но методы являются общедоступными, так что, я думаю, все в порядке.

Если вы хотите, чтобы кэш и модели были разделены, вы можете использовать очистители.

Ответ №5:

введите описание изображения здесь

У меня также есть требование следить за изменением счетчика. после извлечения исходного кода rails столбец counter_column изменяется с помощью прямого обновления SQL. Другими словами, это не вызовет никакого обратного вызова (в вашем случае, это не вызовет никакого обратного вызова в модели Author при обновлении Post).

в исходном коде rails столбец counter_column также был изменен обратным вызовом after_update.

Мой подход заключается в том, чтобы дать rails путь вверх, обновить counter_column самостоятельно:

 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