在开发web项目中一个我们很常见的场景就是,我们需要去跟踪模型记录的改变,要知道记录是从什么值变为什么值。Rails通过提供了ActiveModel::Dirty中的一系列方法,来很好的帮助我们解决这些问题,让我们来看看有哪些好用的方法:
首先假设我们有 Card这个模型其中有name这个属性
1. .changed? and <attribute>_changed?
这两个方法是用于判断记录或属性是否被修改,
card = Card.first
card.name # => "克洛泽"
card.changed? # => false
card.name_changed? # => false
card.name = "Klose"
card.changed? # => true
card.name_changed? #=> true
如上面代码所示,使用ActiveModel::Dirty提供的这两个方法可以很好的帮助我们,让我们知道记录或者属性是否被修改了。
2. <attribute>_was
如果我们想知道一个属性被修改前的值的话,ActiveModel::Dirty也提供了相应的方法,那就是:
card.name = 'Klose'
card.name_was # => '克洛泽'
3. changes
上面我们已经知道了如何得到一个属性被修改之前的值,那么使用changes方法,我们将会得到,所有被修改的属性及其迁移值的哈希
card.name = 'Klose'
card.changes # => {"name"=>["克洛泽", "Klose"]}
changes 返回的是以被修改的属性的名字作为键,以原有值和当前值的两个元素的数组为值的哈希。
以上是 Rails的ActiveModel::Dirty中提供的一组跟踪记录修改的方法,不过其中还有有一个非常重要的方法没有介绍。
4. previous_changes
在上面的例子中,当我们把记录持久化之后:
card.name = "Klose"
card.changed? # => true
card.save
card.changed? # => false
card.name_changed? #=> false
如你所见,changed?和其他的方法并不能跟踪已经持久化的记录,不过ActiveModel::Dirty中还是提供了
previous_changes这个方法,它能够让我们在记录已经被持久化后还能知道记录是否被修改,以及修改之前的值:
card.name = "Klose"
card.save
card.previous_changes # => {"name"=>["克洛泽", "Klose"], "updated_at"=>[Sat, 09 Apr 2016 14:56:39 CST +08:00, Sat, 09 Apr 2016 15:08:03 CST +08:00]}
previous_changes可以返回,记录被持久后,哪些值被修改了以及修改前后的值,还有修改时间,如果记录没有被改变的话,返回的是一个空的Hash。
这个方法的一个非常常见的使用场景就是,使用after_update回调来进行,对记录修改后的通知功能。
class Card < ActiveRecord
after_update :notify_if_name_changed
private
def notify_if_name_changed
notify.call if self.previous_changes['name']
end
end
当然其实这里还有另外一个方法,去实现记录修改后的通知功能,而且不使用previous_changes。
class Card < ActiveRecord
around_update :notify_if_name_changed
private
def notify_if_name_changed
changed = self.name_changed?
yield
notify.call if changed
end
end
这个解决办法中,我们使用了环绕回调方法, 该方法是被用yield分隔的两部分
- 在yield之前的代码或者发生修改之前执行
- 而在yield之后是修改已经发生后执行的
那么yield其实就是触发这个修改动作的标识,并且因为同在一个方法中被分隔的两部分的变量是在同一作用于下的,因此我们就可以使用它实现改成的功能了。
以上的几个方法是跟踪修改的实际场景中非常有用的,希望本文能够对你有帮助。