Three months ago I merged a migration that added a null: false column with no default value. The table had 800k rows. Production broke, and the worst part wasn't the bug itself — it was that nothing in our workflow could have caught it.
I was staring at a GitHub diff, reading raw Ruby DSL, trying to mentally reconstruct what the table looked like before this migration and whether the change would blow up on existing data. That's the review process most Rails teams rely on: hope that someone caffeinated enough catches the problem in a PR.
I decided to build the tool I wished I had. That's how Migflow was born.
The actual problem
Rails migrations are powerful, but the way we review them is stuck in 2010. You open a PR, you see a change method with some add_column calls, and you try to reason about what the schema looks like right now, what it will look like after, and whether anything in between could go wrong.
There's no visual context. No before-and-after. No way to see how this migration fits into the history of the table. You're reading code that describes a transformation, without seeing what's being transformed.
For small projects this is fine. For a production app with hundreds of migrations and dozens of tables, it's a recipe for incidents.
What Migflow actually does
Migflow is a mountable Rails engine. You add the gem, mount it, and you get a visual panel that reads directly from your db/migrate/ directory and db/schema.rb. No database connection needed. No background jobs. No extra infrastructure.
Here's what it gives you:
A migration timeline with plain-English summaries. Instead of reading Ruby DSL, you see a chronological list of every migration with a human-readable description of what changed. "Added column email_verified (boolean, default: false) to users" is a lot easier to process during review than scanning a change method.
Schema diffs showing before and after. For any migration, you can see a unified diff of schema.rb — what the schema looked like before the migration ran and what it looks like after. This is the context that's completely missing from a normal PR review.
An interactive ERD canvas. This is probably the feature I'm most proud of. It's a visual graph of your tables, columns, and foreign keys that updates as you walk through your migration history. Added a table? It shows up in green. Removed a column? Highlighted in red. You can literally watch your schema evolve over time.
Audit warnings for the things that actually bite people. Migflow checks for six common issues:
- Foreign key column missing an index
-
_idcolumn without a foreign key constraint - String column without a
:limit - Table without timestamps
- Dangerous operations like
remove_column,drop_table, orrename_column -
null: falsewith no default value (the exact thing that broke my production)
A risk score per migration. Each migration gets a score based on the audit rules it triggers. At a glance, you can see which migrations in a PR deserve extra scrutiny.
A CI gate via rake task. You can plug the risk score into your pipeline and set a threshold. If a migration exceeds it, the build fails. This turns migration quality from "whoever reviews the PR" into an automated, repeatable check.
What makes this different
There are great tools in the Rails ecosystem for migration safety. strong_migrations is excellent — it intercepts db:migrate at runtime and blocks unsafe operations before they hit your database. If you're not using it, you probably should be.
But strong_migrations and Migflow solve different problems at different moments.
strong_migrations acts when you run the migration. Migflow acts when you review it. One is a runtime guardrail. The other is a review tool that gives you the visual context to understand what a migration actually does to your schema before anyone runs anything.
They're complementary. If you're already using strong_migrations, Migflow gives you and your reviewers the context to catch problems even earlier — during the PR, not during deployment.
And there are things Migflow does that nothing else in the ecosystem offers:
An ERD that travels through time. You can step through your migration history and watch the schema graph update with each change. No other tool shows you what your database looked like at any point in its history, visually, with highlights for what was added or removed.
Review without database access. Because Migflow reads files, not the database, any reviewer can use it. Your frontend developer reviewing a PR doesn't need access to staging or production to understand what a migration does.
Institutional quality control. The CI rake task means migration review quality doesn't depend on who's on rotation for code review that day. The threshold is the same whether your senior DBA is reviewing or a junior developer.
Who this is for
If you're a solo developer on a small app, you probably don't need this. You know your schema, your tables are small, and the risk of a bad migration is low.
But if any of these sound familiar, Migflow might save you a production incident:
- Your team has more than a couple of developers pushing migrations
- Your app has been around long enough that nobody remembers what every table looks like
- You've had a migration-related incident and added "be more careful" to your post-mortem action items
- Your PR reviews for migrations are basically "looks fine to me" because the diff doesn't give enough context
- You want migration quality checks in CI but don't want to build custom tooling
Getting started
Add the gem, mount the engine, and you're done. There's no database setup, no configuration file to maintain, no service to run. It reads your migration files and schema, and it works.
# Gemfile
gem 'migflow'
# config/routes.rb
mount Migflow::Engine, at: '/migflow'
That's it. Visit /migflow in your development environment and you'll see your full migration history with the timeline, ERD, diffs, and audit warnings.
For CI integration, add the rake task to your pipeline:
bundle exec rake migflow:ci
Try it out
Migflow is open source and available on GitHub: github.com/jv4lentim/migflow
I built this because I was tired of reviewing migrations blind. If you've ever merged a migration and immediately regretted it, or if you've ever wished you could see what a migration does instead of reading Ruby DSL and hoping for the best — give it a try.
I'd love to hear your feedback, especially if you've tried to solve this problem differently or if the audit rules are missing something that's bitten you before.
Top comments (1)
The split between review-time and runtime guardrails is the idea that sticks. We've normalized runtime safety checks in databases—constraints, triggers, strong_migrations—but review-time tooling for schema changes is still basically "hope someone reads the diff carefully." It's the same energy as waiting until production to run your tests.
What I'm thinking about is how this maps onto the actual social dynamics of a team. The "looks fine to me" migration review isn't usually laziness. It's that the reviewer doesn't have enough context to be confidently skeptical. Reading a migration file in isolation is like reviewing a patch to a function you've never seen called. You can check the syntax, but you can't reason about the side effects. The ERD diff and the timeline give the reviewer enough ambient context to ask better questions, which is the real bottleneck in review quality anyway.
The audit rule for
null: falsewith no default is the obvious killer feature because it encodes a specific scar. But I'm curious about the long tail of those rules—the ones that come from incidents that haven't happened yet. Every team has migration scars that are idiosyncratic to their stack or their data patterns. Does the rule system have enough extension points for teams to add their own checks, or is the current set meant to be canonical? That feels like the difference between a tool that captures what's bitten you before and one that can capture what'll bite you next.