High Leverage Rails
Introduction
Introduction to this course
Why use Ruby on Rails
Why use SQLite
Ruby on Rails + SQLite
Powering Your App with SQLite
Creating tables
Timestamps
Column types
Typeof
Ruby types
Creating table introduction
Creating table advanced
Inserting data
Updating data
Upserting data
Reading data
Virtual columns
Enums
Introduction to JSON
Indexing JSON
JSON table functions
Building a Modern Rails Application
Creating a new Rails application
Installing Solid Queue
Installing Solid Cache
Installing Solid Cable
Dockerfile
Application overview
Authentication
Base styles
Registration
Scaffolding posts
Polishing posts
Scaffolding comments
Polishing comments
Polishing models
Polishing controllers
Creating new post
Updating post
Reviewing MVP
Tagging posts
Custom tags
Friendly URLs
Full text search
Deploying & Operating Your App
Backups
Check Litestream locally
Verifying backups
Deployment options
Deploying with Hatchbox
Deployment constraints
Vertical scaling
Database access
Migrations
Locked video

Please purchase the course to watch this video.

Video thumbnail
Powering Your App with SQLite
Inserting data

Full Course

$
129
$179
USD, one-time fee
This course came at the perfect time. I’ve recently gotten back into Rails after an 18-year hiatus, and this was a perfect refresher and shows just how much you can accomplish with Rails right out of the box.
Garrett Winder
Garrett Winder

Rails hosting made simple

Deploy apps to servers that you own and control.

Move fast and fix things

Application monitoring that helps developers get it done.

Summary

Learn how to optimize bulk inserts in Rails with insert and insert_all, bypassing validations and callbacks for improved performance. Discover how these methods differ from create!, making them ideal for high-volume data operations while weighing the trade-offs in data integrity and safety.

Video Transcript

Typically, when creating records in a Rails application, you want to check validations and run callbacks. However, if you need to insert hundreds of thousands of records quickly, that extra overhead can slow things down.

In this video, we’ll explore how to insert records efficiently without triggering validations or callbacks, making bulk inserts significantly faster.

Using insert Instead of create!

Jumping back into our IRB session playground, we have our posts table with title and body columns, along with a Post model that includes our validation and callback from the previous video.

Let’s set this up and look at a new method for inserting data:

result = Post.insert({ title: "the title", body: "some text" })

Instead of passing our attributes directly, we have to pass a hash of attributes and their values.

Key Differences Between insert and create

Running this, we see several key differences in the SQL logs:

1. Direct SQL Execution

  • Unlike create, which prepares generic SQL statements, insert directly writes values into the SQL query. This means no bind parameters (?) are used for reusability.
  • We also now have an extra ON CONFLICT clause, with DO NOTHING RETURNING "id"
  • We receive an instance of an Active Record result class instead of an instance of our post model.

2. No Validations Are Run

If we run a test of trying to insert an empty record, it runs a SQL query.

result = Post.insert({})

However, it has not checked our validations. Even though our post model has said that we need to ensure that the title attribute is present, when we use the insert method without a title, it's completely fine. There's not a database constraint saying there must be a title, that is only a constraint at the application level.

3. No Callbacks Are Triggered

Callbacks like after_create_commit are not executed because insert doesn’t wrap the operation in a transaction. Even if we add another callback, like after_create, it still won’t run.

Using insert_all for Bulk Inserts

If you need to insert thousands of records, iterating over each one individually is inefficient. Instead, use insert_all, which inserts multiple records in a single SQL query:

Post.insert_all([
  { title: "title one" },
  { title: "title two" }
])

The insert_all method takes an array of hashes of your attributes.

This generates one SQL statement that inserts multiple rows at once, significantly improving performance.

Understanding ON CONFLICT DO NOTHING

You might notice that insert statements include an ON CONFLICT DO NOTHING clause, which we did not see with create.

  • This clause ensures that if there is a uniqueness constraint on a column (e.g., title must be unique), the insert will fail silently instead of throwing an error.
  • With create, Rails wants validation failures to bubble up so that exceptions can be handled properly.

When to Use insert vs. create

Now that we know the three major ways to insert records into an SQLite database (create, save, and insert), here’s when to use each:

Method Runs Validations? Runs Callbacks? Best Use Case
create! ✅ Yes ✅ Yes Default method for most inserts
save ✅ Yes ✅ Yes When updating existing objects
insert ❌ No ❌ No Bulk inserts when performance is critical

Best Practice:

  • Always default to create! to ensure data integrity.
  • Use insert and insert_all only if performance is a major concern and you are confident in your database schema.

Now that we’ve covered efficient data insertion, our next focus will be updating existing records in the database.