acts_as_taggable_redux
acts_as_taggable系列のタグ付けプラグインをいろいろチェックしていて、まあ、あるわあるわ。結局どれがいいのかよく分からないが、acts_as_taggable_on_steroidsとacts_as_taggable_reduxのどちらかを採用しようかといろいろソースを見比べている。どっちもチェックする予定だけどまずacts_as_taggable_redux。ただ、最終的にはどっちにしても自分の環境に合わせていじることは必須でしょう。
参考
Rails プラグイン acts_as_taggable_redux でタグクラウドを作ろう
railsで簡単にタグクラウドを作る
追記:
acts_as_taggable_reduxとacts_as_taggable_on_steroidsの比較に関しては以下を書きました。あわせて参考にしてください。
http://d.hatena.ne.jp/yotena/20071220/1198103145
特徴
他のブログで言われているようにtag_cloudが簡単に出来ると言う点と、taggable以外にtaggerというクラスが設定出来るのが一番の特徴といったところでしょうか。
generatorでmigrationが作れますが、これを見ても分かるようにuser_idというカラムがあるって誰(tagger)がタグ付けしたのかが分かるように設定することが前提になってますね。これで、誰(tagger)が何(taggable)に付けたTagというのがモデル化されてます。
class AddActsAsTaggableTables < ActiveRecord::Migration def self.up create_table :tags do |t| t.column :name, :string t.column :taggings_count, :integer, :default => 0, :null => false end # index関連なので省略 # create_table :taggings do |t| t.column :tag_id, :integer t.column :taggable_id, :integer t.column :taggable_type, :string t.column :user_id, :integer end # index関連なので省略 # end def self.down drop_table :tags drop_table :taggings end end
モデルクラス
上のmigrationによって追加されるテーブルに対応するモデルはTagクラスとTaggingクラス。
Tagクラスには、以下が宣言されて、あとは読めば分かるぐらいの簡単な便利メソッドがあるぐらい。
has_many :taggings
tag(taggable, user_id = nil)でtaggableにタグ付けすることと、taggedでこのタグに関連付けられたtaggingから「このタグを付けられているモノ(taggable)」を探すことが出来る。
Taggingクラスには、以下が宣言されていて、taggable_typeとtaggable_idでポリモーフィック関連があり、userモデルに紐付けされている。
belongs_to :tag, :counter_cache => true belongs_to :taggable, :polymorphic => true belongs_to :user
TaggableとTagger
acts_as_taggable宣言をしたモデルはtaggableなクラス(タグ付けされる物)になって、acts_as_tagger宣言されたモデルはtaggerなクラス(タグ付けする人)になると考えるとそのまんま。
Taggerには特にメソッド追加されていないので省略。
Taggableにあるメソッドは以下
- find_tagged_with(tags, options = {})
- find_tagged_with_by_user(tags, user, options = {})
あんまり気にしなくて使ってみたが別に問題なさげ。
- tag_list=(new_tag_list)
これはtaggableなクラスにタグを設定するメソッド。
- user_id=(new_user_id)
タグ付けした人を特定するためにuser_idを設定するメソッド。
ところでこれ、すでにuser_idというカラムがあるテーブルのモデルクラスにタグ付けするとuser_id=()メソッドを上書きしちゃう気がします。それって誰かが所有しているtaggableオブジェクトにuser_idを設定できなくなっちゃいますね。
例でいうとBookモデルにuser_id(著者だか登録者だか)があって、このモデルにuser(著者だか登録者)をセットするときに
book = Book.new book.user_id=current_user.id book.tag_list(params[:tag_list]) book.save
とかしちゃうとbooksテーブルのuser_idではなくて、タグ付けした人のuser_idになってしまうんじゃないかと。なので、とりあえず私は、acts_as_taggable宣言したクラスTaggableBookと宣言してないクラスBookを作りました。acts_as_taggable宣言されたのクラスTaggableBookでは、acts_as_taggableのuser_idメソッドを使って、クラスBookではテーブル自身のuser_idをラップしたメソッドが使われる様にです。たぶんこれで問題なさげです。もっと効率のいい方法おしえて&間違ってたら誰か教えてください。
- tag_list(user = nil)
user=nilの時に同じタグが大量にリストに入ってしまいますね。用途によるわけですが、たとえばBookモデルがあったとして、「Ruby on Rails」という本があったとして、これにたくさんの人が同じrailsタグとかを付けた場合、tag_listの中身が「rails, rails, rails, rails....RoR, RoR....」とかになった入りするわけです。なので、acts_as_taggable宣言するクラス(上の例のBookに相当)で以下の様にオーバーライドしました。
def tag_list(user = nil) if tags.size>0 tags.uniq! end unless user tags.collect { |tag| tag.name.include?(" ") ? %("#{tag.name}") : tag.name }.join(" ") else tags.delete_if { |tag| !user.tags.include?(tag) }.collect { |tag| tag.name.include?(" ") ? %("#{tag.name}") : tag.name }.join(" ") end end
(追記)さらにもろもろ問題があったので、以下のように修正。
http://d.hatena.ne.jp/yotena/20080507/1210112159
def tag_list(user = nil) unless user tags.uniq.collect { |tag| tag.name.include?(" ") ? %("#{tag.name}") : tag.name }.join(" ") else taggings.find(:all, :conditions=>['user_id=?', user.id]).map { |tagging| tagging.tag }.uniq.collect { |tag| tag.name.include?(" ") ? %("#{tag.name}") : tag.name }.join(" ") end end
- update_tags
ソースは以下のとおりなんですが、
ここは、
def update_tags if @new_tag_list Tag.transaction do unless @new_user_id taggings.destroy_all else taggings.find(:all, :conditions => "user_id = #{@new_user_id}").each do |tagging| tagging.destroy end end Tag.parse(@new_tag_list).each do |name| Tag.find_or_create_by_name(name).tag(self, @new_user_id) end tags.reset taggings.reset @new_tag_list = nil end end end
これのうち、
unless @new_user_id taggings.destroy_all else
の部分、@new_user_idがなかったらBookモデルのすべてのタグを削除してる気がします。
def update_tags if @new_tag_list Tag.transaction do # unless @new_user_id # taggings.destroy_all # else if @new_user_id > 0 taggings.find(:all, :conditions => "user_id = #{@new_user_id}").each do |tagging| tagging.destroy end end Tag.parse(@new_tag_list).each do |name| Tag.find_or_create_by_name(name).tag(self, @new_user_id) end tags.reset taggings.reset @new_tag_list = nil end end end
にしたほうがいいのかなー。あってる?
helper
- tag_cloud
acts_as_taggable_reduxの目玉機能なんですが、
tags = Tag.find(:all, :limit => 100, :order => 'taggings_count DESC').sort_by(&:name)
の部分。だれもタグ付けしてないタグ(だれかが一度付けたタグを消してしまうとtaggings_countが0になる)も出しちゃいそう(実際出てた。)なので、以下のように修正してみました。参考までに。
tags = Tag.find(:all, :limit => 100, :conditions =>['taggings_count>0'], :order => 'taggings_count DESC').sort_by(&:name)
この部分はバグというよりは、TODOコメントにあるように、使う人が自分で修正することが前提なんでしょう。
これ以外に、tag_cloud_for_taggerとかtag_cloud_for_taggableとかtag_cloud_for_taggable_and_taggerとか作ってみたらかなり便利なプラグインになりました。(あまり汎用的に作ってないのと、昨日サーフィンで疲れて眠いので、サンプルソースは要望があればいつかちょっと修正してから載せます)
以下、user_id=()メソッドに関する補足です。
ただし、補足2、3は試してないので検証してから使ってね。
補足1
わたしがとった手はこんな感じです。
# books tableはこんな感じ # id --> integerr # title --> string # user_id -->integer #メソッドuser_id(uid)はbooksテーブルのuser_idを設定 class Book < ActiveRecord::Base attr_accessible :id, :title, :user_id end #メソッドuser_id(uid)はacts_as_taggableのuser_idなので #taggingsテーブルのuser_idに設定する。 class TaggableBook < ActiveRecord::Base acts_as_taggable attr_accessible :tag_list, :user_id self.table_name = 'books' end
BookモデルもTaggableBookモデルは、どちらもbooksテーブルを扱うモデル。
ただし、
booksテーブルに行を追加したい時は、Bookモデルを使って、
タグを付けたいときにはTaggableBookモデルを使う。
補足2
user_idだけどbooksテーブルに付いてるuserはownerだ!と割り切れば、owner_idというアクセサメソッド作っちゃうのもいいかも。
class Book < ActiveRecord::Base def owner_id=(uid) write_attribute 'user_id', uid end def owner_id read_attribute 'user_id' end end
この方法だと無駄にTaggableBookなんてモデルを作らなくてもよくなる。
補足3
もうひとつ手としては、vendor配下のacts_as_taggable.rbファイル中のuser_id=()メソッドは以下の通りですが
def user_id=(new_user_id) @new_user_id = User.find(new_user_id).id end
これをいじってしまうという手になりますかね。ただし、こっちをいじっちゃうと、プラグインがバージョンアップしても自分でメンテナンスしないといけないです。
def user_id=(new_user_id) @new_user_id = User.find(new_user_id).id write_attribute 'user_id', new_user_id end
この方法でも無駄にTaggableBookなんてモデルを作らなくてもよくなる。