iT邦幫忙

DAY 28
12

Rails 的簡單任務系列 第 30

[RoR] 小小複雜建置 Social Tagging 社會標籤功能

上一篇 [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')
# 可把 某使用者 在某項目 所下的標籤名稱 給列出來

透過上述的作法,就已把 社會標籤 的最核心的部份,透過這樣子的函數操作就可完成。接下來是如何在網頁上呈獻這些標籤項目。


上一篇
[RoR] 簡單加入 Tag Cloud 標籤雲 功能
下一篇
[RoR] 小小複雜模仿 del.icio.us 的個人化的社會標籤 per user tagging
系列文
Rails 的簡單任務33

1 則留言

0

2009/03/09遲到 11:55 到

SORRY!是感恩謝謝分享!

我要留言

立即登入留言