這篇文章可以視為「如何在多對多關係中,記錄額外的資訊」。其實一般想要在 M:N 之間的關係記錄額外的資訊,應該是會透過 has_many :through 比較恰當,不過因為當初我在寫好友名單時,先用了 上一篇文章 的寫法,所以才找到本篇文章要談的作法。

簡單來說,如果要記錄的資訊只有一個欄位,用本篇作法是快又有效;否則,我認為還是用 has_many :through 應該比較方便。後者的作法下次再談。

常見的社群網站,建立好友之間的關係不外乎是加入好友、並通知對方,似乎比較少有可以替好友分群組、加上描述的功能,其中丁丁大站就是可替好友們加上描述以及分組。這篇文章就是教你如何像丁丁大站一樣,用簡單的程式建立好友關係並加上簡單的描述。

首先一樣是你必須有一張記錄 Friendship 的 Table,偷用上一篇的 Migration,與 上一篇 不同的是,這裡多了 description 欄位。

class AddFriendship < ActiveRecord::Migration
  def self.up
    create_table :friendships, :id => false do |t|
      t.column :user_id, :integer, :null => false
      t.column :friend_id, :integer, :null => false
      t.column :description :string
    end
  end

  def self.down
    drop_table :friendships
  end
end

接下來一樣是定義 model,一樣是拿上一篇的程式,多了三行程式:

  • attr_accessor :description,由於 ActiveRecord 的 Class 在 mapping 到資料庫時,是直接對應到 table 的 column,但目前 users table 沒有 description 這個 column,因此我們在此給 User 這個 class 一個屬性,等一下在建立好友關係時就可以一併連好友描述都塞到 friendships 裡面。
  • :insert_sql,因為除了 ActiveRecord 會幫我們找到 foreign_key 所屬的欄位之外,我們還需要塞入額外的資訊,因此自訂語法。
  • after_find 的定義,由於 ActiveRecord 撈出資料後,User 這個 model 本身沒有 description 欄位,所以我們在撈出好友關係的時候,再把 description 寫到剛剛建立好的屬性。
class User < ActiveRecord::Base
  attr_accessor :description
  has_and_belongs_to_many :friends,
    :class_name => "User",
    :join_table => "friendships",
    :association_foreign_key => "friend_id",
    :foreign_key => "user_id",
    :insert_sql => 'INSERT into friendships (user_id, friend_id, description) VALUES (#{id}, #{record.id},

如此一來,你就可以用下列的程式來建立、取得好友及描述

u = User.create(:name => "linyiru")
k = User.create(:name => "punk")
u.description = "Rails 愛好者: linyiru"
k.friends <<u unless k.friends.include?(u)

# 輸出好友描述
k.friends.each do |friend|
  puts friend.description
end

註:description 的字串丟進去之前要記得先處理,避免發生不必要的問題,例如 SQL injection 之類的。