Like source code, database schema changes and evolves over time. Migrations are a feature of Active Record that allows you to manage the database schema changes gracefully, without writing any SQL. This post contains my notes on Active Record migrations from the official Rails guides.
Key Points
- Think of each migration as a new version of the database.
- Migrations allow you to modify schema in a database-independent way.
db/schema.rb
file contains the current structure of your database.
Here’s an example of a migration that adds a time_logs
table with four explicit columns named project
, task
, start_time
, and end_time
. As you can see, it’s a Ruby class with a change method. Along with the four columns, the migration will also add an id column as a primary key. The timestamps macro adds two columns created_at
and updated_at
.
class CreateTimeLogs < ActiveRecord::Migration[6.1]
def change
create_table :time_logs do |t|
t.string :project
t.string :task
t.datetime :start_time
t.datetime :end_time
t.timestamps
end
end
end
To generate a migration, run the following command, which generates an empty migration.
bin/rails generate migration AddPartNumberToProducts
It generates the following code:
class AddPartNumberToProducts < ActiveRecord::Migration[6.0]
def change
end
end
If you want to add/remove columns or index to a table, use the add_column
, remove_column
, and add_index
methods.
class AddPartNumberToProducts < ActiveRecord::Migration[6.0]
def change
add_column :products, :part_number, :string
add_index :products, :part_number
end
end
Rails even allows you to mention the changes in the command.
bin/rails generate migration CreateProducts name:string part_number: string
# or
bin/rails generate migration AddDetailsToProducts part_number:string price:decimal
Naming Conventions
If the name of the migration starts with Create__
followed by column names along with the types, then the resulting migration will create a table.
# bin/rails generate migration CreateProducts name:string part_number:string
class CreateProducts < ActiveRecord::Migration[6.0]
def change
create_table :products do |t|
t.string :name
t.string :part_number
t.timestamps
end
end
end
If the name starts with Add
, it will add columns to an existing table.
# bin/rails generate migration AddDetailsToProducts part_number:string price:decimal
class AddDetailsToProducts < ActiveRecord::Migration[6.0]
def change
add_column :products, :part_number, :string
add_column :products, :price, :decimal
end
end
If the name contains JoinTable
, the generator will produce a join table.
# bin/rails generate migration CreateJoinTableCustomerProduct customer product
class CreateJoinTableCustomerProduct < ActiveRecord::Migration[6.0]
def change
create_join_table :customers, :products do |t|
# t.index [:customer_id, :product_id]
# t.index [:product_id, :customer_id]
end
end
end
Model Generation
If you would like to generate a model class along with the migration, use the model generator.
➜ rails generate model Post title:string content:text
Running via Spring preloader in process 71862
invoke active_record
create db/migrate/20210722065837_create_posts.rb
create app/models/post.rb
invoke test_unit
create test/models/post_test.rb
create test/fixtures/posts.yml
This generates the following code.
# xxxx_create_posts.rb
class CreatePosts < ActiveRecord::Migration[6.1]
def change
create_table :posts do |t|
t.string :title
t.text :content
t.timestamps
end
end
end
# post.rb
class Post < ApplicationRecord
end
Rails Migration Commands
So far, we have seen how to generate migrations in Rails. However, creating a migration on its own doesn’t update the database. You have to run the migration to make the changes. This post summarizes all the commands that modify the database.
rails db:migrate
You will be using this command often to run the migration. This command runs the change
method for all the migrations that have not run yet, in the order based on migration’s date. It also updates the db/schema.rb
file to match the database structure.
rails db:rollback
Reverts the last migration. If you made a mistake and want to go back to the state before running the migration, use this command. Provide the STEP=n
option if you want to revert last n
migrations. You can run db:migrate
after making corrections to the migration.
However, the Rails guides recommend that it’s not a good idea to edit an existing migration, especially if it has already been run on a production database. What you should do is create a new migration that performs the changes you need.
rails db:setup
This command creates the database, loads the schema, and initializes it with the seed data.
rails db:reset
Drop the database and set it up again. Equivalent to running db:drop
and db:setup
in sequence.
rails db:seed
Migrations can also add, modify, or delete data in the database. However, to add seed data after a database is created, you can use the ‘seeds’ feature in the database. Simply add some sample data in db/seeds.rb
, and run the db:seed
command.
rails db:prepare
This command creates the database if it doesn't exist, then runs the migrations to update the database schema. Finally, it runs the db:seed
command to seed the database.
Schema Files
Rails stores the current structure of the database schema in the db/schema.rb
file. Schema files are also handy to check the attributes of a model. This information is not in the model’s code and is frequently spread across several migrations, but it’s outlined in the schema file.
Schema files are commonly used to create new databases, and it’s recommended to check them into source control.
By default, the schema file uses the :ruby
format, but you can set it to :sql
. This will save the schema in db/structure.sql
file, using a database-specific tool, e.g. pg_dump
for PostgreSQL and SHOW CREATE TABLE
for MySQL.
rails db:schema:load
To create a new instance of your database, you can simply run the rails db:schema:load
command. It’s better than running the entire migration history, as it may fail to apply correctly.
The :ruby
format cannot express everything in the database schema, such as triggers, stored procedures, etc. Setting the format to :sql
will ensure that an accurate schema is generated and a perfect copy of the database schema is created upon running db:schema:load
.
The db/schema.rb
or db/structure.sql
is a snapshot of the current state of your database and is the authoritative source for rebuilding that database. This allows you to delete old migration files.