在Rails中多對多關聯通過在關聯表對應的類中聲明has_and_belongs_to_many來實現。
在數據庫中,多對多關聯使用中間表來實現,表中包括關聯表的主鍵,Active Record假定這個中間表的名字是由關聯表的名字根據字母的順序串聯起來得到的。例如,關聯表為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列,Active Record在訪問某個行時會自動包含所有的列。如果包含了一個id列,那麼這個id列就會復寫掉在關聯表中的id列。
The has_and_belongs_to_many() 聲明
has_and_belongs_to_many在很多方面很像has_many,has_and_belongs_to_many創建了本質上是一個集合的屬性,該屬性支持和has_many相同的方法。
也許我們使用Rails來寫一個社區站點,在這裡用戶可以閱讀文章。這裡有很多的用戶和文章,而且任何一個用戶都可以閱讀多個文章,為了跟蹤,我們希望知道誰讀了哪些文章,每篇文章有誰閱讀過,我們也希望知道用戶最後一次在什麼時間閱讀了哪篇文章,我們會這樣設計表:
我們這樣設置兩個Model類互相關聯:
class Article < ActiveRecord::Base has_and_belongs_to_many :users # ... end class User < ActiveRecord::Base has_and_belongs_to_many :articles # ... end
這樣我們就可以列出所有閱讀過文章123的用戶和名為pragdave的用戶閱讀的所有文章:
# Who has read article 123? article = Article.find(123) readers = article.users # What has Dave read? dave = User.find_by_name("pragdave") articles_that_dave_read = dave.articles
當我們的程序通知某個人閱讀了某篇文章的時候,將user記錄和article記錄建立關聯,我們調用下面的方法:
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( )和<<方法的作用一樣,都是給兩個Model之間設置連接,而且還賦值給中間表記錄什麼人在什麼時間閱讀了文章。
注:如果該方法難以理解,可以想象一下C#中使用反射給某個對象的字段賦值,我們需要提供對象,對象的字段名,字段對應的值來進行操作。
作為一種的關聯方法,has_and_belongs_to_many支持一系列聲明來復寫Active Record的默認設置::class_name, :foreign_key和:conditions,和其他的has_方法一樣(:foreign_key設置中間表中的外鍵的名字)。進一步說,has_and_belongs_to_many支持復寫中間表的名字,外鍵列的名字,find,insert,delete中使用的SQL,詳細請參考Rdoc。