<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: João Victor Valentim</title>
    <description>The latest articles on DEV Community by João Victor Valentim (@joo_victorvalentim_f537).</description>
    <link>https://dev.to/joo_victorvalentim_f537</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2693552%2F651121dc-1ec3-44ff-9521-e897be4fcdb4.jpg</url>
      <title>DEV Community: João Victor Valentim</title>
      <link>https://dev.to/joo_victorvalentim_f537</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/joo_victorvalentim_f537"/>
    <language>en</language>
    <item>
      <title>Why I Built a Visual Layer for Rails Migrations (And Why Reading Ruby DSL in a Diff Wasn't Enough)</title>
      <dc:creator>João Victor Valentim</dc:creator>
      <pubDate>Fri, 24 Apr 2026 03:23:14 +0000</pubDate>
      <link>https://dev.to/joo_victorvalentim_f537/why-i-built-a-visual-layer-for-rails-migrations-and-why-reading-ruby-dsl-in-a-diff-wasnt-enough-3f7e</link>
      <guid>https://dev.to/joo_victorvalentim_f537/why-i-built-a-visual-layer-for-rails-migrations-and-why-reading-ruby-dsl-in-a-diff-wasnt-enough-3f7e</guid>
      <description>&lt;p&gt;Three months ago I merged a migration that added a &lt;code&gt;null: false&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;I was staring at a GitHub diff, reading raw Ruby DSL, trying to mentally reconstruct what the table looked like &lt;em&gt;before&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;I decided to build the tool I wished I had. That's how &lt;a href="https://github.com/jv4lentim/migflow" rel="noopener noreferrer"&gt;Migflow&lt;/a&gt; was born.&lt;/p&gt;




&lt;h2&gt;
  
  
  The actual problem
&lt;/h2&gt;

&lt;p&gt;Rails migrations are powerful, but the way we review them is stuck in 2010. You open a PR, you see a &lt;code&gt;change&lt;/code&gt; method with some &lt;code&gt;add_column&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;For small projects this is fine. For a production app with hundreds of migrations and dozens of tables, it's a recipe for incidents.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Migflow actually does
&lt;/h2&gt;

&lt;p&gt;Migflow is a mountable Rails engine. You add the gem, mount it, and you get a visual panel that reads directly from your &lt;code&gt;db/migrate/&lt;/code&gt; directory and &lt;code&gt;db/schema.rb&lt;/code&gt;. No database connection needed. No background jobs. No extra infrastructure.&lt;/p&gt;

&lt;p&gt;Here's what it gives you:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A migration timeline with plain-English summaries.&lt;/strong&gt; Instead of reading Ruby DSL, you see a chronological list of every migration with a human-readable description of what changed. "Added column &lt;code&gt;email_verified&lt;/code&gt; (boolean, default: false) to &lt;code&gt;users&lt;/code&gt;" is a lot easier to process during review than scanning a &lt;code&gt;change&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schema diffs showing before and after.&lt;/strong&gt; For any migration, you can see a unified diff of &lt;code&gt;schema.rb&lt;/code&gt; — 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An interactive ERD canvas.&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audit warnings for the things that actually bite people.&lt;/strong&gt; Migflow checks for six common issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Foreign key column missing an index&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;_id&lt;/code&gt; column without a foreign key constraint&lt;/li&gt;
&lt;li&gt;String column without a &lt;code&gt;:limit&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Table without timestamps&lt;/li&gt;
&lt;li&gt;Dangerous operations like &lt;code&gt;remove_column&lt;/code&gt;, &lt;code&gt;drop_table&lt;/code&gt;, or &lt;code&gt;rename_column&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;null: false&lt;/code&gt; with no default value (the exact thing that broke my production)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;A risk score per migration.&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A CI gate via rake task.&lt;/strong&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes this different
&lt;/h2&gt;

&lt;p&gt;There are great tools in the Rails ecosystem for migration safety. &lt;code&gt;strong_migrations&lt;/code&gt; is excellent — it intercepts &lt;code&gt;db:migrate&lt;/code&gt; at runtime and blocks unsafe operations before they hit your database. If you're not using it, you probably should be.&lt;/p&gt;

&lt;p&gt;But &lt;code&gt;strong_migrations&lt;/code&gt; and Migflow solve different problems at different moments.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;strong_migrations&lt;/code&gt; acts when you &lt;em&gt;run&lt;/em&gt; the migration. Migflow acts when you &lt;em&gt;review&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;They're complementary. If you're already using &lt;code&gt;strong_migrations&lt;/code&gt;, Migflow gives you and your reviewers the context to catch problems even earlier — during the PR, not during deployment.&lt;/p&gt;

&lt;p&gt;And there are things Migflow does that nothing else in the ecosystem offers:&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this is for
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;But if any of these sound familiar, Migflow might save you a production incident:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your team has more than a couple of developers pushing migrations&lt;/li&gt;
&lt;li&gt;Your app has been around long enough that nobody remembers what every table looks like&lt;/li&gt;
&lt;li&gt;You've had a migration-related incident and added "be more careful" to your post-mortem action items&lt;/li&gt;
&lt;li&gt;Your PR reviews for migrations are basically "looks fine to me" because the diff doesn't give enough context&lt;/li&gt;
&lt;li&gt;You want migration quality checks in CI but don't want to build custom tooling&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Gemfile&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'migflow'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/routes.rb&lt;/span&gt;
&lt;span class="n"&gt;mount&lt;/span&gt; &lt;span class="no"&gt;Migflow&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;at: &lt;/span&gt;&lt;span class="s1"&gt;'/migflow'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Visit &lt;code&gt;/migflow&lt;/code&gt; in your development environment and you'll see your full migration history with the timeline, ERD, diffs, and audit warnings.&lt;/p&gt;

&lt;p&gt;For CI integration, add the rake task to your pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rake migflow:ci
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Try it out
&lt;/h2&gt;

&lt;p&gt;Migflow is open source and available on GitHub: &lt;a href="https://github.com/jv4lentim/migflow" rel="noopener noreferrer"&gt;github.com/jv4lentim/migflow&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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 &lt;em&gt;see&lt;/em&gt; what a migration does instead of reading Ruby DSL and hoping for the best — give it a try.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>opensource</category>
      <category>productivity</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
