Understanding Active Record Migrations

Active Record migrations (within Ruby on Rails) are a convenient way to alter your database schema of a period of time. This overview/guide is a look at just what you can achieve with migrations and why they are the backbone of any given Ruby on Rails application. We’ll talk about conventions, techniques, and how to extend a migration to fit your application’s own respective needs.

What are migrations?

Active Record migrations use a Ruby DSL (Domain specific language) to generate the necessary SQL automatically so you don’t have to author it by hand. This allows you to keep your schema changes to be database independent.

Think of each migration as a new version of the database. You can add to the schema or subtract from the schema.

To view the schema is a Ruby on Rails application head to db/schema.rb.

The syntax of a migration might look similar to the following:

class CreatePosts < ActiveRecord::Migration[5.2]
  def change
    create_table :posts do |t|
      t.string :title
      t.text :content

      t.timestamps
    end
  end
end

Once migrated a new database table called posts will be created. Inside the table will be a title column with the type string, a column column with the type text, and two timestamp columns called created_at and updated_at which you get for free with the t.timestamps line.

Creating this happens automatically with a migration of which you can generate from the command line. In most cases the “creation” of a table happens when you create a new model within rails.

That might look like this:

$ rails g model Post title:string content:text

Notice the conventions at play here. The model name in this generation needs to be singular but translates down to plural. So while the command states Post. A new migration file within db/migrate will contain a posts.

The change method allows the migration to “change”. So in the event, you need to roll back your database:

$ rails db:rollback

You can rest assure, removing those tables, columns, etc.. will happen smoothly.

Creating Active Record migrations

Imagine you’re building a new feature to your Ruby on Rails application and you need a new column on a table you’ve already generated by creating a new model within the rails CLI.

To create a new migration you can call to it from the command line much like other generators.

$ rails generate migration AddSkuNumberToProducts sku_number:string

Here, it’s assumed I already have a Product model/table created. To extend the table I want to add a new column for a sku number to keep track of it internally.

There’s some awesomeness happening here that I love:

  • The migration internals will know that if you add “Add” or “Remove” to the beginning of what you name your migration it will automatically generate the necessary Ruby DSL code to add or remove a column
  • When a migration is born, the file gets timestamped and saved to db/migrate. There you can reference it as well as see the history of the migrations for the app.
  • On the command line, you can optionally pass “columns” and their “type” to save some time. There are cases where it’s easier to write these by hand after creating the migration. Simply leave out any columns names to do this and head to the file that gets generated to make changes. You can declare however many you want.
  • Pro tip. If you need a column with a type of string. On the command line, you can optionally omit the :string declaration of the column name you pass. The default is this type which can be a timesaver

The migration above generates the following:

class AddSkuNumberToProducts < ActiveRecord::Migration[5.2]
  def change
    add_column :products, :sku_number, :string
  end
end

Because of the migration naming conventions, the migration generated the correct change to implore on the products table in the database. This kind of magic helps keep you out of the weeds of SQL which is a huge perk of migrations.

Helpful variants

Want to add an index to that last migration?

$ rails generate migration AddSkuNumberToProducts sku_number:string:index

It’s as simple as passing :index at the end of the column you are making changes to.

That looks like this in the end:

class AddSkuNumberToProducts < ActiveRecord::Migration[5.2]
  def change
    add_column :products, :sku_number, :string
    add_index :products, :sku_number
  end
end

More advanced greatness

$ rails generate migration AddDetailsToProducts 'price:decimal{5,2}' supplier:references{poloymorphic}

You can do a lot with one bash command to generate a pretty unique migration. This generates the following:

class AddDetailsToProducts < ActiveRecord::Migration[5.2]
  def change
    add_column :products, :price, :decimal, precision: 5, scale: 2
    add_reference :products, :supplier, polymorphic: true
  end
end

Type Modifiers

On top of columns and tables, you can pass modifiers through on a given migration. These might depend on certain scenarios and many will need to be added manually instead of from the command line. From the rails documentation, we get this list.

  • limit Sets the maximum size of the string/text/binary/integer fields.
  • precision Defines the precision for the decimal fields, representing the total number of digits in the number.
  • scale Defines the scale for the decimal fields, representing the number of digits after the decimal point.
  • polymorphic Adds a type column for belongs_to associations.
  • null Allows or disallows NULL values in the column.
  • default Allows setting a default value on the column. Note that if you are using a dynamic value (such as a date), the default will only be calculated the first time (i.e. on the date the migration is applied).
  • index Adds an index for the column.
Up and Down instead of Change

If you’ve seen older tutorials and/or Rails apps/gems in the wild you might notice a different syntax within each migration file.

Instead of:

class CreateProducts < ActiveRecord::Migration[5.2]
  def change
    create_table :posts do |t|
      t.string :title
      t.text :content

      t.timestamps
    end
  end
end

You see something like:

class CreateProducts < ActiveRecord::Migration[5.2]
  def up
    create_table :posts do |t|
     t.string :title
     t.text :content

     t.timestamps
    end
  end

  def down
    remove_table :posts do |t|
     t.string :title
     t.text :content

     t.timestamps
    end
  end
end

Both work equally well, though the change method seems DRY-er no? When in doubt, use it.

Instead of using the UP or DOWN methods I would suggest creating another migration to perform a new change to the database schema.

A better approach?

Imagine you wanted to remove the sku_number entry from before. You could make a new migration for that.

$ rails generate migration RemoveSkuNumberFromProducts sku_number:string

This would create a new file in db/migrate

class RemoveSkuNumberFromProducts < ActiveRecord::Migration[5.2]
  def change
    remove_column :products, :sku_number, :string
  end
end

This is easier to rollback and allows you to pivot your app if the need ever arises.

Invoking relationships

Wouldn’t it be nice to get relationships defined automatically? In many applications, you need to relate models by adding a modelname_id to another table. This can be done with migrations but you can also speed up that process if you already know what direction you’re heading.

Imagine you have a Product model that you want to associate it with a given user. (It’s assumed you already have a Usermodel set up.)

$ rails generate migration AddUserRefToProducts user:references

The user:references line allows you to automatically create a user_id column with the appropriate index all in one command.

class AddUserRefToProducts < ActiveRecord::Migration[5.2]
  def change
    add_reference :products, :user, foreign_key: true
  end
end

Related content

Shameless Plug!

If you liked this post, I have more videos on YouTube and here on my blog. Want more content like this in your inbox? Subscribe to my newsletter and get it automatically.

Check out my course


https://i2.wp.com/i.imgur.com/KIaaJEB.jpg?ssl=1

☝ Want to learn Ruby on Rails from the ground up? Check out my upcoming course called Hello Rails.