天天看點

CRUD-update

我們已經讨論了很多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