acts_as_taggable_on_steroidsの使い方まとめ

モデルにタグ付けの機能がつくacts_as_taggableの機能追加版であるacts_as_taggable_on_steroidsの使い方を整理してみた。

  • 導入
  • モデルにタグ機能をつける – acts_as_taggable
  • モデルオブジェクトへのタグ付けと参照 – tag_list
  • タグの付いているモデルオブジェクトを取得する – find_tagged_with
  • タグの集計リストを取得する – tag_counts
  • タグクラウド – tag_cloud
  • タグのデリミタを変更する – TagList.delimiter
  • タグのキャッシュと使われなくなったタグの削除

導入

プラグインをインストールしてマイグレーションでデータベースの準備。

プラグインのインストール
$ ruby script/plugin install http://svn.viney.net.nz/things/rails/plugins/acts_as_taggable_on_steroids
データベースの準備
$ ruby script/generate acts_as_taggable_migration

これでマイグレーション用のファイルができるのでマイグレーション。

$ rake db:migrate

導入は以上。

モデルにタグ機能をつける – acts_as_taggable

モデルに「acts_as_taggable」と一行追加。

class Item < ActiveRecord::Base
  acts_as_taggable
end

これでそのモデルのオブジェクトにタグ付けできるようになる。

モデルオブジェクトへのタグ付けと参照 – tag_list

tag_listを介してタグ付けと付けたタグの参照ができる。

タグ付け

tag_listにタグのリストを代入するとモデルオブジェクトにタグが付く。
モデルオブジェクトをsaveするとタグも保存される。

item.tag_list = "テント, 設営器具, ペグ"
# => ["テント", "設営器具", "ペグ"]
# item.tag_list = ["テント, "設営器具", "ペグ"] でもよい
item.save

以下タグのリストに関する仕様。

  • タグのリストは配列でも文字列でも文字列で指定する
  • 文字列で指定した場合のデリミタのデフォルトはコンマ
  • 重複タグは自動的にユニークになる
  • タグの前後の空白は取り除かれる
  • 空タグ(nilや空文字列)は取り除かれる
タグの参照

付けたタグはtag_listで参照できる。返値は配列。

item.tag_list
# => ["テント", "設営器具", "ペグ"]

文字列で欲しい場合はto_s。デリミタで区切られた文字列で返される。

item.tag_list.to_s
# => "テント, 設営器具, ペグ"

デリミタが半角スペース以外の時にはデリミタとタグの間に半角スペースが入った形になる。

タグリストの変更 – add, remove, toggle

タグリストへの追加や削除を行うためのメソッドが用意されている。
モデルオブジェクトにすでにタグが付いているときなど、既存のタグリストに対して簡単にタグの追加や削除を行ったりすることができるので便利。

tag_list.addで追加。

item.tag_list.add("Coleman, コールマン")
# => ["テント", "設営器具", "ペグ", "Coleman", "コールマン"]

tag_list.removeで削除。
指定したタグがタグのリストの中に存在していなくてもエラーにはならない。

item.tag_list.remove("Coleman, コールマン")
# => ["テント", "設営器具", "ペグ"]

tag_list.toggleでトグルなんてこともできる。

item.tag_list.toggle("スチール製")
# => ["テント", "設営器具", "ペグ", "スチール製"]

item.tag_list.toggle("スチール製")
# => ["テント", "設営器具", "ペグ"]

タグの付いているモデルオブジェクトを取得する – find_tagged_with

find_tagged_withを使うと付いているタグからレコードを取得できる。
引数にはタグを指定する。

items = Item.find_tagged_with("テント")
# => 「テント」のタグが付いているレコードを取得

find_tagged_withには通常のfindと同じく:conditions、:order、:limitなどを設定することが可能。

items = Item.find_tagged_with("テント", :conditions => ["maker_id = 1"], :order => 'price', :limit => 20)
複数のタグを指定してレコードを取得

複数のタグを指定すると、デフォルトではそれらタグのどれかがついているレコードを取得する。
つまりOR条件での取得になる。

items = Item.find_tagged_with("テント, タープ")
# => 「テント」もしくは「タープ」のいずれかのタグを持つレコードを取得

AND条件っぽく取得したい場合はオプションで「:match_all => true」を指定する。
すると指定されたタグがすべて付いているレコードのみ取得する。

items = Item.find_tagged_with("テント, 本体", :match_all => true)
# => 「テント」でかつ「本体」両方のタグを持つレコードを取得
ページネーション

取得したデータのページネーションはwill_paginateを使っていると簡単。
find_tagged_withに続けてpaginateを使う。

コントローラには次のように書く。:conditionsや:orderなどはfind_tagged_withの方に含める。

@items = Item.find_tagged_with('テント', :order => :price).paginate(:page => params[:page], :per_page => 20)

ビューで次のように書く。

<%= will_paginate(@items) %>

参考:will_paginateに移行 – ひげろぐ

コントローラではpaginate_tagged_withというメソッドを使ってもいいのだがその場合は「:match_all => true」とあわせて使うことができないようだ。

@items = Item.paginate_tagged_with('テント', :page => params[:page], :order => :price, :per_page => 20)

タグの集計リストを取得する – tag_counts

タグクラウドのようなタグの一覧を作ったりするためにタグの使用回数が集計されたリストを取得することができる。

すべてのタグを取得

Tag.countsですべてのタグの集計を取得できる。

返値はTagモデルオブジェクトの配列。
個別のタグにはタグ名と使われている回数の情報が含まれており、それらはTagモデルのオブジェクトのメソッドで取得できる。

name タグ名
count タグの使われている回数
tags = Tag.counts
p "#{tags.first.name}(#{tags.first.count})"
# => テント(123)

複数のモデルにタグ機能を付けている場合はすべてのモデルに付いているタグが全部出てくる。
なのでこの取得の仕方はあまりしない方がいいだろう。

複数のモデルをまたいでタグの集計リストを取得するという明確な意図がある場合は別だが。

あるモデルのレコードに付けられているすべてのタグを取得

モデルのクラスメソッドでtag_countsというメソッドが使えるようになっているのでこれを使う。
そのモデルに付いているタグのみを取得するという以外はTag.countsと同じ。

tags = Item.tag_counts
p "#{tags.first.name}(#{tags.first.count})"
# => テント(123)
tag_countsのオプション

tag_countsにはいろいろオプションがある。

:start_at タグ付けされた日時が指定した日時以降のタグのみ対象に取得
:end_at タグ付けされた日時が指定した日時以前のタグのみ対象に取得
:conditions findと同じようにwhere条件を指定。モデルのカラムの他、tags.nameやtaggings.created_atなどのカラムも条件に含めることができる。
:limit 取得件数の制限。findで指定するのと同じ。
:order ソート条件の指定。findで指定するのと同じ。
:at_least タグのついているレコード数が指定した数値以上のタグのみを対象に取得
:at_most タグのついているレコード数が指定した数値以下のタグのみを対象に取得

これらを使うことでいろいろな条件で集計が可能。
タグごとにタグ付けされた日時が保存されているので例えば月間ごとにタグクラウドを出したりとかそんなこともできる。

関連と一緒に使う

こともできる。

Category.find_by_name('テント・タープ').items.tag_counts

楽でよろしい。

タグクラウド – tag_cloud

用意されているtag_cloudヘルパーを使うことでタグクラウドを簡単に作ることができる。
tag_cloudヘルパーはTagsHelperをインクルードすると使用可能となる。

tag_cloudヘルパーの引数にはTag.countsの返値とCSSクラスの配列を与え、ブロックを渡す。

サンプルで説明するとまずヘルパーをインクルードして

module ApplicationHelper
  include TagsHelper
end

ビューで次のように使う。

<% @tags = Item.tag_counts %>
<% tag_cloud(@tags, ['tag-x-small', 'tag-small', 'tag-medium', 'tag-large', 'tag-x-large']) do |tag, css_class| %>
  <%= link_to(tag.name, { :action => :tag, :id => tag.name }, :class => css_class) %>
<% end %>

対応するCSSは以下。

.tag-x-small { font-size:  80%; }
.tag-small   { font-size:  90%; }
.tag-medium  { font-size: 100%; }
.tag-large   { font-size: 120%; }
.tag-x-large { font-size: 150%; }

「@tags = Item.tag_counts」の箇所は実際にはコントローラ内に書くと思うがサンプルと言うことでひとつ。
CSSクラスの数は任意に変更でき大きさの段階を細かくしたい場合には増やせば、粗くしたい場合は減らせばよい。

タグのデリミタの変更 – TagList.delimiter

TagList.delimiterの値を変更することでタグのデリミタを変更できる。

半角スペースにしたい場合は次のように。

TagList.delimiter = " "

デリミタはいつでも変更できるが基本的に使っている途中で別のものに変更するとかは考えにくいのでconfig/environment.rbに書くのが普通だろう。

タグのキャッシュと使われなくなったタグの削除

パフォーマンスなどに関わるデータベース周りの話。

タグリストのキャッシュ

タグをモデルのテーブルに文字列としてキャッシュすることができる。
モデルについているタグを参照するときにタグテーブルにアクセスしにいかなくなるのでパフォーマンス的に助かる。

このキャッシュと使うためには単に「cached_tag_list」という文字列型のカラムをモデルのテーブルに作ればよい。

例えば以下のようなマイグレーションで。
ひとつのオブジェクトにつくタグの数が多い場合はカラムのサイズに注意すること。

class CacheItemTagList < ActiveRecord::Migration
  def self.up
    add_column :items, :cached_tag_list, :string
  end
end

カラムがあれば自動的にキャッシュされるようになる。
タグリストを変更した場合にはキャッシュの内容も自動的に変更されるので、キャッシュを使っていると意識する必要はまずない。

何らかの事情でカラムの名前を変えたければset_cached_tag_list_column_nameを使う。

class Item < ActiveRecord::Base
  acts_as_taggable

  set_cached_tag_list_column_name "tags_cache"
end

カラムが存在しなくてもエラーにはならない。
なければキャッシュが働かないと言うだけ。

使われなくなったタグを自動的に削除する

いったん作られたタグはモデルオブジェクトのタグリストから削除されて、まったく使われなくなってもtagsテーブルに残り続ける。
これが目障りなら以下のコードをどこかに書いておけば使われなくなったタグは自動的に削除される。

Tag.destroy_unused = true

config/environment.rb辺りに書いておけばよいと思う。
この削除はタグリストから削除されたタイミングで行われるので、destroy_unusedをtrueにする前に使われなくなったタグはやっぱりそのまま残り続ける。

まあパフォーマンスに影響が出ているとか何らかの問題がなければ残りっぱなしでほっといてもかまわんと思う。

参考

この投稿へのコメント

  1. kno said on 2008年8月5日 at 5:22 PM

    >>1 乙

    参考になりました。

  2. akahige said on 2008年8月5日 at 11:38 PM

    お役に立てて何よりです。

    ところでfind_tagged_withの後にpaginate使うと該当レコード全部持ってきてからページネーション処理に入るからパフォーマンス的にあんまりよくないとかけっこう前に気がついたけどめんどくて修正してなかったりします。サーセン。

  3. borealkiss said on 2008年8月7日 at 4:45 AM

    参考になりました。細かい話ですがtag_cloud関数の表示部分に不要な「(」があるようです。

  4. akahige said on 2008年8月7日 at 11:56 PM

    おお、ご指摘ありがとうございます。
    修正しておきました。
    引数は括弧で囲う派なのでむしろ末尾の「)」が抜けていたかんじです。

    基本的には試したコードを載せているんですが、ブログの編集中に変数名などを修正することがあって壊れちゃうときがあるんですよね。すみませんでした。

コメントを残す

メールアドレスが公開されることはありません。

この投稿へのトラックバック

トラックバックはありません。

トラックバック URL