Rails migration best practices
Never Modify the up
Method on a Committed Migration
Modifying up
methods in committed migrations introduces potential chaos in your deployment and team synchronization processes.
# db/migrate/20230101120000_example_migration.rb
class ExampleMigration < ActiveRecord::Migration[6.0]
def up
add_column :users, :email, :string, null: false
end
def down
remove_column :users, :email
end
end
Never change the up
method once committed to avoid forcing your team into error-prone database version synchronizations.
Never Use External Code in a Migration
Have you already been onboarded onto a team and the migration didn’t pass due to some inexplicable error in the model?
Invoking model classes or other Ruby code inside migrations can result in migrations that fail in the future, as external dependencies might change or be removed.
In simple words:
- Day 1: You add the line
Post.create(title: "Migration Best Practices")
in your migrations. - Day 2: Another dev adds the line
validates :content, presence: true
in the Post model. - Day 3: Your new dev cannot run the migration done on day 1 because of a validation error.
What do you do? Do you change a committed migration?
The solution: Execute raw SQL. No ORM in the migrations.
# db/migrate/20230101121000_add_jobs_count_to_user.rb
class AddJobsCountToUser < ActiveRecord::Migration[6.0]
def up
add_column :users, :jobs_count, :integer, default: 0
# Poor approach: Depending on the User model which might change
# Good approach: Use pure SQL for reliability
execute <<-SQL
UPDATE users
SET jobs_count = (SELECT count(*) FROM jobs WHERE jobs.user_id = users.id)
SQL
end
def down
remove_column :users, :jobs_count
end
end
Use SQL statements in migrations to avoid dependency on the application’s evolving business logic.
Always Provide a down
Method in Migrations
Every migration should come with a way to undo its changes, ensuring that your schema management is as flexible as it is reliable.
If a migration can’t be reversed, explicitly raise an exception.
# db/migrate/20230101121500_create_users.rb
class CreateUsers < ActiveRecord::Migration[6.0]
def up
create_table :users do |t|
t.string :name
t.timestamps
end
end
# Here it's obvious and does not pose a problem. But in some cases, it's more cryptic...
# In this case, I would not add the down method, but I wondered what the `down` method was.
def down
drop_table :users
end
end
NOTE:
Sometimes you cannot revert a migration if the time to invest in that method is not available.
It’s okay, but at least throw an error and communicate to your team before merging your code.
Remember, once it’s committed, it’s hard and risky to edit committed migrations.
Related posts
Building a Rails Engine