如何检查内存记录的数据库更改?

7
我想检查一个ActiveRecord实例是否已在数据库中发生了更改。类似于:
p1 = Product.first
p1.name #=> "some name"

p2 = Product.first
p2.name = "other name"
p2.save #=> true

p1.database_changed? #=> true

我正在比较记录的属性和已持久化的属性:
class Product < ActiveRecord::Base

  def database_changed?
    Product.find(id).attributes != attributes
  end

end

这似乎可以工作,但我想知道是否有一种内置的方法来查找数据库更改?


2
你知道“脏属性”吗? - Roman Kiselenko
2
@Зелёный 脏属性基本上是相反的 - 它们跟踪内存中的更改,我正在寻找数据库中的更改。在上面的示例中,p1 没有更改,p1.changed? 将返回 false - Stefan
所以这很有趣 +1。 - Roman Kiselenko
@Зелёный,你的评论指引了我正确的方向,谢谢 :-) - Stefan
3个回答

3

Зелёный的评论之后,我已经审查了ActiveModel::Dirty并意识到它几乎做到了我想要的。已经有一个内存状态(记录的属性)和数据库状态(由ActiveModel::Dirty处理)。我只需要一种方法来更新数据库状态,保持内存状态不变:

def refresh
  @changed_attributes = {}
  fresh_object = self.class.unscoped { self.class.find(id) }
  fresh_object.attributes.each do |attr, orig_value|
    @changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
  end
  self
end

  • @changed_attributesActiveModel::Dirty 的哈希表,用于存储修改过的值。显然我们需要重置它。
  • fresh_object 是同一记录,从数据库中新获取(this line 来自于 reload,感谢 emaillenin)。
  • 在循环内,每个(新的)属性都与对应的(内存中的)属性进行比较。如果它们不同,则将其添加到 @changed_attributes 哈希表中。 这行代码 来自于 ActiveRecorddup 方法。(实际上来自于由 dup 调用的一个私有方法,而 _field_changed 也是私有的。最好使用 ActiveRecord 的公共 API,但我懒得做)
  • 最后,refresh 为了方便起见返回 self,就像 reload 一样。
这是一个使用示例:
p1 = Product.first
p1.name           #=> "some name"
p1.changed?       #=> false

p2 = Product.first
p2.name = "other name"
p2.save           #=> true

p1.refresh
p1.name           #=> "some name"
p1.changed?       #=> true
p1.name_changed?  #=> true
p1.name_was       #=> "other name"

p1.name = "other name"
p1.name_changed?  #=> false

p1.changed?       #=> true
p1.changes        #=> {"updated_at"=> [Tue, 29 Jul 2014 21:58:57 CEST +02:00, Tue, 29 Jul 2014 15:49:54 CEST +02:00]}

酷的 @Stefan,我首先考虑“脏”的原因是因为它是相反的。 - Roman Kiselenko

1
Rails的做法是在ActiveRecord对象上使用reload方法来实现这个功能。
  def database_changed?
    attributes != reload.attributes
  end

终端 1

2.1.2 :001 > c = Car.find(1)
  Car Load (0.4ms)  SELECT  "cars".* FROM "cars"  WHERE "cars"."id" = ? LIMIT 1  [["id", 1]]
 => #<Car id: 1, name: "Audi", model: "R8", created_at: "2014-07-29 11:14:43", updated_at: "2014-07-29 11:14:43">
2.1.2 :002 > c.database_changed?
  Car Load (0.1ms)  SELECT  "cars".* FROM "cars"  WHERE "cars"."id" = ? LIMIT 1  [["id", 1]]
 => false
2.1.2 :003 > c.database_changed?
  Car Load (0.2ms)  SELECT  "cars".* FROM "cars"  WHERE "cars"."id" = ? LIMIT 1  [["id", 1]]
 => false
2.1.2 :004 > c.database_changed?
  Car Load (0.2ms)  SELECT  "cars".* FROM "cars"  WHERE "cars"."id" = ? LIMIT 1  [["id", 1]]
 => true

终端 2

2.1.2 :001 > c = Car.find(1)
  Car Load (0.2ms)  SELECT  "cars".* FROM "cars"  WHERE "cars"."id" = ? LIMIT 1  [["id", 1]]
 => #<Car id: 1, name: "Audi", model: "R8", created_at: "2014-07-29 11:14:43", updated_at: "2014-07-29 11:14:43">
2.1.2 :002 > c.model = 'A5'
 => "A5"
2.1.2 :003 > c.save!
   (0.2ms)  begin transaction
  SQL (0.3ms)  UPDATE "cars" SET "model" = ?, "updated_at" = ? WHERE "cars"."id" = 1  [["model", "A5"], ["updated_at", "2014-07-29 11:15:32.845875"]]
   (1.2ms)  commit transaction
 => true
2.1.2 :004 >

reload会改变接收器,我更喜欢一种非破坏性的方法。 - Stefan

1
def database_changed?
  self.class.where(self.class.arel_table[:updated_at].gt(updated_at)).exists? self
end

这个检查的是表中是否有新的记录,而不是特定的记录是否被更改。 - Stefan
1
再看一遍。将“self”作为参数提供给exists将查看所讨论的记录是否以更新状态存在于数据库中。 - Kyle Decot
抱歉,参数被代码块裁剪了,你是正确的 :-) - Stefan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接