Why you should (almost) never rename your Rails database migrations
Rails database migrations are a common source of confusion when working in large — or multiple — development teams. In particular they are a common source of Git merge conflicts.
How many times have you gone to merge your pull request— approved by a selection of your peers — only to find you have a
db/schema.rb conflict? It’s very annoying right?
And how many times has someone snuck in a migration with a version dated later than yours…or you’ve rebased onto the latest version of
main only to discover a newer migration than yours?
What do you do? If your answer to this question is ‘rename my migration to use a more recent date’, then please STOP! (And read on…)
TL;DR: Once you have generated an ActiveRecord Migration (it being allocated a version number in the file name), you almost never need to change that version number.
ActiveRecord Migrations follows a sophisticated set of protocols that help ensure your databases are as consistent as reasonably possible across multiple environments. Renaming a migration interferes with these protocols.
If you deploy a migration to any environment outside your local machine; a test server for example, or another developer’s machine (perhaps you are pairing, or perhaps another developer runs your migration on their machine to test it), then you should not change the version number of that migration in future.
Because if you do rename your migration you will break all of those environments on which it has already run. The whole point of version numbers in ActiveRecord Migrations is to ensure every environment can determine if a specific migration has been run upon its database or not.
The fact that version numbers are chronological is incidental and of no significance when choosing if a migration should be run or not.
When you run
db:migrate, Rails will check every single migration in the
db/migrate folder against a
schema_migrations table to see if that migration has been run on the target database or not. It doesn’t matter when that migration was created or what version number it has. If the version number is not recorded in
schema_migrations then Rails will attempt to run the migration.
What does matter — of course — is that outstanding migrations are run in the correct order; and here the version numbers are significant. About the only time you ever need to change a version number of a migration is to guarantee that it runs in a specific order, relative to another migration.
When you change a version number you break the system. Because any environment on which your migration has already been run has no way to know it, because the version number on which it depends has changed. It will try to run the migration again and probably break, possibly with undesirable consequences.
But what about the version in
This is where I think most of the confusion — and indeed merge conflicts — are caused.
This version number has nothing to do with what migrations will be run on a given environment with
schema.rb file is there to provide a snapshot of the structure of the entire database so that it can be restored using
db:schema:load. When a database is created using
db:schema:load, Rails will use this
version number to make an assumption about what migrations
Rails assumes that
db/schema.rb describes the state of the database after running all migrations up to
version. In this case, the chronological order of migrations is significant. Wherever your migration is in date order, it doesn’t matter as long as its outcome is correctly represented in
schema.rb and its version is equal to or before the
version defined here. But this only matters when using
db:schema:load. If you are iteratively migrating your database using
db:migrate then this version number is largely irrelevant.
So how do I handle these merge conflicts?
Simply set the version number in
db/schema.rbto the most recent version chronologically, whether that be the one in your branch or the one in ‘theirs’.
If you follow these simple rules, you’ll hardly ever need to think about database migration versions:
- Never rename (the version number part of) a migration once you’ve committed it to your shared repo.
- When you commit your migration, make sure its outcome is correctly represented in
db:migratelocally and committing the changes to your repo).
db/schema.rbis always set to the the most recent version represented therein.
What can go wrong?
Most of the time, nothing. There are two scenarios that could occur when things go out of sync:
- You build your database using
versionis set to the wrong migration. If this occurs and you subsequently run
db:migrateon that database, Rails may try to run a migration that is already present in the schema.
- You build your database using
db:schema:load, but there is a migration with a version before or equal to the one specified in
versionwhose outcome is not reflected in
schema.rb. In this case, running
db:migratewill not run the migration, because Rails will have assumed the migration has been run already.
However, if you follow the simple rules above, this will (almost) never happen.