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:stringdeclaration 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.
limitSets the maximum size of thestring/text/binary/integerfields.precisionDefines the precision for thedecimalfields, representing the total number of digits in the number.scaleDefines the scale for thedecimalfields, representing the number of digits after the decimal point.polymorphicAdds atypecolumn forbelongs_toassociations.nullAllows or disallowsNULLvalues in the column.defaultAllows 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).indexAdds 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
- 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.
