Active Record Database Migrations Cheatsheet

Rails Database Migrations Cheatsheet

The database schema evolves continuously. Rails migrations is a powerful and flexible way to update the schema without dropping and re-creating the database. However, all this power and flexibility can be overwhelming. Here's a handy cheatsheet for most common migration operations.

11 min read
If you want to skip reading the ~2800 word article knowing you'll forget most of it anyway, download the PDF cheatsheet.

Rails provides a ton of different ways to modify the database schema via Active Record migrations. It's a very powerful and flexible way to handle schema changes while keeping all updates under version control. However, all the migration methods, options, conventions, and their variations can be overwhelming, and hence, confusing.

I found myself reaching out for the Rails migration guide and the Rails API almost every time I wanted to create a new Rails migration to make database changes. So last night I decided to spend a few hours to note down all the common database operations I use most of the time.

Now, this is not an exhaustive reference of ALL the methods and options. You'll still need to refer to the Rails API for the obscure ones. I've just listed the stuff I use most of the time, but I'm sure many of you will find it useful, too.

Table of Contents

Sounds fun? Let's begin...


Generating Migrations

To generate a database migration, use the rails generate migration command.

$ rails generate migration CreateCustomers name:string email:string
      invoke  active_record
      create    db/migrate/20230829033126_create_customers.rb

After running this command, Rails will add the new migration in your project's db/migrate directory. Each migration filename contains a timestamp that Rails uses to determine the order of the migrations.

# db/migrate/20230829033126_create_customers.rb

class CreateCustomers < ActiveRecord::Migration[7.1]
  def change
    create_table :customers do |t|
      t.string :name
      t.string :email

      t.timestamps
    end
  end
end

When you create a new table, Rails will add t.timestamps at the end of the definition. It adds two columns to the table: created_at and updated_at.

In the above command, we provided CreateCustomers as the name of the migration. Typically, Rails will try to guess the name of the table using the name of the migration, i.e. customers in this case. If Rails can figure out the table name from the migration name, it will pre-fill the generated migration file with the specified table. Otherwise, you can simply edit the migration to specify the table name.

Migration Structure

The migration file contains a change method, which is used to add new tables, columns, or indexes to your database. In this method, you can use the TableDefinition object to create and modify tables.

In this example, we're creating the customers table with two string columns named name and email. By default, Rails also adds the created_at and updated_at columns with the timestamps methods.

# db/migrate/20230829033126_create_customers.rb

class CreateCustomers < ActiveRecord::Migration[7.1]
  def change
    create_table :customers do |t|
      t.string :name
      t.string :email

      t.timestamps
    end
  end
end

Applying Migrations

Once you're satisfied with the migration code, you've to run that migration. To run the newly created migration (or all outstanding migrations), run the rails migrate command:

$ rails migrate

To verify which migrations have been already applied, use the rails db:migrate:status command:

$ rails db:migrate:status

database: storage/development.sqlite3

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20230829171000  Create customers

Revert (Rollback) a Migration

If you made a mistake, or noticed a typo in the migration that you just ran, use the db:rollback command to roll back the previous migration. This command reverts the schema back to the previous version.

bin/rails db:rollback

You can roll back a specific number of migrations by providing the STEP option to the rollback command. For example, the following command will roll back the last five migrations:

rails db:rollback STEP=5

After fixing the related migrations, you've to migrate the database again by running the db:migrate command.

Roll Back and Migrate Using A Single Command

The db:migrate:redo command will roll back the database one migration and re-migrate the database.

bin/rails db:migrate:redo

As with the rollback command, you can roll back and migrate a specific number of migrations by passing the STEP option to the redo command. The following command will roll back and re-migrate the last five migrations:

bin/rails db:migrate:redo STEP=5

The db:migrate:reset command drops and recreates all databases from their schema for the current environment and loads the seeds. This command effectively re-creates your entire database.

bin/rails db:reset

Common Rails Database Commands

You can see all the available commands in Rails by running the bin/rails --help command. To narrow down the database-related commands, grep it on the database prefix, i.e. bin/rails --help | grep db. Here're the common ones.

# Create the database from DATABASE_URL or config
bin/rails db:create

# Load the seed data from db/seeds.rb
bin/rails db:seed

# Create databases, load schema and seed data
bin/rails db:setup

# Same as setup, but drop databases first
bin/rails db:reset

# Run setup if database doesn't exist, otherwise migrate
bin/rails db:prepare

# Generate a migration
bin/rails generate migration CreateCustomers name:string:index email:string

# Migrate the database
bin/rails db:migrate

# Show status of migrations
bin/rails db:migrate:status

# Revert (rollback) the previous (n) migration(s)
bin/rails db:rollback STEP=n

# First rollback, then migrate previous (n) migration(s)
bin/rails db:migrate:redo STEP=n

For a detailed overview of these tasks, please refer to this blog post:

All the Database Tasks in Rails
Once you start programming in Rails, you inevitably come across db:migrate, which applies the migration to your database. Then you make a mistake and learn about db:rollback to revert the previous migration. After programming for a while, you stumble upon db:reset, to drop and recreate your databas…

Naming Conventions for Active Record Migrations

You can flesh out the structure of the migration during generation by following a few simple conventions. Here're the most common ones I've found. If you know more, let me know in the comments.

# Creates a table named 'Table' with provided columns
bin/rails g migration CreateTable col:type col:type

# Adds column 'Column' to table 'Table'
bin/rails g migration AddColumnToTable col:type

# Removes column 'Column' from table 'Table'
bin/rails g migration RemoveColumnFromTable col:type

# Adds the `amount` column on the `carts` table with precision 10 and scale 2, and
# adds the `user_id` column on the `carts` table (cart references user)
bin/rails g migration AddAmountToCarts 'amount:decimal{10,2}' user:references

# Creates a join table between `customers` and `products` tables.
bin/rails g migration CreateJoinTableCustomerProduct customer product

Working with Tables

ActiveRecord provides a bunch of methods to create, update, and drop tables in the database. Let's explore the most common ones.

Create Table

To create a new database table, use the create_table method. This method accepts the name of the table and a block that's used to define the new table, e.g. to add columns, indexes, etc.

# create_table(table_name, id: :primary_key, primary_key: nil, force: nil, **options)
create_table :subscribers do |t|
  t.string :name
  t.string :email
end

It creates a table named subscribers. Assumes an auto-incrementing, integer primary key named id.

t is an instance of ActiveRecord::ConnectionAdapters::TableDefinition. It represents the database table schema and lets you manipulate the schema representation.

To create a join table, use the create_join_table method.

# create join table between customers and products tables.
create_join_table :customers, :products

When creating the table, you may use any of the ActiveRecord's column methods to define the table's columns.

Options:

  • :primary_key - Change the primary key name. Default is :id
  • :force - Drop the table if it already exists. Warning: This will result in unwanted data loss, if the existing table has data.
  • :if_not_exists - If you try to create a table with duplicate name, Rails will throw an error. This option lets you return silently without raising an error, if the table already exists. Default is false.
  • :options - Provide database-specific options.
  • :temporary - Create a temporary table that will only exist during the current connection to the database.
  • id: false - Do not generate a primary key at all.

Column Options: See the Create Column section for available column options.

Here's a table that shows all available column types along with their corresponding values in different databases.

Type Description MySQL SQLite PostgreSQL
:integer whole number int(11) integer integer
:bigint very large integer bigint integer bigint
:float floating point number float float float
:decimal financial calculations decimal decimal decimal
:boolean true or false tinyint(1) boolean boolean
:string 255 characters varchar(255) varchar(255) character varying(255)
:text unlimited text text text
:date y, m, d (no time) date date date
:time h, m, s (no date) time time time
:datetime date and time datetime datetime timestamp
:timestamp same as datetime datetime datetime timestamp
:json JSON data json - json
:binary raw binary data blob blob bytea

Update Table

Use the change_table method to update existing tables. Like create_table, the change_table method accepts the name of the table and a block to add columns or indexes to the table:

change_table :products do |t|
  t.remove :description, :name
  t.string :service_number
  t.index :service_number
  t.rename :creator, :maker
end

Rename Table

To rename an existing table, use the rename_table method. The following method renames the from_table to to_table.

rename_table('from_table', 'to_table')

Drop Table

To drop an existing table, you can use the drop_table method.

drop_table('users')

Note: drop_table ignores the provided block and options when running the migration, but uses them when reverting the migration to generate the corresponding table.

def change
  drop_table :accounts

  drop_table :posts do |t|
    t.string :title
    t.text :content
  end
end

Referencing Other Tables

The naming convention for foreign keys is the singularized table name followed by _id.

For example, if table projects depends on table clients, i.e., a project belongs_to a client and a client has_many projects, then projects table will have a client_id column in it.

To add this foreign key, use the add_reference :table_name, :reference method. For example, if the posts table references a user, then posts will have a user_id column in it.

def change
  # add a reference of user to posts.
  add_reference :posts, :user 
  # is same as
  add_belongs_to :posts, :user
end

Alternatively, you can use the references method on the TableDefinition instance yielded to create_table or change_table methods.

change_table :posts do |t|
  t.references :user
  # or
  t.belongs_to :user
end

Note: Referencing another table will also create an index.

Working with Columns

Similar to table, ActiveRecord provides various ways to work with columns. You can add new columns, modify existing ones, or delete them altogether. Let's explore the related methods.

Create Column

As seen in the section on creating tables, you can specify new columns while creating a new table. But if you want to add new columns to an existing table, use the add_column method.

The add_column(table_name, column_name, type) method adds a new type column named column_name to table_name.

# add_column(table_name, column_name, type, **options)

add_column(:users, :picture, :binary)

Available Column Types

The type parameter is normally one of the migrations native types, which is one of the following: :primary_key, :string, :text, :integer, :bigint, :float, :decimal, :numeric, :datetime, :time, :date, :binary, :blob, :boolean.

Column Options

  • :default - sets the default value. If you don't set it, it will be NULL.
  • limit: size - adds a limit for the column. For string types, it's number of characters. For others, it specifies the number of bytes.
  • null: false - adds a `not null` constraint.
  • precision: 10 - specifies the precision (total # of digits) for :decimal, :numeric, :datetime and :time columns.
  • scale: 3 - specifies the number of digits to the right of the decimal point for :decimal and :numeric columns.
  • if_not_exists: true - do not add the column if it already exists (to avoid duplication error)
  • comment: text - adds a comment that will be added to schema.rb.

Note: The same options apply while crating a new table with new columns.

Update Column

The change_column method allows you to modify the type and attributes of existing columns. For example, to increase the size of a name column, use the change_column method as follows:

# change_column(table_name, column_name, type, **options)

change_column :users, :name, :string, limit: 80

Rename Column

To rename a column, use the rename_column method.

# rename_column(table_name, column_name, new_column_name)

rename_column(:suppliers, :description, :name)

Renames the column from description to name on the suppliers table

Drop Column

Finally, to drop a column, you may use the remove_column method.

remove_column :users, :password

Alternatively, you can also call the rename_column and the remove_column method on the table definition, while changing a table.

change_table :users do |t|
  t.remove_column :password
end

If you want to drop multiple columns from a table, pass the list of column names to the remove_columns method.

remove_columns :users, :password, :address, :phone

Working with Indexes (Indices)

Database indexes provide a way to quickly lookup the requested data. An index is a powerful data structure that makes your database queries faster.

The database uses these indices to search for data without looping through every row in the table. The indices are updated automatically whenever new data is added or removed.

Create Index

ActiveRecord supports several types of indexes. Use the add_index or t.index method to add new index. Additionally, you can mark the index as unique or give it a different name.

The following example creates a new email column and specifies that its values should be unique.

create_table :users do |t|
  t.string :name, index: true
  t.string :email, index: { unique: true }
  t.index [:title, :address], unique: true
end

Alternatively, you may use the add_index method.

def change
  # adds a new index on the `posts` table for the `title` column. 
  add_index :posts, :title

  # adds a new index for the `company_id` and `manager_id` columns;
  add_index :suppliers, [:company_id, :manager_id]
end

You may even pass an array of columns to the add_index method to create a compound (or composite) index:

add_index(:accounts, [:branch_id, :party_id], unique: true)

When creating an index, ActiveRecord will automatically generate an index name based on the table, column names, and the index type, but you may pass a second argument to the method to specify the index name yourself.

create_table :users do |t|
  t.string :name, index: true
  t.string :email, index: { unique: true, name: 'unique_emails' }
end
  
 # OR
  
add_index(:accounts, [:branch_id, :party_id], unique: true, name: 'by_branch_party')

Rename Index

To rename an index, use the rename_index method. This method accepts the table name as its first argument and the current index name and the desired name as its second and third arguments, respectively.

rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'

Alternatively, use the rename_index method while changing a table. This is useful when you want to change multiple index names in a single migration.

change_table :users do |t|
  t.rename_index(:user_id, :account_id)
end

Drop Index

To drop an index, you may use the remove_index method, passing the name of the table and either the column name or the name of the index.

remove_index :accounts, :branch_id

remove_index :accounts, column: [:branch_id, :party_id]

remove_index :accounts, name: :by_branch_party

Alternatively, use the remove_index method while changing the table.

t.remove_index(:branch_id)
t.remove_index(column: [:branch_id, :party_id])
t.remove_index(name: :by_branch_party)
t.remove_index(:branch_id, name: :by_branch_party)

I'll stop here. If you think this cheatsheet should include any other operations, methods, or options, please let me know in the comments below.

Download the PDF cheatsheet.

Here's the preview:

Active Record Database Migrations Cheatsheet
Active Record Database Migrations Cheatsheet

That's a wrap. I hope you liked this article and you learned something new. If you're new to the blog, check out the start here page for a guided tour or browse the full archive to see all the posts I've written so far.

As always, if you have any questions or feedback, didn't understand something, or found a mistake, please leave a comment below or send me an email. I reply to all emails I get from developers, and I look forward to hearing from you.

If you'd like to receive future articles directly in your email, please subscribe to my blog. If you're already a subscriber, thank you.