A migration is the Rails facility which alters a database’s structure. Migrations are an essential aspect of all database development with Rails and they generally work quite well with the default command:
bundle exec rake db:migrate
bundle exec rails db:migrate
That command will cause Rails to execute all pending migrations. A pending migration is one where the timestamp on the migration is not stored in the table schema_migrations.
The trick with migrations is understanding how to manipulate them, replay them, etc when they fail.
The Hard Way - The schema_migrations Table
Let’s start with the hard way - manipulating schema_migrations table directly. Let’s say that you need to re-run the last 2 migrations. Here’s what you need to do:
Start by getting the timestamps of the last two migrations. You can do this with an ls -ltr db/migrations and pick them out of what might be a giant list of migrations (my current work project has 858 migrations) – and this can, well, suck. You can also be smart about it and use tail to get just the last few (default 8 on OSX) migrations.
ls -ltr db/migrate | tail -rw-r--r-- 1 sjohnson staff 201 Jul 18 04:44 20190717234458_create_units.rb -rw-r--r-- 1 sjohnson staff 120 Jul 18 04:44 20190717235922_add_unit_id_to_habits.rb -rw-r--r--@ 1 sjohnson staff 154 Jul 18 04:44 20190718000145_add_unit_id_to_habit_tasks.rb -rw-r--r--@ 1 sjohnson staff 129 Jul 18 04:44 20190718075945_add_unit_preferences_json_to_users.rb -rw-r--r--@ 1 sjohnson staff 154 Jul 18 04:44 20190718080511_add_unit_type_to_units.rb -rw-r--r--@ 1 sjohnson staff 131 Jul 18 09:28 20190718110444_fix_stupidity_with_float_val.rb -rw-r--r-- 1 sjohnson staff 164 Jul 18 13:33 20190718133051_add_options_to_habits.rb -rw-r--r--@ 1 sjohnson staff 151 Jul 25 15:48 20190725100535_add_has_loggable_tasks_to_habits.rb -rw-r--r--@ 1 sjohnson staff 257 Jul 26 15:21 20190726191446_add_plan_id_to_habits.rb -rw-r--r--@ 1 sjohnson staff 523 Jul 27 03:29 20190726133807_create_plans.rb
Now you can delete from schema_migrations using a database console or by using ActiveRecord in the Rails console so either:
delete from schema_migrations where version in (20190726133807, 20190726191446);
-or- in a Rails console:
ActiveRecord::Base.connection.execute("delete from schema_migrations where version in (20190726133807, 20190726191446)")
Now you would also need to undo any changes your migrations might have partially implemented. If this was table creation then this is a relatively simple “drop table foo” statement but if it was an index creation or something harder, you need to selectively alter individual tables. And even a hard core SQL guy like myself generally doesn’t want to do that. So let’s look at the easier options.
The Easy Way
Happily Rails provides some additional facilities for this allowing you to rollback the last migration or an individual migration.
gets rid of the last migration. I do NOT, however, ever recommend that you do this. I’m currently working on an active side project where pull requests are flowing and even when you might think that you know what the last migration you created was, you may not realize that another developer slipped a migration in and whammo, “Houston we have a problem”.
My recommendation is to always specify the version with:
rake db:migrate:down VERSION=20190726133807
This is absolute and will only affect the migration in question. Given that I’m a big damn fan of always understanding the state of my persistent storage, it isn’t surprising that I recommend this.
All of this can be easily referenced: