Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to check for database changes of in-memory records?

I want to check if an ActiveRecord instance was changed database-wise. Something like:

p1 = Product.first
p1.name #=> "some name"

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

p1.database_changed? #=> true

I'm currently comparing the record's attributes to the persisted attributes:

class Product < ActiveRecord::Base

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

end

This seems to work, but I'm wondering if there is a built-in way to find database changes?

like image 836
Stefan Avatar asked Jul 29 '14 10:07

Stefan


3 Answers

After Зелёный's comment I've reviewed ActiveModel::Dirty and realized that it almost does what I want. There's already an in-memory state (the record's attributes) and a database state (handled by ActiveModel::Dirty). I just need a method to update the database state, leaving the in-memory state unchanged:

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_attributes is ActiveModel::Dirty's hash to store changed values. We obviously have to reset it.
  • fresh_object is the same record, freshly fetched from the database (this line comes from reload, thanks emaillenin).
  • Within the loop, each (fresh) attribute is compared to the corresponding (in-memory) attribute. If they differ, it is added to the @changed_attributes hash. This line comes from ActiveRecord's dup method. (it's actually from a private method called by dup, and _field_changed is private, too. It might be better to use ActiveRecord's public API, but I was lazy)
  • Finally, refresh returns self for convenience, just like reload does.

Here's an example usage:

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]}
like image 112
Stefan Avatar answered Nov 19 '22 04:11

Stefan


def database_changed?
  self.class.where(self.class.arel_table[:updated_at].gt(updated_at)).exists? self
end
like image 22
Kyle Decot Avatar answered Nov 19 '22 04:11

Kyle Decot


The Rails way to to do this is to use reload method on the ActiveRecord object.

  def database_changed?
    attributes != reload.attributes
  end

Terminal 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

Terminal 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 >
like image 35
Lenin Raj Rajasekaran Avatar answered Nov 19 '22 06:11

Lenin Raj Rajasekaran