Rails migration best practices

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

Subscribe for Ruby On Rails tricks and news about upcoming workshops

We care about your data. No spams.

Back to homepage