ActiveModel Dirty详解

作者:周星 发布:2015-01-11

       在做 Rails 项目时,我们经常会有这样的需求,根据一个对象的某个属性值是否变化来做不同的行为,比如如果 @user 对象的 name 属性改变了,我们就记录日志,当然我们可以使用一个临时变量来达到我们的需求:

_name = @user.name
# do something
if @user.name != _name
# name 属性的值变了
else
# name 属性的值没变
end

但是 Rails 的ActiveModel::Dirty提供给了我们更便捷、更强大的方式来“监听”对象是否"changed",下面我们举例如何使用它:

先获取一个 User 对象,看它是否已经"changed"

user = Person.find_by(name: 'Edward')
user.changed?       # => false

通过在 user 对象上执行 changed? 方法,我们可以知道一个对象是否改变了,在上面的例子中,我们并没有在 user 对象上做任何赋值操作,所以调用 changed? 方法返回值为 false。

下面我们来改变 user 对象的 name 属性:

user.name = 'Bob'
user.changed?       # => true
user.name_changed?  # => true
user.name_changed?(from: "Edward", to: "Bob") # => true
user.name_was       # => "Edward"
user.name_change    # => ["Edward", "Bob"]
user.name = 'Bill'
user.name_change    # => ["Edward", "Bill"]

       当我们给 name 属性赋一个新的值后调用 changed? 方法,发现 user 对象变了,changed? 返回值为 true,接下来我们调用了一些关于字段变化的方法,方法名为:属性名_方法,name_changed? 判断 name 属性是否变化了,它可以接收一个 hash 作为参数,name_was 方法返回 name 属性改变前的值,name_change 方法返回一个数组,数组中的元素分别为改变前的值和改变后的值。你可能会好奇,Rails 怎么知道我的 user 有一个 name 属性,并且定义了 name_changed/name_was 方法呢?这个问题不在本次分享之内,不过我建议您去了解一下元编程。
恭喜您收获了这么多方法,不过您是否注意到,我们的 user 并没有 save,如果在 save 了之后的对象上调用这些方法,会怎么样呢?

user.save
user.changed?       # => false
user.name_changed?

       很显然,之前的方法不能判断一个 save 后的对象改变了哪些属性值,不过我们仍然可以获得 user 对象的变化,只要这个对象没有 reload。

user.previous_changes # => {"name" => ["Edward", "Bill"]}
user.reload!
user.previous_changes # => {}

Rails4.2 的 ActiveModel 提供了 rollback! 方法,它可以重置所有的"change"

user.name = "Uncle Bob"
user.rollback!
user.name           # => "Edward"
user.name_changed?  # => false

       我们再深入探究一下,user 的 name 属性值为 "Edward",我们如果再手动给赋值为原值("Edward"),调用 name_changed 和 name_change 方法会返回什么呢

user.name = 'Edward'
user.name_changed?  # => false
user.name_change    # => nil

上面的例子都是关于一个属性值的操作,如果我们改变了很多属性,如何获取对象的属性变化呢?请看下面的例子:

user.name = 'Bob'
user.email = "test@qq.com"
user.changed        # => ["name", "email"]
user.changes        # => {"name" => ["Edward", "Bob"], "email" => ["edward_mjz@hotmail.com", "test@qq.com"]}

changed 方法以数组的形式返回哪些属性发生了变化,changes 方法以 hash 的形式返回发生变化的详细情况。

如果一个属性是在原值的基础上变化,你会用到 属性值_will_change! 方法:

user.name << 'zhou'
user.name_change       # => nil 注意是nil
user.reload            # => 重置
user.name_will_change!
user.name_change       # => ["Edward", "Edward"]
user.name << 'zhou'
user.name_change       # => ["Edward", "Edwardzhou"]

如果你想在自己的 ruby 程序中使用 Dirty,你需要include ActiveModel::Dirty

class Person
  include ActiveModel::Dirty

  define_attribute_methods :name

  def name
    @name
  end

  def name=(val)
    name_will_change! unless val == @name
    @name = val
  end

  def save
    # do persistence work
    changes_applied
  end
  def reload!
    # get the values from the persistence layer
    clear_changes_information
  end

  def rollback!
    restore_attributes
  end
end

如果您想了解更多,请前往 Rails API官网,如果您对本文由什么意见或建议,请联系博主。

支付宝扫码赞助博主


评论(2)

lhy-fans

lhy-fans第1楼

谢谢星哥的dirty 这让我想起了观察者模式,去观察一个对象的状态执行不同的操作; 再次谢谢星哥的耐心讲解,获益匪浅。

2015-01-11 22:06:31

周星

周星第2楼

谢谢洋仔,你也教了我很多知识,现在没开发留言回复功能,暂时只能这样回复了

2015-01-11 22:37:11