Question

Add a Default Value to an Existing Column in a Rails Migration

rubyruby-on-railsmigration

Question

How can I add a default value to a database column that already exists by using a Rails migration? The column was created previously, and I need to change its schema definition without creating it again.

Short Answer

You will learn how Rails migrations modify the default value of an existing column with change_column_default. You will also understand the important difference between setting a database default for future rows and updating values in rows that already exist.

Concept

A database default value is the value the database uses when a new row is inserted without an explicit value for a particular column.

In Rails, use a migration to change an existing column's default rather than recreating the column. The usual method is:

change_column_default :table_name, :column_name, new_default

For example, if existing orders have a boolean paid column and new orders should be unpaid unless stated otherwise:

change_column_default :orders, :paid, false

This changes the database schema. It does not change old records that already have NULL, true, or another stored value.

Defaults are useful because they enforce a sensible fallback at the database level. This protects data created outside the normal Rails application too, such as imports, background jobs, SQL scripts, or another service connected to the same database.

Mental Model

Think of a column default as the default setting on a form.

When a person starts a new form, a checkbox might already be unchecked. Changing that default affects forms created from now on, but it does not travel back in time and change forms that were already submitted.

Likewise, changing a database default affects newly inserted rows that omit the column. Existing rows keep their current stored values until you explicitly update them.

Take Quiz

Syntax and Examples

Use change_column_default in a Rails migration.

class AddDefaultToOrdersPaid < ActiveRecord::Migration[7.1]
  def change
    change_column_default :orders, :paid, false
  end
end
  • :orders is the table name.
  • :paid is the existing column name.
  • false is the new default.

For a reversible migration, specify both the previous and new default values:

class AddDefaultToOrdersPaid < ActiveRecord::Migration[7.1]
  def change
    change_column_default :orders, :paid, from: nil, to: false
  end
end

Rails can then reverse the migration correctly: migrating up changes NULL to false, and rolling back changes false back to NULL.

String and timestamp-style defaults

change_column_default :users, :role, "member"
change_column_default :tasks, :status, "pending"

Use values that match the column type. For example, use false for a boolean column, not the string "false".

Step by Step Execution

Consider this migration:

class SetDefaultStatusOnTasks < ActiveRecord::Migration[7.1]
  def change
    change_column_default :tasks, :status, from: nil, to: "pending"
  end
end

Suppose the table initially contains:

idtitlestatus
1Write testsNULL
2Deploy appdone

When you run rails db:migrate:

  1. Rails finds the tasks.status column.
  2. Rails alters that column's schema default from NULL to "pending".
  3. Existing rows remain unchanged. Row 1 is still NULL; row 2 is still "done".
  4. A later insert that does not provide status receives "pending" from the database.
Task.create!(title: "Review pull request")
# The new task has status: "pending"

If the application explicitly sends a value, that value takes precedence:

Task.create!(title: "Fix production bug", status: "urgent")
# status is "urgent", not "pending"

Real World Use Cases

Common uses for defaults on existing columns include:

  • Order workflows: New orders begin with status: "pending".
  • Feature settings: New user preferences default to notifications_enabled: true.
  • Task management: Newly created tasks default to priority: "normal".
  • Soft deletion: A new record defaults to archived: false.
  • Multi-tenant data: A newly added visibility column defaults to "private" or "team".

Adding the default in a migration is especially useful when a column was introduced without one and application rules later require a consistent value for new data.

Take Quiz

Real Codebase Usage

In a production Rails codebase, a default change is often part of a safe multi-step data migration.

Add a default for future records

class DefaultArchivedOnProjects < ActiveRecord::Migration[7.1]
  def change
    change_column_default :projects, :archived, from: nil, to: false
  end
end

Backfill old NULL records separately

If old rows should also have the value, update them explicitly:

class BackfillArchivedOnProjects < ActiveRecord::Migration[7.1]
  def up
    Project.where(archived: nil).update_all(archived: false)
  end

  def down
    Project.where(archived: false).update_all(archived: nil)
  end
end

For very large tables, teams commonly backfill in batches through a background job or deployment task instead of one large migration. This reduces long-running locks and avoids placing too much load on the database.

Application code should still validate important business rules. A database default helps when a value is omitted, while validations communicate rules clearly within the Rails application.

Common Mistakes

Expecting a default to update existing rows

This migration changes only the schema:

change_column_default :projects, :archived, false

It does not turn existing NULL values into false.

Avoid it: Run a separate, intentional backfill if existing data needs changing.

Project.where(archived: nil).update_all(archived: false)

Using a string for a boolean default

# Avoid
change_column_default :projects, :archived, "false"

"false" is text, not the Ruby boolean false.

# Correct
change_column_default :projects, :archived, false

Omitting the old value in a migration you plan to roll back

change_column_default :tasks, :status, "pending"

This may be fine, but explicitly giving from: and to: documents the intended schema transition and makes reversal reliable.

change_column_default :tasks, :status, from: nil, to: "pending"

Confusing model defaults with database defaults

A Rails model can assign a default in Ruby, but data written through raw SQL or another application can bypass it. Use a migration when the database itself should provide the fallback.

Adding null: false before fixing old null values

If old rows contain NULL, this can fail:

change_column_null :projects, :archived, false

First backfill missing values, then add the NOT NULL constraint.

Comparisons

ApproachChanges database schema?Affects existing rows?Best use
change_column_defaultYesNoSet a fallback for future inserts
update_allNoYes, matching rowsBackfill existing data
Model attribute defaultNoOnly records created through Rails model codeConvenience in application code
change_column_nullYesNo value replacementRequire future rows to have a value

A common sequence is:

  1. Set a database default for future rows.
  2. Backfill old rows if needed.
  3. Add null: false only after no rows contain NULL.
Take Quiz

Cheat Sheet

# Set a default on an existing column
change_column_default :orders, :paid, false

# Preferred reversible form
change_column_default :orders, :paid, from: nil, to: false

# Default text value
change_column_default :tasks, :status, from: nil, to: "pending"

# Backfill existing NULL values
Task.where(status: nil).update_all(status: "pending")

# Require a value after backfilling
change_column_null :tasks, :status, false

Rules to remember:

  • A default applies when a new insert omits the column.
  • An explicit inserted value overrides the default.
  • Existing rows do not change automatically.
  • Match the Ruby value to the column type: false, 0, "text", and so on.
  • Use from: and to: when you want a clear reversible migration.

FAQ

How do I add a default value to an existing Rails column?

Create a migration and call change_column_default, for example:

change_column_default :users, :active, from: nil, to: true

Does change_column_default update existing records?

No. It changes the schema default used by future inserts. Backfill existing rows separately if necessary.

Can I roll back a default-value migration?

Yes. The clearest approach is to use from: and to: in a change method, so Rails knows both directions.

Should I use false or "false" for a boolean column?

Use false. The quoted version is a string and is not the same type.

Can I add null: false at the same time?

Only if every existing row already has a non-null value. Usually, set the default, backfill null rows, then add the constraint.

Why use a database default instead of a Rails model default?

A database default also works for writes that do not go through the Rails model, such as imports, SQL scripts, and other services.

What command creates the migration?

For example:

bin/rails generate migration AddDefaultToOrdersPaid

Then edit the generated migration and run bin/rails db:migrate.

Take Quiz

Mini Project

Description

A project-management app has a tasks table with an existing nullable status column. New tasks should start as "pending", and old tasks with no status should be repaired before the column is made required.

Goal

Create a safe migration that gives new tasks a default status, fills missing statuses on old tasks, and prevents future null statuses.

Requirements

  1. Set the default value of the existing tasks.status column to "pending".
  2. Update existing tasks whose status is NULL to use "pending".
  3. Make the status column reject NULL values.
  4. Include a rollback path for the migration.
Take Quiz

Keep learning

Related questions

View more