As young developers, we often get caught up in what we don’t know. One of the first and hardest lessons to learn when you’re starting out as a programmer is the sheer volume of things that you don’t know. You have to get comfortable not knowing them, and you have to work towards learning as much about them as you can.
However, sometimes we focus so much on learning new things, that we forget to come back to what we already know – or at least, what we think we already know. In fact, this is exactly what I’ve been guilty of for the past few weeks. I’ve been focusing so much on learning new frameworks and getting comfortable with other languages, that I forgot to question my knowledge of Rails. Since it was a known domain and language, I assumed that I didn’t need to revisit it that often.
But boy, was I wrong. Just because you’re familiar with something doesn’t mean that you understand it completely. The trick to dealing with this is to make yourself feel uncomfortable in your otherwise familiar language. And that’s exactly what happened to me. Last week, while writing some lines of SQL (yes, really), I ran across a database migration that used an up
and a down
method. I saw those lines of code and realized something: I had no idea how my database really works. In fact, I’ve written so many migrations in so many Rails applications that, at some point, I’ve stopped thinking about what was actually going on under the hood. So, I set out to make myself uncomfortable and uncover the secret life of my database.
Teach Me How To Migrate
Most of us were introduced to Rails databases through migrations. Migrations are how we alter our database schema over time. They implement a Ruby DSL (domain-specific language), and run SQL queries in our database for us. And, they are super easy to learn to use and understand:
1
|
|
Running a command like the one above prompts Active Record to create a unique, timestamped file within our /db
directory, with a migration class that might look something like this:
1 2 3 4 5 6 7 8 9 10 |
|
Nothing too surprising here; we’ve seen this kind of change
method before. But what you may not have seen – or at least, understood – is something that looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
So, what does that up
and down
actually mean? What is going up, exactly…and what’s going down? And how is this different from using the change
method, which probably seems way simpler right about now? Well, it’s time for us to find out.
All The Migrations Fit To Run
The first step to understanding how something works is by unpacking it, step by step. And that’s what we’ll do with our migrations. Let’s first look at how many migrations we currently have.
We’ve already generated a few migrations using the rails generate migration
commands for our in-progress bookstore application. We can take a look at what all those migrations look like by running the rake db:migrate:status
command, which will show us the status of our migrations, including any pending ones we may have:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Whoa – look at all those migrations! And more importantly, look at the column to the left of the migrations: seem familiar? Each one of our migrations has a status
, which is either up
or down
. Let’s remember this, we’re going to come back to it in a second.
According to the Rails source code, the rake:db:migrate:status
rake task displays the status of all our migrations. This can be helpful in determining if we have any migrations we need to run. And it looks like we do! Let’s run rake db:migrate
and then check the status of our migrations again with rake db:migrate:status
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Hm, interesting. That last migration, which adds a user_id
column to our reading_lists
table, now has a status of up
. We’ve basically migrated our database up, meaning that we have no pending migrations and all of our migrations are up to date.
But what if we wanted to rollback our migration, instead? Or, better yet, what if we wanted to rollback the last two migrations? Could we go back in time? Back to when we didn’t even have a reading_lists
table in our database? How would we do that?
Well, we could run a command like rake db:rollback STEP=
, which rolls back our database however many steps we specify. For this example, we’ll just rollback to two migrations ago (rake db:rollback STEP=2
). What is the state of our database now? Let’s check the migrate:status
again:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Nice! We’ve migrated our database down, back to two migrations ago. There’s some serious Back To The Future stuff going down right now.
What Your Database Won’t Tell You (Unless You Ask)
Okay, so we can migrate up and down a database. Cool. We can go back and forth in time, to older versions of our database and to newer – and even the newest! – version of our database. Double cool. But what about what we originally set out to figure out? What do the up
and down
methods do in our migrations? And why do we only see them some of the time?
If you had an inkling that the up
and down
methods were somehow interlinked with the migration status
column we saw when we ran rake db:migrate:status
…well, your inkling was right on the money. They are connected, and in a really cool way.
The up
and down
methods are defined on a Migration
class, which inherits from the ActiveRecord::Migration
class. Active Record literally puts the M (model) in MVC, since it’s what handles all of our database records. And Active Record is pretty darn smart. Depending on which rake task we provide it, Active Record will execute either an up
method in a migration file, or a down
method.
The Rails Guides explain this pretty well:
“The up method should describe the transformation you’d like to make to your schema, and the down method of your migration should revert the transformations done by the up method. In other words, the database schema should be unchanged if you do an up followed by a down. For example, if you create a table in the up method, you should drop it in the down method. It is wise to perform the transformations in precisely the reverse order they were made in the up method.””
As you might have guessed, these two methods are inverse of each other in both form and function. The up
method is called when migrating “up” the database – forward in time – while the down
method is called when migrating “down” the database – or, back in time. In other words, the up
method is a set of directions for running a migration, while the down
method is a set of instructions for reverting a migration. This implies that the code in these two methods should fundamentally do the opposite things of one another.
This also means is that only one of these methods can ever actually run during a migration. If we run rake db:migrate
, all of the up
methods will execute, and every migration that is currently set to down
will change to a status of up
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
And if we run rake db:rollback
, the most recent migration that was run (and had its status set to up
) will be reverted by calling the down
method in the migration file. If we run rake db:rollback STEP=
, the down
method will be invoked in every migration file we specify we want to rollback (or how many steps back we want to go in the database’s history):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
And, we could even run or revert a specific migration by giving ActiveRecord the version number/migration id of the migration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
In the example above, when we ran a migrate:down
task, all that was happening is that only the down
method written in the “AddMediaColumnsToBooks” migration file was was invoked!
Neat, right!? Or maybe just kind of mind-blowing if you’ve never seen this before. Who knew that this is what migrations were really all about? Well, now you and I both know, so that’s pretty rad.
Why Change When You Can Go Up And Down?
The change
method is pretty standard when it comes to migrations partly because it’s a newer addition to Rails. Just like up
and down
, the change
method is defined on the ActiveRecord::Migration
class. In fact, it does exactly what up
and down
accomplish together. The change
method is usually able to automatically figure out the inverse operation you provide it; for example, if you call create_table
inside of the change
method, when you run rake db:rollback
, it will drop_table
. The same goes for add_column
and remove_column
.
So, if the change
method can do all of these things in one go (rather than in two methods), why do we sometimes see an up
and down
method defined together in a migration file?
Well, there are many times when we might want Active Record to be smart and figure out when to drop a column or table. But other times, it might not be as clear.
For example, what if we wanted a migration that just created or fixed data? We wouldn’t want ActiveRecord to try to figure out whether to add or remove a column…or worse, drop our table! Or what if we wanted to remove columns when we migrated up, and add columns when we migrated down? We’d have to specify that explicitly in our up
and down
methods.
We could even get fancy with some SQL and do something a bit more granular like rename a table:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
In this example, we’re using the execute
method, which takes either a string value of a SQL query, or a heredoc like the one we’ve written above.
And we might even want to make it completely impossible for someone to ever revert a migration. We could specify that kind of behavior in our down
method by raising an ActiveRecord error:
1 2 3 |
|
Depending on which migration file this down
method lived in, that migration could never be “migrated down”. This can sometimes be dangerous, but also useful – particularly if we had many other data models that were depending on those tables existing in the first place!
It turns out that our database is a pretty powerful thing. Rails provides us with a lot of functionality and flexibility of moulding it to be exactly the way that we want it to be. It’s just up to us to take advantage of it in the right situations! The more we learn about shaping our database, the better equipped we’ll be to creating effective, concise, and streamlined databases for each and every one of our applications.
But the truth is, we’ve only scratched the surface of databases today. Tune in again next week, when I’ll uncover the secret life of all your…JOIN TABLES! Try and contain your enthusiasm, my friends.
tl;dr?
- The
up
anddown
methods are a more granular way of defining thechange
method in a Rails migration. Theup
method is a set of instructions of what to do when you migrate, and thedown
method is a set of directions of what to do when you rollback. - Want to see more examples of when you might want to use the
up
anddown
methods in place of thechange
method? Check out the Rails Guides on Active Record Migrations. - Need to read more on running migrations? Read this. Gotta brush up on migrations after reading this post? Head over here.