前陣子寫了兩篇關於建立好友名單的文章:Rails: 建立好友名單Rails: 建立好友名單(續)加上好友描述,文中有提到,除非有特殊需求,否則建議建立多對多關係請盡量使用 has_and_belongs_to_many(habtm) 或 has_many :through 的方式來建立。

我個人比較常用到的是 has_many :through,也就是本篇的主題。

範例說明

本範例將建立物品清單管理,每個使用者擁有多個物品,例如 A 擁有電腦、手機、相機;使用者設定物品清單的時候可以建立描述,例如紀錄購入時間、價格或是其他文字描述等等。

資料庫規劃

首先建立兩個 Model 分別為 User、Item,分別對應到 Users 以及 Items 資料表,另外建立記錄兩者 relationship 的 Model 及 Table,使用 migration 來建立內容如下:

# db/migrate/001_create_users.rb
class CreateUsers < ActiveRecord::Migration
  def self.up
    create_table :users do |t|
      t.column :name, :string, :null => false
      t.column :created_at, :datetime
    end
  end

  def self.down
    drop_table :users
  end
end
# db/migrate/002_create_items.rb
class CreateItems < ActiveRecord::Migration
  def self.up
    create_table :items do |t|
      t.column :name, :string, :null => false
      t.column :description, :text
      t.column :created_at, :datetime
    end
  end

  def self.down
    drop_table :items
  end
end
# db/migrate/003_create_ownerships.rb
class CreateOwnerships < ActiveRecord::Migration
  def self.up
    create_table :ownerships do |t|
      t.column :user_id, :integer, :null => false
      t.column :item_id, :integer, :null => false
      t.column :description, :text
      t.column :created_at, :datetime
      t.column :updated_at, :datetime
    end
  end

  def self.down
    drop_table :ownerships
  end
end

Model 的程式如下,簡單來說就是透過第三個 Model 來記錄兩個 Model 之間的關係:

# app/models/item.rb
class Item < ActiveRecord::Base
  has_many :ownerships
  has_many :users, :through => :ownerships
end
# app/models/user.rb
class User < ActiveRecord::Base
  has_many :ownerships
  has_many :items, :through => :ownerships
end
# app/models/ownership.rb
class Ownership < ActiveRecord::Base
  belongs_to :item
  belongs_to :user
end

如此一來,就可以用下列指令來增加 User, Item,並且可以查詢到某 User 所有的 items,或是 擁有某 item 的 users。

peter = User.create(:name => "Peter")

deduce = User.create(:name => "Deduce")

phone = Item.create(:name => "phone")
camera = Item.create(:name => "camera")

peter.items << phone

peter.items.count
phone.users.count