February 22, 2019
•Last updated November 5, 2023
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 thestring/text/binary/integer
fields.precision
Defines the precision for thedecimal
fields, representing the total number of digits in the number.scale
Defines the scale for thedecimal
fields, representing the number of digits after the decimal point.polymorphic
Adds atype
column forbelongs_to
associations.null
Allows or disallowsNULL
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 User
model 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
- Understanding Ruby on Rails ActiveRecord Validations
- Debugging Ruby on Rails
- Understanding the Ruby on Rails CLI
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
☝ Want to learn Ruby on Rails from the ground up? Check out my upcoming course called Hello Rails.
Categories
Collection
Part of the Ruby on Rails collection
Products and courses
-
Hello Hotwire
A course on Hotwire + Ruby on Rails.
-
Hello Rails
A course for newcomers to Ruby on Rails.
-
Rails UI
UI templates and components for Rails.