上一篇 [RoR] 簡單加入 Tag Cloud 標籤雲 功能 只是用最簡單的方式,建置出只有作者本人能下標籤,或者也可以設成,任何人不需登入,也可以下標籤;這些都並沒有記錄是哪個使用者下的標籤,所以還稱不上 Folksonomy ,或說利用這些標籤達到 Social、社群的功用。透過今天的例子,就可建置出像 del.icio.us 的多人標籤機制,達到類似像 Social bookmarking 中的 Social Tagging 的功能。
這一篇應是這一系列中,最複雜的一篇,主要是有關資料庫關聯的運用,但要建置這種 Social Tagging 的功能,相對於其他程式環境所要建置的複雜度,應算是比較簡單了。
del.icio.us 的多人標籤系統,可以把許多人所下的標籤與書籤做連結與結合,是很棒的構思;但要怎麼去處理標籤的資料庫的規畫或運算,想起來有點複雜;在 Tags: Database schemas 這篇文章中,介紹了幾種怎麼去做出像 del.icio.us 的多人多標籤的資料庫規畫的探討
在此利用第9日的 [RoR] 簡單完成分頁功能 中的例子,把聖經的內容,作為 Social Tagging 的對象,可以達到:不同的使用者可以對不同的聖經經文,下不同的標籤,同一經文可以看出哪些使用者下了哪些不同的標籤。
延伸 be_taggable 的功能
接續 [RoR] 簡單加入 Tag, Tagging 標籤功能 中,安裝 be_taggable 的 plugins 之後,再參考 http://ianli.com/site/HowTo/RailsStarter-BeTaggable 這篇文章的內容來做一些修改,才能夠使 Tag 系統記錄各使用者下了哪些標籤。
新增 /home/ironman/test/lib/be_taggable.rb 內容為:
require_dependency "#{RAILS_ROOT}/vendor/plugins/be_taggable/lib/be_taggable.rb"
module BeTaggable
module ClassMethods
# find all rows owned by user
def find_tagged_by(user_id, options={})
find(:all,
{:select => "DISTINCT m.*",
:joins => %(as m INNER JOIN #{Tagship.table_name} AS ts ON m.id=ts.model_id
INNER JOIN #{Tag.table_name} AS t ON ts.tag_id=t.id),
:conditions =>["ts.model_type=? and t.user_id=?", name, user_id],
:limit => 20}.merge(options))
end
alias find_all_tagged_by find_tagged_by
alias find_all_tagged_with find_tagged_with
end
module InstanceMethods
# create new or overwrite tags, without touching overlapping tags.
def tag(str, user_id)
old_tags = tag_names(user_id)
new_tags = self.class.split_tag_names(str)
(new_tags - old_tags).each{|tag|
self.tags << Tag.find_or_create_by_name_and_model_type_and_user_id(tag, self.class.name, user_id)
}
(old_tags - new_tags).each{|tag|
tag_obj = Tag.find_by_name_and_model_type_and_user_id(tag, self.class.name, user_id)
self.tags.delete(tag_obj)
tag_obj.destroy if tag_obj.tagships_count == 1 # cached 1
}
update_attribute(:tags_cache, all_tag_names.to_yaml) if respond_to? :tags_cache
end
def tag_names(user_id)
tags = self.tags.find_all_by_user_id(user_id)
tags.collect{|tag| tag.name}.sort!
end
def all_tag_names
tags.collect { |tag| tag.name }.sort!
end
end
end
修改資料庫綱要
如果之前是以 Article 為標籤的目標,先把資料庫回到前一版本 migration
rake db:migrate VERSION=200810xxxx_create_simple_captcha_data
把 20081009xxxx_add_be_taggable.rb 從 db/migrate 的目錄中刪除。
如果之前沒有建過 be_taggable 的話,在裝好 be_taggable 從這邊繼續下去:
執行 script/generate be_taggable_tables Bible
編輯 db/migrate/20081011xxxx_add_be_taggable.rb
將 create_table 之中 加入
t.column :user_id, :integer, :null => false
# 同時修改 add_column remove_column 的參數為 :bibles
add_column :bibles, :tags_cache, :string, :default => "--- []"
remove_column :bibles, :tags_cache
再 rake db:migrate 就可讓資料庫記錄 哪個使用者下什麼標籤的資料。
Console 端測試使用者的標籤功能
編輯 app/models/bible.rb 加入 be_taggable 的字樣,
同時新增一個 app/models/tag.rb
class Tag < ActiveRecord::Base
def self.userid_itemid(userid, itemid, modeltype)
find_by_sql ["select distinct tags.name from tags inner join tagships on tagships.tag_id = tags.id inner join users on user_id = tags.user_id where tags.user_id = ? and tagships.model_id = ? and tagships.model_type = ?;",userid, itemid, modeltype]
end
end
上述是用來查詢 user 在哪個項目中,下了哪些標籤,需要一些基本的 SQL 語法的基礎,就可以利用 INNER JOIN 的方式,把 tags, tagship, users 三個表格關聯起來,而獲得查詢。
執行 ./scipt/console 測試:
>> b=Bible.find(1)
=> #<Bible id: 1, engs: "Gen", chap: 1, sec: 1, txt: "起初,神創造天地。", engf: "Genesis", chinesef: "創世記", chineses: "創", sengs: "Ge", tags_cache: "--- []">
>> b.tag('創世紀,宇宙論,物種起源',1)
=> true
# 看到 該項目的 tags_cache 已將標籤加入
>> b
=> #<Bible id: 1, engs: "Gen", chap: 1, sec: 1, txt: "起初,神創造天地。", engf: "Genesis", chinesef: "創世記", chineses: "創", sengs: "Ge", tags_cache: "--- \n- !binary |\n 5Ym15LiW57SA\n\n- !binary |\n 5a6H...">
# 查詢 user id 為 1 且 Bible 的項目為 1 的標籤有哪些?
>> Tag.userid_itemid(1,1,'Bible')
=> [#<Tag name: "創世紀">, #<Tag name: "宇宙論">, #<Tag name: "物種起源">]
>>
從以上的操作,可以透過
項目.tag('標籤一,標籤二,標籤三',使用者id)
# 可將 某項目 加上 某使用者 下了哪些標籤 的記錄
Tag.userid_itemid(使用者id,項目id,'該項目所屬的Model')
# 可把 某使用者 在某項目 所下的標籤名稱 給列出來
透過上述的作法,就已把 社會標籤 的最核心的部份,透過這樣子的函數操作就可完成。接下來是如何在網頁上呈獻這些標籤項目。