🧵 Series: Transactions with Active Record + PostgreSQL 📌 Post 2 – Nested Transactions in Rails: What Really Happens and When (Not) to Use Them
In the first post of this series, we saw how database transactions ensure atomicity, consistency, isolation, and durability(ACID). But what happens when you nest one transaction inside another?
Most developers assume that if something fails inside a nested `.transaction` block, the entire operation rolls back.
🚨 Spoiler: That's not exactly what happens.
In this post, you'll learn:
💡 What Are Nested Transactions?
Nested transactions occur when you wrap a `.transaction` block inside another `.transaction`.
ActiveRecord::Base.transaction do
# Outer transaction
ActiveRecord::Base.transaction do
# Inner transaction
end
end
You might expect that if the inner block fails, the whole transaction is rolled back. But that’s not exactly true in Rails.
🧠 What Actually Happens in Rails (and PostgreSQL)
🔍 PostgreSQL does not support true nested transactions.
So how does Rails manage it?
👉 Rails fakes it using SAVEPOINTs.
This can lead to false assumptions of safety if you're not careful.
🛠 Practical Example: Inner Rollback, Outer Continues
ActiveRecord::Base.transaction do
User.create!(name: "Outer")
begin
ActiveRecord::Base.transaction do
User.create!(name: "Inner")
raise "Something went wrong"
end
rescue => e
puts "Rescued: #{e.message}"
end
User.create!(name: "After inner")
end
Result:
📌 The outer transaction remains alive after the error inside the nested transaction — unless you explicitly raise again.
🔥 Gotchas (Things That Can Go Wrong)
✅ When Nested Transactions Are Actually Useful
🎯 Real-World Case: Optional User Setup
ActiveRecord::Base.transaction do
user = User.create!(name: "Lucas")
begin
ActiveRecord::Base.transaction do
Settings.create!(user: user, notifications: true)
Preferences.create!(user: user, theme: "dark")
raise "Failed to fetch external config"
end
rescue => e
Rails.logger.warn("Optional setup failed: #{e.message}")
# Continue: user is valid even if settings fail
end
WelcomeMailer.send_welcome_email(user)
end
Why this works:
✅ This is a good use of nested transactions.
🚫 But Be Careful…
🧠 SAVEPOINTs (Advanced Use)
Rails exposes savepoint control via `connection`:
ActiveRecord::Base.transaction do
connection = ActiveRecord::Base.connection
connection.create_savepoint
begin
# risky operation
rescue
connection.rollback_to_savepoint
ensure
connection.release_savepoint
end
end
⚙️ This gives fine-grained control, but for most use cases, Rails' built-in .transaction with error handling is enough.
✅ Recommendations
⚠️ Warnings
🧠 Summary
📌 Coming next in the series: How to handle side effects, external calls, and background jobs in transactional flows — without losing data integrity or sending wrong emails.
Software Engineer | Python, Django, AWS, RAG
3wThanks for sharing, Fabio
Software Engineer | Mobile Developer | Flutter | Dart
1moThanks for sharing!
SDET | QA Engineer | Test Automation Engineer | Playwright | Cypress | Robot Framework | Postman | Cucumber | Jenkins | Typescript | Javascript | Python | Manual Testing | Jira
1mo💡 Great insight.
AI Engineer | Python | Computer Vision | Generative AI | LLM | Data Science | M.Sc.
1moExcellent breakdown. Savepoints are powerful but easy to misuse if you're not aware of how Rails handles nested transactions.
.NET Software Engineer | Full Stack Developer | C# | React | Azure
1moExcellent article!