ActiveRecord is one of the most useful utilities in the Rails toolkit. It allows us to perform complex database queries while abstracting the adapter, essentially making a database “pluggable”.
However,
some edge cases stand out like a sore thumb.
One such query is the ability to set values to the standard Rails
timestamps which are available across models.
Namely,
created_at
,
created_on
,
updated_at
,
updated_on
.
In a typical model insertion, one does not need to actively set values for the timestamps. They are set by the underlying framework at the time of insertion.
> Book.create(title: "Ruby on Rails Tutorial: Learn Web Development with Rails")
> Book.last.created_at
=> Tue, 18 Jan 2022
Before
One would expect consistent action across similar queries. Let’s have a look at mass insertion queries.
> Book.insert_all[
{title: "Ruby on Rails Tutorial: Learn Web Development with Rails"},
{title: "Service-Oriented Design with Ruby and Rails"}
]
=> Traceback (most recent call last):
1: from (irb):1
ActiveRecord::NotNullViolation (SQLite3::ConstraintException: NOT NULL constraint failed: books.created_at)
It raises an unexpected exception ActiveRecord::NotNullViolation
,
since a database constraint is present on the created_at
column.
This leaves us with only one option — manually providing values for all timestamp columns.
The only way to make this work is,
> Book.insert_all[
{title: "Ruby on Rails Tutorial: Learn Web Development with Rails", created_at: Time.now, updated_at: Time.now},
{title: "Service-Oriented Design with Ruby and Rails", created_at: Time.now, updated_at: Time.now}
]
=> #<ActiveRecord::Result:0x00007f9ffcdfb620 @columns=[], @rows=[], @hash_rows=nil, @column_types={}>
After
Fortunately, Rails 7 allow setting timestamps on insert_all / upsert_all record queries.
> Book.insert_all[
{title: "Ruby on Rails Tutorial: Learn Web Development with Rails"},
{title: "Service-Oriented Design with Ruby and Rails"}
]
=> #<ActiveRecord::Result:0x00007f9ffcdfb620 @columns=[], @rows=[], @hash_rows=nil, @column_types={}>
We have the option to override this configuration using,
> Book.insert_all[
{title: "Ruby on Rails Tutorial: Learn Web Development with Rails"},
{title: "Service-Oriented Design with Ruby and Rails"}
], record_timestamps: false
=> Traceback (most recent call last):
1: from (irb):1
ActiveRecord::NotNullViolation (SQLite3::ConstraintException: NOT NULL constraint failed: books.created_at)
Which, as expected, will result in the previous error.