多對多關聯是關係型資料庫中常見的模式。Rails 透過 has_and_belongs_to_many (HABTM) 宣告,配合中間表,讓兩個模型之間能建立雙向的集合關聯。
Rails 預設中間表命名為兩張關聯表名按字母序串接,以底線分隔。例如 categories 與 products 的中間表為 categories_products。中間表不包含 id 欄位,主鍵為雙外鍵複合鍵:
create table categories_products ( category_id int not null, product_id int not null, constraint fk_cp_category foreign key (category_id) references categories(id), constraint fk_cp_product foreign key (product_id) references products(id), primary key (category_id, product_id) );
設計原因有二:
id,ActiveRecord 讀取時會將其覆蓋模型本身的 id,導致錯誤。class Article < ActiveRecord::Base has_and_belongs_to_many :users end class User < ActiveRecord::Base has_and_belongs_to_many :articles end
宣告後,雙方皆獲得集合存取方法:
# 閱讀過文章 123 的所有用戶
article = Article.find(123)
readers = article.users
# pragdave 閱讀過的所有文章
dave = User.find_by_name("pragdave")
articles_that_dave_read = dave.articles
若需記錄「誰在何時閱讀了哪篇文章」,可在中間表加入 read_at 欄位,並在模型中定義便利方法:
class User < ActiveRecord::Base
has_and_belongs_to_many :articles
def read_article(article)
articles.push_with_attributes(article, :read_at => Time.now)
end
end
push_with_attributes 會同時建立關聯並寫入中間表的額外欄位,類似於 C# 反射為物件動態賦值。
HABTM 支援 :class_name、:foreign_key、:conditions 等常見選項,並可自訂中間表名、外鍵名、甚至 finder_sql/counter_sql/delete_sql 等完整 SQL 行為,詳細請參考 RDoc 文件。