Rails Database Migration Cheatsheet

Rails Guides: ActiveRecord Migrations

Dangerous Migrations (For Large Scale Production Applications)

Certain migrations can be dangerous to perform in production because of how long they can take or how prone they are to causing exceptions if done improperly.

During a long running migration, the database tables invovled are locked, thus blocking requests to the database from the application.

The strong_migrations gem is a helpful tool for catching “dangerous” database changes. The gems’ README examples do a great job of explaining why certain migrations are “dangerous” at scale.

An alternative to using the gem is to perform any long-running/request-blocking migrations during periods of low request volume. Or to put the application in maintenance mode.

This does not avoid the other advantage of the gem, which is catching unsafe migrations that might need matching applciation layer changes.

Cheatsheet

Comprehensive rails migration cheatsheet

Common Commands

Generate a migration

bin/rails g migration AddColumnToTable

Run all pending rails migrations

bin/rails db:migrate

To migrate a specific migration out of order

rake db:migrate:up VERSION=20100905201547

Database rollbacks

Important, after creating a new migration, perform a rollback to make sure the migration is reversible

bin/rails db:rollback

Rollback a certain number of migrations

rake db:rollback STEP=5

Rollback only one specific migration (out of order) use:

rake db:migrate:down VERSION=20100905201547

Breaking Down Common Database Migration Examples

Creating a new table

class CreateUserTable < ActiveRecord::Migration[7.0]
  def change
    create_table :users do |t|
      t.string :full_name, null: false
      t.string :email, null: false
      t.string :encrypted_password, null: false
      t.boolean :admin, null: false, default: false

      t.timestamps null: false
    end

    add_index :users, :email, unique: true
    add_reference :users, :organization, 
      foreign_key: {on_delete: :cascade},
      index: true, null: false
  end
end
NOTE: Boolean columns should always have a `default: VALUE` and `null: false` constraint set

Adding a foreign key reference to an existing table

NOTE: For operations that change a pre-existing table, make sure to have the strong_migrations gem installed or check the gems README for production-appropriate alternatives to long-running request-blocking operations. These examples here are meant to help understand what can be done, not necessarily recommendations for production.
class AddForeignKeyReference < ActiveRecord::Migration[7.0]
  def change
    add_reference :users, :organization, 
              foreign_key: { on_delete: :cascade }, 
              null: false
  end
end

Adding a foreign key reference with a custom column name

class AddForeignKeyReference < ActiveRecord::Migration[7.0]
  def change
    add_reference :users, :foo_bar_org, 
      foreign_key: { to_table: :organizations, on_delete: :cascade }, 
      null: false
  end
end

Alternative syntax for creating a foreign key reference

class CreateUserTable < ActiveRecord::Migration[7.0]
  def change
    create_table :users do |t|
      t.string :full_name, null: false
      
      # yada yada yada ...
      
      t.references :organization, foreign_key: {on_delete: :cascade}, 
                                  index: true, null: false

      t.timestamps null: false
    end
  end
end

Adding or removing a column from a table

Adding a column

class AddPartNumberToProducts < ActiveRecord::Migration[7.0]
  def change
    add_column :products, :part_number, :string
  end
end

Removing a column

class RemovePartNumberFromProducts < ActiveRecord::Migration[7.0]
  def change
    remove_column :products, :part_number, :string
  end
end
rails, migrations, cheatsheet