natemacinnes.github.io

Personal Blog

View My GitHub Profile

How To Handle Database Schema Conflicts in Rails When Rebasing

TLDR;

Rebuild schema.rb at each stage

Before rebasing, checkout and pull the master branch (or the branch we want to rebase onto).

rake db:migrate:status

Everything should be ‘up’, if not, then run migrations,

rake db:migrate

Now switch to the feature branch and rebase.

At the first conflict, unstage the schema diff from the feature branch,

git reset HEAD db/schema.rb

Discard the schema diff from the feature branch,

git checkout db/schema.rb

Rebuild the schema,

rake db:migrate

rebase --continue

The Deets

Collaborating with colleagues and using branches on in a Rails project. Get annoyed of all the conflicting schema.rb files whenever someone else creates a migration?

Let’s assume we’re rebasing a feature branch onto master.

The issue occurs when both master and feature branch contain migrations, leading to conflicts in the schema file - at least one conflict over the ‘version’ declared at the top of the file, and possibly other conflicts.

This ‘version’ number is a timestamp, and will correspond to the filename of the last migration which was added at the time that the schema was generated.

That’s the property that we need to preserve when rebasing,

The version number in the schema file needs to correspond to the migration which was generated last - i.e. the migration with the highest number at the beginning of its filename.

How not to fix it

The temptation is to manually resolve the conflicts by editing the schema.rb file. This risks getting into a state where the committed schema is different to the ‘schema that would be generated by migrating again’.

If that ever happens then at some point someone will run rake db:migrate and get a different schema file. When this happens it’s hard to know what the correct way to resolve this is.

How to fix it

The robust approach is to rebuild the schema at each stage as follows:

Before rebasing, checkout (and pull!) master and ensure that the database agrees with the version of the schema file on remote master. We do this with rake db:migrate:status. Ideally everything should be ‘up’,

if anything is ‘down’ then we probably haven’t migrated everything on local master yet, so running rake db:migrate should sort this out. if there are any lines with NO FILE, like the below, it means that a migration that has been applied to the database doesn’t exist on master and is on a feature branch. Rails has no way of knowing how to rollback this migration, so we need to switch to the branch that added the migration and rollback from there.

up 20170210155150 ********** NO FILE ***********

We can now switch to the feature branch and rebase.

At the first conflict, unstage the schema diff from the feature branch,

git reset HEAD db/schema.rb

Discard the schema diff from the feature branch,

git checkout db/schema.rb

Rebuild the schema,

rake db:migrate

Check the resulting schema diff against the original diff on the feature branch it should be identical… …with one exception: if the migration was created before any of the migrations on master, then the version in the schema file won’t change. This is expected: that version should always relate to the most recently generated migration.

We can now rebase --continue in peace!

Why It Happens

The schema file is constructed from the database, not directly from the migrations When rake db:migrate is run, checks a schema_migrations table in the database (which contains a list of the timestamps of migrations which have been run on this database) and compares that against the db/migrations table:. It then runs any migrations which haven’t been run yet, add adds the timestamps of those migrations into the schema_migrations table, and rebuilds the schema rake db:migrate:status shows the difference between schema_migrations and the db/migrations directory, but without running anything. Schemas in general are mostly reliably built in the same way even if some migrations end up being run out of order, because e.g new indexes are added in alphabetical order, not in migration order.

This post is based on this article by Duncan G M Stuart