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?
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). @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)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]}
def database_changed?
self.class.where(self.class.arel_table[:updated_at].gt(updated_at)).exists? self
end
The Rails way to to do this is to use reload method on the ActiveRecord object.
def database_changed?
attributes != reload.attributes
end
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.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 >
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With