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.
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
:ordersis the table name.:paidis the existing column name.falseis 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:
| id | title | status |
|---|---|---|
| 1 | Write tests | NULL |
| 2 | Deploy app | done |
When you run rails db:migrate:
- Rails finds the
tasks.statuscolumn. - Rails alters that column's schema default from
NULLto"pending". - Existing rows remain unchanged. Row 1 is still
NULL; row 2 is still"done". - A later insert that does not provide
statusreceives"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
visibilitycolumn 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.
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
| Approach | Changes database schema? | Affects existing rows? | Best use |
|---|---|---|---|
change_column_default | Yes | No | Set a fallback for future inserts |
update_all | No | Yes, matching rows | Backfill existing data |
| Model attribute default | No | Only records created through Rails model code | Convenience in application code |
change_column_null | Yes | No value replacement | Require future rows to have a value |
A common sequence is:
- Set a database default for future rows.
- Backfill old rows if needed.
- Add
null: falseonly after no rows containNULL.
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:andto: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.
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
- Set the default value of the existing
tasks.statuscolumn to"pending". - Update existing tasks whose status is
NULLto use"pending". - Make the
statuscolumn rejectNULLvalues. - Include a rollback path for the migration.
Keep learning
Related questions
Calling a Class Method from an Instance in Ruby
Learn how to call a class method from an instance in Ruby using self.class, with examples, pitfalls, and practical usage patterns.
Calling an Overridden Monkey-Patched Method in Ruby
Learn how to call the original method when monkey patching in Ruby, including alias_method patterns, examples, pitfalls, and practical usage.
Check Whether a Value Is an Array in Ruby and Count Items
Learn how to test whether a value is an Array in Ruby and count its elements with is_a?, length, size, and count.