一對一關聯,或者更正確的說是一對零或一對一關聯,是通過外鍵引用到另外一張表中的至多一條記錄實現的,下圖描述了orders表和invoices表的關系:
在Active Record中,要表示這樣的關系需要在Order類中添加has_one:Invoice聲明,並且同時在Invoice類中添加聲明belongs_to:order,事實上,我們可以把這種關聯關系看作是相互的,我們可以使Invoice有一個Order,也可以使Order有一個Invoice,不同的是,在將對象存儲到數據庫時,如果我們給一個對象賦予has_one關聯給另一個既存對象,關聯的對象將會自動被保存。例如:
an_invoice = Invoice.new(...) order.invoice = an_invoice # invoice gets saved
如果我們給一個對象賦予belongs_to關聯到另一個對象,那麼它將不會自動被保存,例如:
order = Order.new(...) an_invoice.order = order # Order will not be saved
還有另外一個不同點,當你給一個對象賦予has_one關聯時,如果指向一個既存的子對象,這個既存對象的外鍵關聯將會被移除,也就是清零,如下圖:
還有一個危險的地方,如果子記錄不能被保存(沒有通過驗證等),Active Record也不會有抱怨,你也不會得到任何信息來指示該記錄沒有添加到數據庫,所以,我們強烈推薦使用下面的方法:
invoice = Invoice.new # fill in the invoice unless invoice.save! an_order.invoice = invoice
因為save!方法在失敗的時候會拋出異常,這樣我們就知道發生了什麼。
belongs_to聲明
belongs_to聲明給一個類指定父關聯,Active Record約定在這個表中包含有引用到另一個表的外鍵,父類的名字假定為混合大小寫,且單數,外鍵字段為單數,並且在末尾添加_id,所以,下面的代碼:
class LineItem < ActiveRecord::Base belongs_to :product belongs_to :invoice_item end
Active Record關聯line item到類Product和InvoiceItem,在底層,使用外鍵product_id和invoice_item_id關聯到products和invoice_items表的id列。也可以像下面這樣,給belongs_to一個哈希(hash):
class LineItem < ActiveRecord::Base belongs_to :paid_order, :class_name => "Order", :foreign_key => "order_id", :conditions => "paid_on is not null" end
在上面的代碼裡,我們創建了一個關聯,叫做paid_order,引用了Order類,通過order_id關聯,並且paid_on字段不為null,在這種情況下,我們的關聯不直接映射到line_items表的單一的列。belongs_to()方法創建了一組實例方法來管理關聯,方法名都以關聯的名字開頭,例如:
item = LineItem.find(2) # item.product is the associated Product object puts "Current product is #{item.product.id}" puts item.product.title item.product = Product.new(:title => "Advanced Rails", :description => "...", :image_url => "http://....jpg", :price => 34.95, :date_available => Time.now) item.save! puts "New product is #{item.product.id}" puts item.product.title
運行後我們會得到下面的輸出:
Current product is 2 Programming Ruby New product is 37 Advanced Rails
我們使用了在LineItem類中生成的方法product()和product=(),來訪問和更新關聯到line item對象上的product對象。在背後,Active Record保存數據庫的步調一致,在我們保存line item對象的時候自動保存關聯的product對象,並且將具有新的id的product對象和line item對象關聯起來。
在這種情況下,下面的方法將被生成到line item對象中:
product(force_reload=false):
返回關聯的product(如果沒有關聯的對象就返回nil),同時,結果將被緩存,對於相同的查詢,將不會到數據庫再次執行,除非force_reload參數為true。
product=(obj)
將指定的對象關聯到line item,設置line item對象的外鍵到product對象的主鍵,如果product對象還沒有保存,那麼會在line item對象保存的同時,對product對象進行保存。
build_product(attributes={})
使用指定的attribute,構建一個新的product對象,line item對象將鏈接到該對象,而且,該對象還沒有保存。
Create_product(attributes={})
和上面的build_product方法基本相同,差別在於product對象會被保存。
has_one聲明
has_one聲明指定一個類為聲明所在類的子類(這裡的子類不是繼承的概念,而是與數據庫結構相對應的主從關系),has_one定義了一組和belongs_to相同的方法,所以下面的代碼:
class Order < ActiveRecord::Base has_one :invoice end
我們可以這樣:
order = Order.new invoice = Invoice.new if invoice.save order.invoice = invoice end
我們也可以通過傳遞一組參數來改變Active Record的默認行為,例如::class_name,:foreign_key和:conditions,就和前面介紹belongs_to時的一樣,也可以使用:dependent和:order。
:dependent的含義是,在從表中的記錄不能獨立於主表中的記錄而存在,也就是說,如果你刪除了父記錄,而且你定義了:dependent= true,Active Record將自動刪除從表中關聯的記錄。
:order指定了在記錄被返回前,怎樣進行排序,我們會在後面關於has_many的內容裡詳細討論。