Override automatic updated_at in ActiveRecord/Rails

Neeraj Singh

By Neeraj Singh

on January 21, 2009

Rails provides some good tools like automatically updating created_at and updated_at columns. Developers do not need to worry about these columns. Rails updates these columns automatically which is great.

However I have a unique business need where I need to update a column but I do not want updated_at to be changed. Or we can see the problem this way. I want to change the updated_at to a particular value.

1>> User.first.update_attributes(:updated_at => 100.years.ago)
2UPDATE `users` SET `updated_at` = '2009-01-20 19:15:25' WHERE `id` = 2

Look at the sql that is generated. Rails discarded the updated_at value that I had supplied and replaced the value by the current time. Rails works fine if you supply created_at value. It is the updated_at value that is discarded.

Rails provides a feature called ActiveRecord::Base.record_timestamps . Using this feature I can tell rails to not to auto time stamp records.

Let's try that.

1>> User.record_timestamps=false
2=> false
3>> User.first.update_attributes(:updated_at => 100.years.ago)
4UPDATE `users` SET `updated_at` = '1909-01-20 18:52:50' WHERE `id` = 2
5>> User.record_timestamps=true
6=> true

It worked. I have successfully set updated_at to year 1909. However there is a problem.

For a brief duration User.record_timestamps was set to false. That is a class level variable. It means that for that brief duration if any other User record is updated then that record will not have correct updated_at value. That is not right. I want just one record ( User.first) to not to change updated_at without changing the behavior for the whole application.

In order to isolate the behavior to only the record we are interested in, I can do this.

1>> u = User.first
2
3>> class << u
4>>  def record_timestamps
5>>    false
6>>  end
7>> end
8
9>> u.update_attributes(:updated_at => 100.years.ago)
10UPDATE `users` SET `updated_at` = '1909-01-20 18:58:10' WHERE `id` = 2
11
12>> class << u
13>>  def record_timestamps
14>>    super
15>>  end
16>> end
17
18>> u.update_attributes(:updated_at => 200.years.ago)
19UPDATE `users` SET `updated_at` = '2009-01-20 19:22:11' WHERE `id` = 2

In order to restrict the changes to a model, I am opening up the metaclass of u ( user object) and in that object I am adding a method called record_timestamps . The idea is to insert a method called record_timestamps in the metaclass which will return true and in this way the changes are restricted to a single object rather than making change at the class level.

At this point the meta class of the user object has the method record_timestamps and this returns false. Now I update the record with updated_at set to 100 years ago. And I succeed.

Now I need to put the object behavior back to normal. I open up the metaclass and call super on the method so that the method call will go up the chain. And that's what happens when I try to test updated_at. This time the updated_at value that I set is ignored and rails changes the updated_at value.

update_without_timestamping method

This strategy of opening up an instance object works but it is messy. I would like to have a method that is much easier to use and this is what I came up with. Stick this piece of code in an initializer.

1module ActiveRecord
2  class Base
3
4    def update_record_without_timestamping
5      class << self
6        def record_timestamps; false; end
7      end
8
9      save!
10
11      class << self
12        def record_timestamps; super ; end
13      end
14    end
15
16  end
17end

This is how you can use it.

1>> u = User.first
2>> u.updated_at = 100.years.ago
3>> u.created_at = 200.years.ago
4>> u.update_record_without_timestamping
5UPDATE `users` SET `created_at` = '1809-01-20 19:08:21',
6`updated_at` = '1909-01-20 19:08:22' WHERE `id` = 2

Good usage of remove_method

In the above solution I used super when I want to bring back the default auto time stamping behavior. In stead of super I can also use remove_method. More about the what remove_method does is here .

1module ActiveRecord
2  class Base
3
4    def update_record_without_timestamping
5      class << self
6        def record_timestamps; false; end
7      end
8
9      save!
10
11      class << self
12        remove_method :record_timestamps
13      end
14    end
15
16  end
17end

Using the above technique, I can fully control updated_at values without rails messing up anything.

Stay up to date with our blogs. Sign up for our newsletter.

We write about Ruby on Rails, ReactJS, React Native, remote work,open source, engineering & design.