Migrations are maintained in files stored in the db/migrate folder. Each file contains one more-or-less discrete set
of changes to the underlying database. Unlike most of the code you
write, migrations are not automatically run when you start up Rails,
instead waiting for an explicit command from the rake
tool.
Prior to Rails 2.1, migration files had relatively comprehensible names, such as 001_create_people.rb. Rails 2.1 brought a new naming convention, in which the first part of the name changed from being a sequential number to being a much longer timestamp, like 20080701211008_create_students.rb. The new wider names are incredibly annoying if you’re just one developer creating applications on a laptop with a narrow screen, but help avoid name collisions if you’re a developer working on a team where multiple people can check in their own migrations. (Perhaps the team developers have larger monitors as well?)
While you can create migration files by hand, if you’re going to
work with migrations in the new world of timestamps, you should
probably stick to using script/generate
, which will handle all of that for you. Many script/generate
calls will create migrations
as part of their work toward creating a model and supporting
infrastructure, but if you just want to create a blank migration,
enter the command script/generate
migration
NameOfMigration
, where
NameOfMigration
is a reasonably human-comprehensible description of what the
migration is going to do. (For the name, you can use CamelCase
or underscores_between_words
.) Your result,
depending on the name you give it, will look something like Example 10-1.
Example 10-1. An empty migration file, fresh from script/generate
class EmptyMigration < ActiveRecord::Migration def self.up end def self.down end end
All migrations are descended from ActiveRecord::Migration
. The self.up
and self.down
methods
are the heart of the migration. In theory, at least, they should be
strictly symmetrical. Everything created in self.up
should vanish when self.down
is called, leaving the database
structure in the same state it had before the migration was run. If
you let these two methods get out of sync, you’ll have a very hard
time recovering.
While you’ll obviously be paying attention when writing your applications, and the generators shouldn’t create problems, there’s one situation that might still bite you: an unsaved file you’re editing. If you think you’ve made changes and run the migration forward, but didn’t save the file, and then save the file and roll the migration back....
Unfortunately, fixing it really depends on what exactly you did. Just be careful to make sure that you’ve saved all of your files when editing migrations before running them.
Most of the rest of the chapter will examine what you can put inside of those two methods.
You apply migrations to the database using the Rake tool. You can
run rake --tasks
to see the
ever-growing list of tasks it supports, and most of the
database-related tasks are prefixed with db:
. (In Heroku, you get to rake
through the gear menu.) While you’re
learning Rails, there are only three tasks that you really need to
know, and a fourth you should know about:
db:migrate
You’ll run rake
db:migrate
frequently to update your database to
support the latest tables and columns you’ve added to your
application. If you run your application and get lots of strange
missing or nil object errors, odds are good that you forgot to
run rake db:migrate
. It also
updates the db/schema.rb
file, which is a one-stop description of your database.
db:rollback
If you made changes but they didn’t quite work out,
rake db:rollback
will let
you remove the last
migration applied. If you want to remove multiple migrations,
you can specify rake db:rollback
STEP=
n
, where
n
is the number of migrations you
want to go back. Be careful—when Rails deletes a column or
table, it discards the data. It also updates the db/schema.rb file, which is a
one-stop description of your database.
db:drop
If things have gone really wrong with your migrations,
rake db:drop
offers you a
“throw it all away and start over” option, obliterating the
database you’ve built—and all its data.
db:reset
Using rake db:reset
is a little different from using rake
db:drop
—it obliterates the database and then builds a
new one using the db/schema.rb file, reflecting the
last structure you’d created.
db:create
The rake db:create
command tells the database to create a new database for your
application, without requiring you to learn the internal details
of whatever database system you’re using. (You must, of course,
have the right permissions to create that database.)
Most of the time, rake
db:migrate
will be your primary interaction with rake
. When you run it, it will show
information on each migration it runs, as shown in Example 10-2, using the
migrations from the previous chapter. (The start of each migration is
bolded to make it easier to review the output.)
Example 10-2. Output from Rake, for a set of four migrations
S:students001c simonstl$ rake db:rollback (in /Users/simonstl/Documents/RailsOutIn/current/code/ch09/students001c) S:students001c simonstl$ rake db:migrate (in /Users/simonstl/Documents/RailsOutIn/current/code/ch09/students001c)== 20080701211008 CreateStudents: migrating ============================
-- create_table(:students) -> 0.0046s == 20080701211008 CreateStudents: migrated (0.0048s) ===================== 20080701211027 CreateAwards: migrating ==============================
-- create_table(:awards) -> 0.0054s == 20080701211027 CreateAwards: migrated (0.0056s) ======================= 20080705141325 CreateCourses: migrating =============================
-- create_table(:courses) -> 0.0031s == 20080705141325 CreateCourses: migrated (0.0034s) ====================== 20080705141333 CreateCoursesStudents: migrating =====================
-- create_table(:courses_students, {:id=>false}) -> 0.0026s -- add_index(:courses_students, [:course_id, :student_id], {:unique=>true}) -> 0.0039s == 20080705141333 CreateCoursesStudents: migrated (0.0071s) ============
The timing information may be more than you need to know, but you can see what got called in what migration. If something goes wrong, it will definitely let you know.
Rails will happily let you perform operations on multiple tables from within a single migration. Eventually, that may be an attractive option, but when you’re first starting out, it’s usually easier to figure out what’s going on, especially what’s going wrong, when each migration operates only on a single table.