acts-as-taggable-on のタグ件数に条件を追加する

acts-as-taggable-on を利用すると以下のように簡単にタグクラウドの実装が出来てしまいます。
mbleigh/acts-as-taggable-on: A tagging plugin for Rails applications that allows for custom tagging along dynamic contexts.

<% tag_cloud(@tags, %w(css1 css2 css3 css4)) do |tag, css_class| %>
  <%= link_to tag.name, { :action => :tag, :id => tag.name }, :class => css_class %>
<% end %>

 

内部的には

counter cache を利用して、都度件数を算出することがないようにしています。

# acts-as-taggable-on-5.0.0/lib/acts_as_taggable_on/tagging.rb

module ActsAsTaggableOn
  class Tagging < ::ActiveRecord::Base #:nodoc:
    DEFAULT_CONTEXT = 'tags'
    belongs_to :tag, class_name: '::ActsAsTaggableOn::Tag', counter_cache: ActsAsTaggableOn.tags_counter

 

Counter Cache には、条件を追加できない

ところが、デフォルトでは Counter Cache に条件を追加することはできません。
例えば、記事を表示するようなブログの場合、まだpublishedしていない記事の場合は、tagの件数に含めたくないですよね。

いくつか解決策はあるようで、以下のgemを利用すれば条件の追加ができるようになります。

 
ただ、acts-as-taggable-on のデータ構造は、汎用的に作られたタグ管理のため、私が望む形で利用することができませんでした。(使い方間違っただけかもしれませんが)

他の解決策

例えば、counter cache をやめる。
counter cache の自動更新ではなく、Active Record の callback を利用し任意のタイミングで更新させる。
邪悪な感じがしますが、これで対応して見ました。

やり方

1. counter cache を止める

counter cacheの機能自体は、以下で止めることができます。
taggings_count カラムはそのまま利用することにします。
Home · mbleigh/acts-as-taggable-on Wiki

ActsAsTaggableOn.tags_counter = false 

2. callback で更新する

タグ付け対象のモデル

class Article < ApplicationRecord
  after_update :update_counter_cache

  private
    def update_counter_cache
      if saved_change_to_attribute?(:status)
        taggings.each { |tagging| tagging.update_counter_cache }
      end
    end

タグ管理テーブル

# app/lib/core_ext/acts_as_taggable_on/tagging.rb
# config/initilaizers で読み込むようにしています
module ActsAsTaggableOn
  class Tagging
    after_save :update_counter_cache
    after_update :update_counter_cache

    def update_counter_cache
      article_ids = ActsAsTaggableOn::Tagging.where(tag_id: self.tag_id).pluck(:taggable_id)
      published_article_count = Article.published.where(id: article_ids).count
      self.tag.update!(taggings_count: published_article_count)
    end
  end
end