Monday, May 9, 2011

How rake db:migrate works

When I first started developing Rails applications, the Database migrations with ActiveRecord seemed so much more advanced then anything I've worked with in .NET (and I've used ADO.NET, LINQ-to-SQL, and Nhibernate). The migrations seemed so intuitive, powerful, and magically. Unfortunately, when the magic doesn't work, you need an understanding of the underlying architecture to fix it.


The Problem

The other day I rolled out a Production update for Kanban for Developers. In it were 2 migrations that added a new integer column to 2 different tables and a bit column to a third table. The migration passed local validation, but when I went to deploy it to production, I issued the command:


rake db:migrate RAILS_ENV=”PRODUCTION”


But running this command gave me an error stating that it couldn't add a duplicate index. The migration name provided was created in the middle of last summer. There have been numerable migrations since then, so for some reason, the system thought this migration had been skipped.


Now, there's a couple of ways to fix the problem. The first, and easiest would have been to remove the index from the database and re-run the command. However, I wanted to learn how db:migrate operated, so I began to look around for something to indicate the migrations that had been executed on the database.


Schema.rb


The first place I began to look was within the folder structure of the web application itself. The db folder would be the logical place to store this kind of information. In this folder is the schema.rb folder, and as one might guess, it contains the schema information for the database.


The file in production had all of the latest additions, though the database itself didn't. Why? Because in my rush to move code to production, I had copied the entire db folder instead of just the migration folder or just the latest migration file. With the power of source control, I reverted the old file and re-ran rake db:migrate. At this point, I received the same error message.


schema_migrations


And the reason for this is because the migration information is stored within the database, and not a file, which of course makes much more sense. Databases are locked down much more then the file system, generally, and provide a much more logically place to store this information. For Rails, this information is stored in a simple table named 'schema_migrations' which contains a single column, the name of the migrations that have been applied.


Resolution

In the end, after ensuring the the index and all of the database changes from the missing migration had indeed been applied, I simply wrote an insert statement to add a record for the missing migration. After this, I was able to proceed with the new migrations and complete the build.


And the schema.rb file? It actually was updated to include the latest changes after I ran rake db:migrate. So while this file isn't how rake db:migrate determines whether a migration has been executed, it does document what database changes have been made in a location separate from the database.