How to Solve Migration Problems in Rails 5
Here is the conspect from the Lynda course “Rails 5 Essential Training”, video “00026-Solve migration problems”.
Migrations make managing your database schema easy. However, problems often occur when we’re running our migrations and run into a bug in our migration code. It can live your migrations in a broken state, where you can’t migrate up and you can’t migrate down. It’s important to learn how this happens and how to resolve these problems when they occur.
To demonstrate I need to create a bug in my migration code. So let’s start by migrating down. I’m going to use rails db migrate version zero and I’m going to basically take all of my migrations away and then I’m going to introduce a bug into one of the migrations so that we can run into that problem.
Let’s go back over here and inside my AlterUsers migration, that’s what I’m going to change. I’m going to go right here where I have the word “password”, and I’m instead going to change the name of this column to be “broken”. Now of course that’s a column which doesn’t exist so it’s going to give us an error when it tries to do this migration, when it tries to rename the column “broken” into “hashed_password” it’s gonna have a problem.
So let’s try it. Let’s go back over here and let’s run rake db:migrate, and this time we’re gonna to migrate up. So it’s gonna run the first 2 migrations successfully, and then it’s gonna get to our third migration which has a problem. You can see it gives us an error here: “all later migrations canceled, it tells me the problem: “No such column: admin_users.broken”.
Ok, so Rails did run first 2 migrations successfully, and will see those as being complete. Let’s go into MySQL so we can see this:
mysql -u rails_user -p simple_cms_development
Ok, let’s do SHOW TABLES. You see that it did successfully create our users table. We can actually look at the content of schema migrations:
SELECT * FROM schema_migrations
You can see that it did store the fact that it ran those first 2 migrations successfully, and you notice that my table is not called “users” anymore, it’s actually called “admin_users”. So it did this step right here, renamed table “users” to “admin_users”:
If we take a look, we’ll see that it actually added this column “username” and it changed the column “email” to have this definition. It win ahead indeed all of the steps up until the broken step. So let’s go back now, let’s exit out again, and let’s go into our code and let’s fix our problem. So now we just can make this “password” again, which is correct, and let’s try and fix it. Let’s run:
To try and run the migration code up. And we still get an error. Note the error this time is different, this time it’s that the “Table admin_users already exists”, so when it try to rename the table, which was the first step in this up code, it couldn’t do it. Ok, let’s try migrating down again.
rails db:migrate VERSION=0
And we get another error. This time it had “unknown table .users for DROP TABLE “users”. It didn’t even try to run this method, it try to come over here and run this one and drop this table but it couldn’t because that table doesn’t exist anymore, we renamed it. So we’re stuck in this state where we’re in between the 2 migrations. We can neither go up or down and fixing the bug in our code doesn’t solve the fact that we’re now in this in between migrations state. So we’re kind of boxed in.
To resolve it, we have to get our database to either the up state, or the down state. We don’t have any code to move it through half states. One solution could be to open up MySQL and to use MySQL commands, such as drop table and then edit the contents of schema migrations to match what’s there, what’s not there. And that would work. But we’re not gonna do it that way. Instead, let’s solve the problem just by editing our migration file. The problem is that we cut half through this migration. The first 3 lines ran successfully. We can’t run them again.
So what do we need to do? We should comment them out. And the way we can comment them out is by putting a pound sign in front of them. So all those lines are now commented out, so when I’m trying to run this up migration now, the first thing it’s gonna do is run this command, which is the one we wanted to start with. Let’s save the file, come back over and run
Now it completes the rest of the migrations as we would want, and it stores the fact that it successfully completed it. So the migration is considered in place. Of course, we don’t want to leave this commented out, that was just to fix our problem. So let’s uncomment those now, save the file again, and now we should be able to migrate back down and migrate back up again.
So as we saw, the best solution is to go to your migration file, temporarily comment out the part that’s giving you trouble, see if you can get the migration to either an up or a down state, and you can use your migrations normally again. It’s also good to keep your migrations small and concise. Don’t overload them with too many changes, or you’ll create more of this kinds of problems for yourself. Instead, break up each one of the migrations into small pieces. You can have several method calls in one migration, especially if they’re related changes.
But for example you would never want to create 4 tables inside 1 migration. Have 4 different migrations for it! Finally, make sure that all migrations are working 100% correctly in your development environment before you migrate to your production database. You production data is sacred, and you never want to make migration mistakes that might cause you to lose production data. So get those issues resolved in development first.