Enter your email below to watch this video
Deploy apps to servers that you own and control.
Application monitoring that helps developers get it done.
Sometimes, when writing data to a database, we know the final state we want but aren't sure whether to insert a new row or update an existing one. In other words, we either need to insert new data or update existing records depending on the current state of the database.
Fortunately, Active Record provides a utility method called upsert to handle this scenario. Let’s explore how it works.
Returning to our IRB session playground, we have:
Now, let’s use upsert instead of insert, passing a hash of attributes:
Post.upsert(id: 1, title: "New Title", body: "New Text")
At first glance, this looks similar to an insert statement, but there’s a key difference:
INSERT INTO posts (id, title, body, created_at, updated_at)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET ...
Instead of ignoring conflicts (DO NOTHING), this query updates the record when an id conflict occurs.
Breaking it down:
To ensure this, Active Record generates a CASE WHEN statement:
EXCLUDED is a special SQLite keyword representing the newly inserted values in case of a conflict.
This means:
If we now run:
Post.upsert(id: 2, title: "Another Post", body: "More content")
Since ID 2 does not exist, it creates a new record instead of updating one.
Thus, upsert allows us to:
Like insert_all, we can use upsert_all for batch processing:
Post.upsert_all([
{ title: "Bulk Post 1", body: "Content 1" },
{ title: "Bulk Post 2", body: "Content 2" }
])
This generates a single SQL query to insert or update multiple rows at once, significantly improving performance.
upsert is particularly useful for:
However, keep in mind:
Use it only when you are confident about your schema and data structure.
Now that we’ve explored efficient data insertion and updates, in the next video, we’ll dive into querying—retrieving and filtering records efficiently in Active Record.