73

If you have DB columns created_at and updated_at Rails will automatically set those values when you create and update a model object. Is there a way to save the model without touching those columns?

I am bringing in some legacy data and I would like to set those values from the corresponding values in the (differently named) legacy data fields. I'm finding when I set them on the model and then save the model, Rails appears to override the incoming values.

Of course I could just name the Rails model columns differently to prevent that, but after the data is imported, I want Rails to do its automatic timestamp thing.

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Ethan
  • 57,819
  • 63
  • 187
  • 237

12 Answers12

86

Do this in a migration or in a rake task (or in the new database seeds if you're on edge rails):

ActiveRecord::Base.record_timestamps = false
begin
  run_the_code_that_imports_the_data
ensure
  ActiveRecord::Base.record_timestamps = true  # don't forget to enable it again!
end

You can safely set created_at and updated_at manually, Rails won't complain.

Note: This also works on individual models, e.g. User.record_timestamps = false

August Lilleaas
  • 54,010
  • 13
  • 102
  • 111
  • 3
    You can even pull this into a method, e.g. `def without_timestamps old = ActiveRecord::Base.record_timestamps ActiveRecord::Base.record_timestamps = false begin yield ensure ActiveRecord::Base.record_timestamps = old end end` – nonrectangular Oct 21 '13 at 21:01
  • 1
    Do we need to reset it back to `true` even when executing scrips or code inside the console ? – oldergod Sep 12 '14 at 05:44
  • 2
    I think http://stackoverflow.com/a/37193298/1214748 is much better. It doesn't turn it off globally for the whole application – maschwenk Apr 21 '17 at 23:48
  • @maschwenk true, for rails 5. but for those forced to use an older version of rails, this still seems the best option. – maurice Jan 08 '18 at 20:04
  • In no way shape or form, I would imagine :) Migrations runs before the app starts though, iirc, so it shouldn't matter. (It's been a while since I've done rails, so correct me if I'm wrong) – August Lilleaas May 13 '20 at 21:11
59

use update_column method instead:

http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-update_column

update_column(name, value)
# Updates a single attribute of an object, without calling save.

Validation is skipped.

Callbacks are skipped.

updated_at/updated_on column is not updated if that column is available.

Raises an ActiveRecordError when called on new objects, or when the name attribute is marked as readonly.

mahemoff
  • 44,526
  • 36
  • 160
  • 222
yawl
  • 719
  • 6
  • 7
45

Rails 5 provides a convenient way to update a record without updating it's timestamp updated_at: https://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-save

You, just need to pass touch:false while updating your record.

>> user = User.first
>> user.updated_at
=> Thu, 28 Apr 2016 20:01:57 IST +05:30
>> user.name = "Jose"
>> user.save(touch: false)
=> true

>> user.updated_at
=> Thu, 28 Apr 2016 20:01:57 IST +05:30
MSC
  • 3,286
  • 5
  • 29
  • 47
Subhash Chandra
  • 3,165
  • 1
  • 27
  • 30
  • 1
    This by far is the cleanest way to move the records. – John Doe Jun 23 '16 at 19:01
  • 3
    Seems a little odd that the same option doesn't work when doing `update`. You need to split it into two operations as shown here. – MSC Nov 16 '18 at 02:47
30

You can set the following inside your migration:

ActiveRecord::Base.record_timestamps = false

Or altenatively use update_all:

update_all(updates, conditions = nil, options = {})

Updates all records with details given if they match a set of conditions supplied, limits and order can also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the database. It does not instantiate the involved models and it does not trigger Active Record callbacks.

Toby Hede
  • 36,755
  • 28
  • 133
  • 162
16

In Rails 3+, for a single object, set record_timestamps for the object rather than class. I.e.,

>> user = User.first
>> user.updated_at
=> Tue, 12 Apr 2016 22:47:51 GMT +00:00
>> user.name = "Jose"
=> "Jose"
>> user.record_timestamps = false
>> user.save
=> true
>> user.updated_at
=> Tue, 12 Apr 2016 22:47:51 GMT +00:00
>> User.record_timestamps
=> true

This way, you don't touch global state for the model, and you don't have to remember to restore the prior setting in an ensure block.

Chuck Batson
  • 2,165
  • 1
  • 17
  • 15
4

Referring to other answers, I found to my surprise that disabling timestamps for a single model, like in:

User.record_timestamps = false

worked for my development database, but not on my pre-production database, which runs on a different server. However it works if I disable timestamps for all models with

ActiveRecord::Base.record_timestamps = false

(Situation: modifying the created_at attribute in a migration)

3

I like to use a mixin module to temporarily turn off time-stamping in a block:

module WithoutTimestamps
  def without_timestamps
    old = ActiveRecord::Base.record_timestamps
    ActiveRecord::Base.record_timestamps = false
    begin
      yield
    ensure
      ActiveRecord::Base.record_timestamps = old
    end
  end
end

Then you can use it wherever you need it

class MyModel < ActiveRecord::Base
  include WithoutTimestamps

  def save_without_timestamps
    without_timestamps do
      save!
    end
  end
end

Or just a one-off like this:

m = MyModel.find(1)
WithoutTimestamps.without_timestamps do
  m.save!
end
nonrectangular
  • 2,080
  • 20
  • 9
3

Since this is a one-time import, you could do the following:

  1. Create model using legacy_created_at and legacy_updated_at fields.
  2. Load legacy data. Map into the model fields as desired. You can use #save and generally not worry about using update_all or the like, and you can use callbacks if desired.
  3. Create a migration to rename the columns to created_at and updated_at.
runako
  • 6,132
  • 2
  • 25
  • 15
2

When not in a bulk import, you can override the should_record_timestamps? method on your model to add new checks on when to update the updated_at column.

iwiznia
  • 1,669
  • 14
  • 21
1

Or, for thread safety, see: http://www.hungryfools.com/2007/07/turning-off-activerecord-timestamp.html

Andrew
  • 562
  • 5
  • 15
0

I wasn't able to make ActiveRecord::Base.record_timestamps flag change anything, and the only working way is using ActiveRecord::Base.no_touching {}:

ActiveRecord::Base.no_touching do
  Project.first.touch  # does nothing
  Message.first.touch  # does nothing
end

Project.no_touching do
  Project.first.touch  # does nothing
  Message.first.touch  # works, but does not touch the associated project
end

Taken from here.

This is very useful for rake tasks where you have to override some properties of deeply-related models and don't want to bother about inner callbacks, while your users rely on created_at or updated_at attributes or those are used as sorting columns.

victorpolko
  • 359
  • 3
  • 4
0

I'm late to the party but this is how we skip updating updated_at in my shop:

# config/initializers/no_timestamping.rb
module ActiveRecord
  class Base

    def update_record_without_timestamping
      class << self
        def record_timestamps; false; end
      end

      save!

      class << self
        remove_method :record_timestamps
      end
    end

  end
end

Usage:

post.something = 'whatever'
post.update_record_without_timestamping
Dan
  • 1,238
  • 2
  • 17
  • 32