一對多關聯可以使我們表示一組對象,例如,一個order可以包含有任意多個line item,在數據庫中,所有的line item記錄都通過外鍵關聯到特定的order。
在Active Record中,通過在父對象中的has_many來定義到子對象的關聯,在子對象中使用belongs_to來指定父對象。我們已經在上一篇中了解了belongs_to聲明,實際上,在一對多的情況下,和一對一是相同的,所以我們來了解has_many聲明。
has_many聲明
has_many聲明了一個屬性,其行為就像一組子對象,可以把它看作數組來訪問,查詢特定的對象,或者添加新對象。例如:
order = Order.new params[:products_to_buy].each do |prd_id, qty| product = Product.find(prd_id) order.line_items << LineItem.new(:product => product, :quantity => qty) end
追加操作符(>>)所作的不僅僅是向order的line_items列表中追加一個對象,並且和設置了line_item對象外鍵值為order對象主鍵值,而且在order對象保存的時候,會同時保存line_item對象。
我們可以像對數組一樣對has_many關聯進行循環:
order = Order.find(123) total = 0.0 order.line_items.each do |li| total += li.quantity * li.unit_price end
和has_one一樣,我們可以改變Active Record的默認形式,我們可以給has_many一組設定,:class_name,:foreign_key,:conditions,:order和:dependent和has_one中的是一樣的,has_many還有:exclusively_dependent,:finder_sql,:counter_sql。
has_one和has_many都支持:dependent,這告訴Rails在刪除主表中記錄的同時刪除從表中對應的記錄。也就是對所有外鍵為刪除的主記錄的id的子對象調用它們destory方法。
無論如何,如果從表僅僅和主表關聯,而沒有其他表關聯,而且沒有任何鉤子方法在刪除的時候執行操作,你可以使用:exclusively_dependent來代替:dependent,在這種情況下,對於所有的子記錄會使用一條sql語句來刪除,這樣執行速度會快一點。
你還可以通過使用:finder_sql和:counter_sql設定來復寫Active Record用來對子記錄進行查詢和計數的sql。在:conditions設定不足夠的情況下,這兩個設定就很有用了。例如:
class Order < ActiveRecord::Base has_many :rails_line_items, :class_name => "LineItem", :finder_sql => "select l.* from line_items l, products p " + " where l.product_id = p.id " + " and p.title like '%rails%'" end
:counter_sql設定用來復寫Active Record用作計算行數的sql,如果:finder_sql被指定了,但是:counter_sql沒有指定,Active Record將會根據查詢sql來替換計算行數的sql。
:order設定指定了從數據庫中選出的記錄的排序所使用的sql,如果你在遍歷記錄集的時候需要有特定的排序,你就需要指定:order,也就是設定sql語句的order by部分,默認情況下,所有的字段都是升序。例如:
class Order < ActiveRecord::Base has_many :line_items, :order => "quantity, unit_price DESC" end
現在再回到has_one聲明,我們前面提到過也支持:order,但是一條父記錄最多只有一條子記錄,那麼為什麼要支持:order來允許指定排序條件呢?想象一下這樣的場景,一個用戶可能有多條訂單,但是,如果我們想查看該用戶的最新一條訂單呢?這時候,我們就可以使用has_one聲明了:
class Customer < ActiveRecord::Base has_many :orders has_one :most_recent_order, :class_name => 'Order', :order => 'created_at DESC' end
上面的代碼創建了一個新屬性:most_recent_order,將會引用到該用戶的最新一條訂單,我們可以這樣使用它:
cust = Customer.find_by_name("Dave Thomas") puts "Dave last ordered on #{cust.most_recent_order.created_at}"
實際上,Active Record執行了下面的sql:
SELECT * FROM orders WHERE customer_id = ? ORDER BY created_at DESC LIMIT 1
根據前面我們學習的find方法,可以看到只取了排序後的第一條記錄。
has_many添加的方法
就像belongs_to和has_one,has_many也添加了一組屬性相關的方法在它所在的類中,下面我們來看看這些方法,我們的聲明是這樣
class Customer < ActiveRecord::Base has_many :orders end
l orders(force_reload=false)=
返回一個和用戶關聯的訂單的數組,結果集是被緩存的,對於相同的查詢,不會再次從數據庫提取數據,除非force_reload = true。
l orders <<order
添加訂單到指定用戶的訂單的列表中。
l orders.push(order1, ...)
添加一個或多個訂單對象到用戶的訂單列表中,concat()是該方法的別名。
l orders.delete(order1, ...)
從用戶的訂單列表中刪除一個或多個訂單,但是不會刪除數據庫中對應的記錄,只是將它們的customer_id外鍵設置為null,斷開和用戶的關聯。
l orders.clear
分離用戶和訂單,就像delete(),但是如果訂單被指明為:dependent,數據庫中對應的記錄就會被刪除。
l orders.find(options...)
發出一個find()調用,但是僅返回用戶關聯的訂單。
l orders.build(attributes={})
構造一個新的order對象,用給定的屬性初始化,並且關聯到customer,該對象沒有保存。
l orders.create(attributes={})
構造並且保存一個新的order對象,用給定的屬性初始化,並且關聯到customer。