Anatomy of a Migration

Migrations are subclasses of the Rails class ActiveRecord::Migration. When necessary, migrations can contain up and down methods:

 class​ SomeMeaningfulName < ActiveRecord::Migration
 def​ up
 # ...
 end
 
 def​ down
 # ...
 end
 end

The name of the class, after all uppercase letters are downcased and preceded by an underscore, must match the portion of the filename after the version number. For example, the previous class could be found in a file named 20170425000017_some_meaningful_name.rb. No two migrations can contain classes with the same name.

The up method is responsible for applying the schema changes for this migration, while the down method undoes those changes. Let’s make this more concrete. Here’s a migration that adds an e_mail column to the orders table:

 class​ AddEmailToOrders < ActiveRecord::Migration
 def​ up
  add_column ​:orders​, ​:e_mail​, ​:string
 end
 
 def​ down
  remove_column ​:orders​, ​:e_mail
 end
 end

See how the down method undoes the effect of the up method? You can also see that there is a bit of duplication here. In many cases, Rails can detect how to automatically undo a given operation. For example, the opposite of add_column is clearly remove_column. In such cases, by simply renaming up to change, you can eliminate the need for a down:

 class​ AddEmailToOrders < ActiveRecord::Migration
 def​ change
  add_column ​:orders​, ​:e_mail​, ​:string
 end
 end

Now isn’t that much cleaner?

Column Types

The third parameter to add_column specifies the type of the database column. In the prior example, we specified that the e_mail column has a type of :string. But what does this mean? Databases typically don’t have column types of :string.

Remember that Rails tries to make your application independent of the underlying database; you could develop using SQLite 3 and deploy to Postgres if you wanted, for example. But different databases use different names for the types of columns. If you used a SQLite 3 column type in a migration, that migration might not work if applied to a Postgres database. So, Rails migrations insulate you from the underlying database type systems by using logical types. If we’re migrating a SQLite 3 database, the :string type will create a column of type varchar(255). On Postgres, the same migration adds a column with the type char varying(255).

The types supported by migrations are :binary, :boolean, :date, :datetime, :decimal, :float, :integer, :string, :text, :time, and :timestamp. The default mappings of these types for the database adapters in Rails are shown in the following tables:

db2mysqlopenbaseoracle
:binaryblob(32768)blobobjectblob
:booleandecimal(1)tinyint(1)booleannumber(1)
:datedatedatedatedate
:datetimetimestampdatetimedatetimedate
:decimaldecimaldecimaldecimaldecimal
:floatfloatfloatfloatnumber
:integerintint(11)integernumber(38)
:string

varchar(255)

varchar(255)

char(4096)

varchar2(255)

:textclob(32768)texttextclob
:timetimetimetimedate
:timestamptimestampdatetimetimestampdate

postgresqlsqlitesqlserversybase
:binarybyteablobimageimage
:booleanbooleanbooleanbitbit
:datedatedatedatedatetime
:datetimetimestampdatetimedatetimedatetime
:decimaldecimaldecimaldecimaldecimal
:floatfloatfloatfloat(8)float(8)
:integerintegerintegerintint
:string(note 1)varchar(255)varchar(255)varchar(255)
:texttexttexttexttext
:timetimedatetimetimetime
:timestamptimestampdatetimedatetimetimestamp

Using these tables, you could work out that a column declared to be :integer in a migration would have the underlying type integer in SQLite 3 and number(38) in Oracle.

There are three options you can use when defining most columns in a migration; decimal columns take an additional two options. Each of these options is given as a key: value pair. The common options are as follows:

null: true or false

If false, the underlying column has a not null constraint added (if the database supports it). Note: this is independent of any presence: true validation, which may be performed at the model layer.

limit: size

This sets a limit on the size of the field. This basically appends the string (size) to the database column type definition.

default: value

This sets the default value for the column. As this is performed by the database, you don’t see this in a new model object when you initialize it or even when you save it. You have to reload the object from the database to see this value. Note that the default is calculated once, at the point the migration is run, so the following code will set the default column value to the date and time when the migration was run:

 add_column ​:orders​, ​:placed_at​, ​:datetime​, ​default: ​Time.now

In addition, decimal columns take the options :precision and :scale. The :precision option specifies the number of significant digits that will be stored, and the :scale option determines where the decimal point will be located in these digits (think of the scale as the number of digits after the decimal point). A decimal number with a precision of 5 and a scale of 0 can store numbers from -99,999 to +99,999. A decimal number with a precision of 5 and a scale of 2 can store the range -999.99 to +999.99.

The :precision and :scale parameters are optional for decimal columns. However, incompatibilities between different databases lead us to strongly recommend that you include the options for each decimal column.

Here are some column definitions using the migration types and options:

 add_column ​:orders​, ​:attn​, ​:string​, ​limit: ​100
 add_column ​:orders​, ​:order_type​, ​:integer
 add_column ​:orders​, ​:ship_class​, ​:string​, ​null: ​​false​, ​default: ​​'priority'
 add_column ​:orders​, ​:amount​, ​:decimal​, ​precision: ​8, ​scale: ​2

Renaming Columns

When we refactor our code, we often change our variable names to make them more meaningful. Rails migrations allow us to do this to database column names, too. For example, a week after we first added it, we might decide that e_mail isn’t the best name for the new column. We can create a migration to rename it using the rename_column method:

 class​ RenameEmailColumn < ActiveRecord::Migration
 def​ change
  rename_column ​:orders​, ​:e_mail​, ​:customer_email
 end
 end

As rename_column is reversible, separate up and down methods are not required in order to use it.

Note that the rename doesn’t destroy any existing data associated with the column. Also be aware that renaming is not supported by all the adapters.

Changing Columns

change_column Use the change_column method to change the type of a column or to alter the options associated with a column. Use it the same way you’d use add_column, but specify the name of an existing column. Let’s say that the order type column is currently an integer, but we need to change it to be a string. We want to keep the existing data, so an order type of 123 will become the string "123". Later, we’ll use noninteger values such as "new" and "existing".

Changing from an integer column to a string is one line of code:

 def​ up
  change_column ​:orders​, ​:order_type​, ​:string
 end

However, the opposite transformation is problematic. We might be tempted to write the obvious down migration:

 def​ down
  change_column ​:orders​, ​:order_type​, ​:integer
 end

But if our application has taken to storing data like "new" in this column, the down method will lose it—"new" can’t be converted to an integer. If that’s acceptable, then the migration is acceptable as it stands. If, however, we want to create a one-way migration—one that cannot be reversed—we’ll want to stop the down migration from being applied. In this case, Rails provides a special exception that we can throw:

 class​ ChangeOrderTypeToString < ActiveRecord::Migration
 def​ up
  change_column ​:orders​, ​:order_type​, ​:string​, ​null: ​​false
 end
 
 def​ down
 raise​ ActiveRecord::IrreversibleMigration
 end
 end

ActiveRecord::IrreversibleMigration is also the name of the exception that Rails will raise if you attempt to call a method that can’t be automatically reversed from within a change method.