Nowy standard. Twoje centrum tworzenia inteligentnego, szybkiego i zrównoważonego oprogramowania AI

Zobacz teraz
Best practices for Ruby on Rails data migrations Ruby on Rails Data Migration Best Practices for Smooth Database Updates

Ruby on Rails Data Migrations: Best Practices for Safe Updates

Hubert Białęcki
|   Updated Nov 1, 2026

A data migration changes the values stored in a database. A schema migration changes the database's structure. In practice, that covers a wide range of everyday tasks.

Backfilling a new column, moving data between columns, generating new records, fixing corrupted values, removing obsolete data, reformatting dates, and merging duplicate records all count. Ruby on Rails only tells you what not to do. Its official guides advise against putting data changes in migration files, but Rails doesn't ship a built-in, tracked way to run them instead.

Each team ends up choosing an approach on its own, often without realizing there's a decision to make at all. It matters more than it sounds. Get it wrong, and a routine deployment can turn into slow releases, failed rollbacks, or corrupted production data.

Executive Summary

Rails advises against data changes inside migration files but offers no built-in, tracked alternative, so most teams choose among three approaches: bundling data changes into schema migrations anyway, running them as one-off rake tasks or scripts, or managing them with a dedicated gem.

Each option trades convenience for risk in a different place. Teams that pick the wrong approach for their situation usually learn this during a failed deployment. The right choice depends mainly on data volume, how often the team ships, and how much tooling investment is worth making upfront.

Why Getting Ruby on Rails Data Migrations Right Matters

Every growing Rails application eventually needs to change its data, not just its structure. It can be a new feature that needs existing records backfilled, a bug that corrupted a column, or two systems that merged and now have duplicate customers.

Teams treating this as an afterthought end up paying for it during deployment, when fixes are far more expensive. A migration that touches millions of rows can lock tables long enough to cause visible downtime. On PostgreSQL, which wraps each migration in a transaction, a failed data change rolls back the schema change in the same file, turning one bug into a blocked release. On MySQL, which can't roll back schema changes, the same failure leaves the migration half-applied and needs manual cleanup.

The downstream cost is significant. It includes deployment delays, emergency rollbacks, and, in the worst case, silently corrupted data that goes unnoticed until a customer complains. It's the direct, predictable result of choosing a data migration approach that doesn't align with how your team ships.

How the Right Migration Approach Protects Your Rails Data

The mechanism is simple: the further a data change is from your regular deployment pipeline, the more control you have over when and how it runs, at the cost of some automation. It's the same tradeoff that appears throughout Rails' broader architecture. Convention buys speed, deliberate structure buys safety, and the right balance depends on what's at stake.

Bundling data changes into a schema migration keeps everything in one file that runs automatically during deployment. However, that's also the risk. A slow or failing data change blocks the entire deployment, and reverting it later requires custom rollback logic rather than a simple schema rollback.

Pulling the data change out into a separate rake task breaks that coupling. The schema deploys on its own, and the data change runs on its own schedule. In other words, a team can test it, run it with a dry-run option first, and re-run it safely if it fails partway through.

This makes data changes testable in the first place. Code in a migration file typically runs only once and is rarely covered by the test suite. A rake task, or a data migration managed through a gem, can be written, tested, and reviewed like any other piece of application code. That's where most of the long-term safety comes from.

Which Rails Data Migration Approach Should You Use?

Each approach makes a different tradeoff between convenience and control. Here's how they compare.

Approach

Runs automatically?

Testable?

Rollback

Best for

Data change inside schema migration

Yes, with deployment

Rarely, runs once

Requires custom down logic

Small backfills tied directly to a new column

Temporary rake task or script

No, run manually

Yes, like regular code

Re-run safely if idempotent

Larger or riskier one-off data changes

Dedicated gem (e.g. data_migrate, maintenance_tasks)

Yes, if wired into your deploy step

Yes

Versioned and tracked (data_migrate supports down migrations)

Teams running frequent data migrations who want them treated like first-class deploys

Bundling data into schema migrations works well when the change is small and directly tied to a schema update. Adding a likes_count column and immediately backfilling it from existing records is a good fit because the two changes are logically a single unit of work. However, any table with millions of rows turns this convenience into a deployment-time risk, because the deploy waits for the backfill to finish, and on PostgreSQL the whole migration runs inside one transaction.

Temporary rake tasks are useful when a data change is large, risky, or needs review before running in production. A rake task can be tested, executed with a dry-run option, and deleted from the codebase once it's done its job. That keeps the migrations folder focused purely on schema history. Since Rails 8.0, rails generate script offers a similar option: one-off scripts in a dedicated script/ folder, run with rails runner. Rake tasks and scripts run manually, so someone has to remember to run them, and coordinating that across a team adds process overhead.

A dedicated gem works best once data migrations become a regular, ongoing need rather than a one-off event. data_migrate gives data changes their own timestamped, tracked directory (db/data). They run through rails data:migrate, which mirrors the db:migrate workflow your team already trusts, and rails db:migrate:with_data runs schema and data migrations together in version order, with rollback support built in.

maintenance_tasks, from Shopify, is the gem the Rails guides themselves point to. It takes a different shape: instead of migration files, it runs data changes as background jobs that process records in batches, can be throttled, paused and resumed, and are triggered from a web UI or the command line. That makes it a strong fit for long-running changes on large tables.

Two other gems take a similar, migration-style approach:

  • rails-data-migrations also tracks data migrations in their own table and runs them with rake data:migrate, but it only supports forward (up) migrations, and its last release dates back to May 2024.

  • nonschema_migrations is built specifically for migrations that should never touch the schema at all. Its versions are locked to specific Rails releases.

All of these sit alongside the gems most Rails teams already rely on for testing and security. A gem is still another dependency to maintain, though, and it's only worth adopting once data migrations happen often enough to justify the setup.

Bundling data into schema migrations carries three specific risks, which are also the reasons the Rails guides advise against it:

  • Irreversibility: Bundling data into a schema migration can make the change hard to reverse cleanly, as undoing a data transformation usually needs custom logic.

  • Mixed responsibilities: It blends two different concerns into one file, structure and content, which makes migrations harder to read months later.

  • Untested edge cases: Code that runs once, inside a migration, tends to accumulate bugs that a rake task or gem-managed migration would catch during review.

Knowing these risks upfront means you catch them in code review rather than in production.

What Needs to Be True for Safe Ruby on Rails Migrations

Picking the right approach only gets you halfway there. What happens next determines whether it holds up in production.

Here's what has to hold, regardless of which approach you picked:

  • Idempotency: Any rake task or gem-managed migration must produce the same result no matter how many times it runs. Partial failures and re-runs are routine in real deployments.

  • Test coverage: Whichever approach a team picks, the data change itself should be covered by tests before it reaches production, in the same test suite (Minitest or RSpec) the rest of the app uses. Code that runs once, untested, is exactly where quiet data corruption comes from.

  • Deployment discipline: Rake tasks only work if someone runs them at the right point in the deploy process. Without a documented, enforced step for this, they get forgotten.

  • Volume awareness: A migration that's safe on a 10,000-row table can lock production for minutes on a 50-million-row table. Process large tables in batches (for example with in_batches or find_each) rather than in one statement, and let volume drive which approach a team picks, ahead of whatever's most convenient to write.

  • Stable code references: Data migration code that calls your application models can break later, once those models change. Old migrations then fail on a fresh setup or a re-run. Defining a minimal model inside the migration, or using plain SQL, avoids this.

  • Rollback readiness: Even an idempotent migration needs a real plan for what happens if it fails halfway through a large table, well beyond the ability to re-run it from the start.

Every condition you skip becomes a debt the next engineer inherits, usually without knowing they owe it.

Key Takeaways

  • Rails' own guides advise against data changes in migration files but ship no built-in alternative, which is exactly why teams need a deliberate approach instead of defaulting to whatever's fastest to write.

  • Bundling data changes into schema migrations is convenient for small backfills, but risky at scale, since a failure blocks the schema change with it.

  • Temporary rake tasks and scripts trade automation for testability and control, which matters most for large or high-risk data changes.

  • A dedicated gem like data_migrate or maintenance_tasks makes sense once data migrations are frequent enough to justify treating them as first-class, tracked deploys.

  • The deciding factor is rarely convenience. It's data volume, deployment frequency, and how much testing the change needs before it touches production.

Are Data Migrations Always a Deployment Risk?

Picture the migration that causes an incident. It's rarely the one everyone was nervous about. It's the quick one nobody thought to test, run against a table three times bigger than anyone remembered. A five-row backfill and a fifty-million-row transformation are not the same problem, even though Rails lets you write both the same way.

Choosing an approach means matching the blast radius of a data change to the amount of control, testing, and rollback safety the situation demands. Developer preference has surprisingly little to do with it.

Not sure which approach fits your team's situation, or wondering whether to bring in outside Ruby on Rails help for the migration itself?

Monterail's Ruby on Rails team can help you audit the risk and build the right process before it costs you a deployment.

Rails Data Migrations FAQ

Hubert Białęcki avatar
Hubert Białęcki
Head of Technology at Monterail
Linkedin
As Monterail’s Head of Technology, Hubert brings a unique blend of technical expertise and strategic leadership to drive innovation and organizational growth. A graduate of Wrocław University of Science and Technology, Hubert has carved an impressive career path from JavaScript developer to technology executive, demonstrating both technical mastery and exceptional leadership capabilities. He excels at understanding complex organizational dynamics and implementing strategic changes that enhance team performance and company development.