天天看点

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