ActiveRecord comes with a mechanism to track changes to your model with ActiveModel::Dirty. Lets put it to some use.

I had a requirement where I needed to track changes in certain places in my rails app.

When saving any record in Rails, if we look at the queries carefully, we can see that each query does not have all values but just the changed ones.

SQL (21.4ms)  UPDATE "users" SET "email" = $1, "updated_at" = $2 WHERE "users"."id" = $3
[["email", "tejas@asd.com"], ["updated_at", "2016-09-26 18:26:47.005326"], ["id", 55]]

Above query is fired after changing email to tejas@asd.com and saving the object. Clearly ActiveRecord keeps track of changes on its objects. This mechanism is provided by the ActiveModel::Dirty module.

We can see the changes before the record is being saved.

[1]> u = User.last
  User Load (4.3ms)  SELECT  "users".* FROM "users"  ORDER BY "users"."id" DESC LIMIT 1
=> #<User:0x007f9216adc488 id: 55, email: "tejas@qwe.com",.... >

[2] pry(main)> u.email = "tejas@asd.com"
=> "tejas@asd.com"

[3] pry(main)> u.changes
=> {"email"=>["tejas@qwe.com", "tejas@asd.com"]}

Note that changes method gives changes only before saving the record. Once the record is saved, the changes are flushed out and ready to record new ones. In order to track changes in existing codebase which has many occurrences like @model.update_attributes() I needed some way to know changes after the record is saved.

ActiveModel to the rescue! When the changes are flushed out after saving, they are not immediately thrown away but kept under another instance variable and can be accessed via previous_changes method.

[2] pry(main)> u.email = "tejas@asd.com"
=> "tejas@asd.com"

[3] pry(main)> u.changes
=> {"email"=>["tejas@qwe.com", "tejas@asd.com"]}

[4] pry(main)> u.save!
   (1.1ms)  SAVEPOINT active_record_1
  SQL (8.4ms)  UPDATE "users" SET "email" = $1, "updated_at" = $2 WHERE "users"."id" = $3
  [["email", "tejas@asd.com"], ["updated_at", "2016-09-26 18:34:23.899504"], ["id", 55]]
   (0.3ms)  RELEASE SAVEPOINT active_record_1
=> true

[5] pry(main)> u.changes
=> {}

[6] pry(main)> u.previous_changes
=> {"email"=>["tejas@qwe.com", "tejas@asd.com"],
    "updated_at"=>[Mon, 26 Sep 2016 18:33:03 UTC +00:00, Mon, 26 Sep 2016 18:34:23 UTC +00:00]}

References:

  1. ActiveModel provides other methods which can be found in the docs.

  2. Or dive into ActiveModel source code.