Find an article

Start typing to search the archive.

← All articles

Rerun a stale migration in Rails

Figure out how to apply a migration that has already run when the standard migration-related Rake tasks don't work.

Updated

Rerun a stale migration in Rails

If you are searching for a way to simply rerun a Rails migration, you can use the command rails db:migrate:redo VERSION=xxxxxxx or just rails db:migrate:redo to rerun the last migration. However, this post is not about that. It is about the case when this command does not work.

What is a stale Rails schema migration

Consider the following scenario. You are reviewing a colleague’s pull request (PR). That PR has a migration that adds a new table into the DB. You run that migration to test the PR. The migration creates the settings table. You go to another branch and continue your work. But you’ve forgotten to reverse the migration so that it doesn’t mess up your local DB state. Later, the PR gets this table renamed to user_settings. Finally, it gets merged.

Then you update the main branch and run migrations. Surprisingly, you’ve got changes in schema.rb. Git shows that it wants to add settings and remove the user_settings table. You realize that you’ve run the migration that’s kinda stale now. You try to roll this migration back with rails db:migrate:down VERSION=20220505072316 command. No luck:

ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR:  table "user_settings" does not exist
: DROP TABLE "user_settings"

Dead-end? See how to cope with it.

How Rails knows which migration has already run

Rails has a special DB table, schema_migrations, that tracks which migrations have already run. Look at its structure inside the DB console (jump into it with rails db command or use any other DB client). This is the description of this table inside PostgreSQL:

~# \d schema_migrations
                 Table "public.schema_migrations"
 Column  │          Type          │ Collation │ Nullable │ Default
═════════╪════════════════════════╪═══════════╪══════════╪═════════
 version │ character varying(255) │           │ not null │
Indexes:
    "unique_schema_migrations" UNIQUE, btree (version)

Whenever you run a migration, it inserts a new row into this table with the version column equal to the timestamp of the migration file. For example, a migration inside the file 20220505072316_create_settings.rb inserts a row with version=20220505072316.

The next time you run migrations, Rails checks if that migration file timestamp is already inside the schema_migrations table. If it’s not there, it applies the migration, otherwise, it skips it. That way, the rails db:migrate command guarantees idempotency.

How to get the stale Rails migration fixed

That knowledge gives us a clue on what to do. If we delete that migration timestamp from schema_migrations, we can run it again. That will add the user_settings table but won’t delete the stale settings table. But that’s not a big deal - we can do that manually in the DB console. Once we do that, the rails db:migrate command won’t generate any diff anymore.

Run these commands within the DB console:

delete from schema_migrations where version = '20220505072316';
drop table settings;

Run rails db:migrate and make sure there are no changes on your schema.rb.

How to see if a migration is run

It’s time for a useful tip. Rails has a built-in rake task that shows status for all migrations - db:migrate:status. Run this rails db:migrate:status in your terminal from the Rails application folder and you will see something like that:

database: myapp_development

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20220923211114  Initial
  down    20230330161714  Create projects

After running the rails db:migrate:status command on my pet project, the output displayed two migrations along with their corresponding statuses. When a migration has a status of up, it means that it has already been executed. Conversely, when the status is down, it indicates that the migration has not been executed yet. If you run rails db:migrate, any migrations with a status of down will be executed.

How to avoid getting stale migrations

Not long ago, I implemented a solution aimed at automating tasks to prevent falling into the trap of stale migrations. This is where the actual_db_schema gem comes in. Please feel free to install and use it, and your feedback would be greatly appreciated. You can find more information about this gem in the Keep DB schema clean and consistent between branches article.