我們已經讨論了很多finder 相關的方法,Active Record 關于更新記錄的操作并不多。
如果你有一個Active Record 對象(或許表示為我們的orders 表的一行),你可以通過save()方法把它寫入資料庫,如果這個對象以前是從資料庫中讀出,這個存盤的操作将更新現有記錄。否則save()将會插入一個新行。
如果一個現有記錄行被更新,Active Record 将使用它的主鍵列比對記憶體中的對象。包
含在Active Record 對象中的屬性決定了被更新的列。即使列值沒有改變,這個列也要在資料庫中去更新。例如,在下面例子中,對于資料庫表中定單為123 的行的所有值将被更新
order = Order.find(123)
order.name = "Fred"
order.save
但是,在下面例子中,Active Record 對象隻包含屬性id,name,和paytype,當對象
被儲存時,隻有這些列将被更新(注意,如果你想儲存使用find_by_sql 擷取的記錄,你必
須包括id 列)。
orders = Order.find_by_sql("select id, name, pay_type from orders
where id=123")
first = orders[0]
first.name = "Wilma"
first.save
(update_attribute,update_attributes)
除了save()方法外,Active Record 還可以讓你直接用單一的方法調用
update_attribute()改變屬性值并儲存model 對象。
order = Order.find(123)
order.update_attribute(:name, "Barney")
order = Order.find(321)
order.update_attributes(:name => "Barney", :email =>
"[email protected]")
[color=red](update,udpate_all)[/color]最後,我們使用update()和update_all()這兩個類方法來組合讀取和更新記錄行的操
作。update()方法接受一個id 參數和一個屬性集合。它擷取相應的記錄行,更新給定的屬性,把結果儲存回資料庫,并傳回model 對象。
order = Order.update(12, :name => "Barney", :email =>
"[email protected]")
你可以給update()傳入一個id 數組和一個含有屬性值的哈希表的數組,它将更新所有
相應的資料庫中的記錄行,并傳回一個model 對象數組。
最後update_all()類方法允許你指定SQL 中update 語句裡的set 和where 的子句。例
如,下面所有标題帶有Java 字樣産品的單價增加10%。
result = Product.update_all("price = 1.1*price", "title like
'%Java%'")
update_all()傳回值取決于資料庫的擴充卡,除了oracle,大部分資料庫傳回資料庫中
被改變的記錄行數。
[color=red]save()andsave!()[/color]有兩個版本的save 方法。如果model 對象是有效的并且能夠儲存的話,舊版本的save()
傳回true。
if order.save
# all OK
else
# validation failed
end
上面的代碼是檢查save 的每個調用,看看是不是你所期望的。這樣做的原因是Active
Record 比較寬松,它假定save()是在controller 的action 方法的上下文中調用,而view代碼将會負責把任何錯誤顯示給使用者。對大多數應用程式來說,基本上就是這樣來使用的。
然而,如果你想在需要的地方儲存model 對象,又想所有的錯誤自動得到處理的話,你
就直接使用save!()吧。這個方法如果碰到對象不能儲存,就抛出一個RecordInvalid 的異常。
begin
order.save!
rescue RecordInvalid => error
# validation failed
end
[color=red]樂觀鎖[/color]
在一個有多程序通路同一資料庫的應用程式中,可能會有這樣的情況:一個程序使用的
資料正慢慢變成老資料了,而另一個程序一直想更新這個記錄行。
例如,兩個程序擷取相應的一個特殊賬号的記錄行。經過幾秒間隔,兩個程序都要去更
新餘額。每個都是以初始的記錄行的内容加載一個Active Record 的model 對象。在不同的時間他們每個都使用他們各自的model 本地拷貝去更新資料庫中的記錄。結果就是一個“竟争條件”,最後一個更新記錄行的人得到他的預期值,而第一個人的改變則丢失了。這顯示
一種解決辦法是鎖住要更新的表或者行。為了阻止它人對它的通路和更新,使用鎖來克
服并發的問題,但是這是強制性措施。它假設可能會出錯于是在這種情況下就使用了鎖。對
于這種原因,此途徑通常被稱為悲觀鎖。悲觀鎖對web 應用來說是不起作用的。因為如果你
需要確定多使用者通路的一緻性,而以資料庫不停機的方式來管理悲觀鎖是非常困難的。
樂觀鎖并不是很直覺的鎖機制。相反,隻在把記錄行的更新寫回資料庫之前,它檢查并
確定沒有其他人仍在更改這個記錄行。在rails 的實作中,每個記錄行包含一個版本号。一
旦這個記錄更新,版本号就會增加。當你從你的應用程式中更新時,Active Record 會檢查表中這個記錄行的版本号和發出更新指令的model 的版本号。如果這兩個号不比對,他就放棄更新并抛出異常。
預設情況下,樂觀鎖是由包含一個叫lock_version 的integer 列的表來激活的。建立一
個新記錄時這個列應該被初始化為0,否則你應該讓它自己來控制--Active Record 會為你打理好。
讓我們看看action 内的樂觀鎖。我們将建立名為counters 的表,它包含一個簡單的count
字段及lock_version 列。
create table counters (
id int not null auto_increment,
count int default 0,
lock_version int default 0,
primary key (id)
);
然後我們在表中建立一個記錄,并把它讀取到兩個獨立的model 對象中,然後拭着分别
更新它們。
class Counter < ActiveRecord::Base
end
Counter.delete_all
Counter.create(:count => 0)
count1 = Counter.find(:first)
count2 = Counter.find(:first)
count1.count += 3
count1.save
count2.count += 4
count2.save
當我們運作時,可以看到一個異常。rails 更新count2 失敗了,因為它的值已經過時了。
/use/lib/ruby/gems/1.8/gems/activerecord-
1.9.0/lib/active_record/locking.rb:42:
in ‘update_without_timestamps':
Attempted to update a stale object (ActiveRecord::StaleObjectError)
如果你使用樂觀鎖,你需要在程式中捕獲這些異常情況。
你也可以關閉樂觀鎖:
ActiveRecord::Base.lock_optimistically = false