1
0
mirror of https://github.com/jbranchaud/til synced 2026-01-03 15:18:01 +00:00
Files
til/rails/manage-timestamps-with-upsert.md
2025-06-26 09:21:17 -05:00

2.5 KiB
Raw Blame History

Manage Timestamps With Upsert

Modern versions of Rails and ActiveRecord have an upsert method which will, if available, use your database's upsert capability to either insert a new row or update an existing row based on the unique identifier.

The docs have the following disclaimer:

It does not instantiate any models nor does it trigger Active Record callbacks or validations. Though passed values go through Active Records type casting and serialization.

It's a bit different to work with than other ActiveRecord methods. It left me wondering if it would handle timestamp management or if I would have to do that myself.

Let's upsert a new record into books:

> Book.upsert({title: "Shogun", author: "James Clavell", added_by_id: 1, publication_date: Date.today}, unique_by: :id)
=> #<ActiveRecord::Result:0x00000001141c3df0 ...

> Book.select(:id, :title, :created_at, :updated_at).last
=> #<Book:0x0000000113bae898 id: 12, title: "Shogun", created_at: "2025-06-26 14:08:26.035633000 +0000", updated_at: "2025-06-26 14:08:26.035633000 +0000">

Notice that the created_at and updated_at timestamps get set.

Now, let's upsert the record (notice we're passing in the id) to update the title with an ō.

> Book.upsert({id: 12, title: "Shōgun", author: "James Clavell", added_by_id: 1, publication_date: Date.today}, unique_by: :id)
=> #<ActiveRecord::Result:0x0000000113cace98 ...

> Book.select(:id, :title, :created_at, :updated_at).last
=> #<Book:0x00000001143aadd0 id: 12, title: "Shōgun", created_at: "2025-06-26 14:08:26.035633000 +0000", updated_at: "2025-06-26 14:10:46.280480000 +0000">

Notice that the updated_at gets set to a time about 2 minutes later.

Lastly let's look at the record_timestamps option. This is nil by default which means the underlying methods default kicks in which is to record the timestamps. We can override that behavior by passing in false.

> Book.upsert({id: 12, title: "Shōgun, Part 2", author: "James Clavell", added_by_id: 1, publication_date: Date.today}, unique_by: :id, record_timestamps: false)
=> #<ActiveRecord::Result:0x0000000113989428 ...

> Book.select(:id, :title, :created_at, :updated_at).last
=> #<Book:0x0000000114fe1b80 id: 12, title: "Shōgun, Part 2", created_at: "2025-06-26 14:08:26.035633000 +0000", updated_at: "2025-06-26 14:10:46.280480000 +0000">

Notice that the updated_at value doesn't change between this upsert and the previous upsert.