0
0
Ruby on Railsframework~15 mins

Creating migrations in Ruby on Rails - Mechanics & Internals

Choose your learning style9 modes available
Overview - Creating migrations
What is it?
Creating migrations in Rails means writing special files that tell the database how to change its structure. These files describe adding or removing tables, columns, or indexes in a way that Rails can understand and apply step-by-step. Migrations help keep the database organized and in sync with the code. They are like instructions for the database to evolve safely over time.
Why it matters
Without migrations, changing the database structure would be risky and confusing, especially when working with a team. Migrations solve this by tracking every change in a clear, repeatable way. This prevents mistakes like losing data or having different database versions on different computers. It makes updating and sharing the database structure easy and reliable.
Where it fits
Before learning migrations, you should understand basic Ruby and Rails models, and how databases store data. After migrations, you can learn about advanced database features like indexing, constraints, and schema design. Migrations fit into the bigger picture of managing data and application growth.
Mental Model
Core Idea
Migrations are step-by-step instructions that safely change the database structure over time.
Think of it like...
Imagine building a LEGO model where each migration is a new instruction sheet telling you which pieces to add or remove, so the model grows exactly as planned without breaking.
┌───────────────┐
│ Migration 001 │
│ Create users  │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ Migration 002 │
│ Add email to  │
│ users table   │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ Migration 003 │
│ Create posts  │
└───────────────┘

Each migration builds on the last, changing the database step-by-step.
Build-Up - 7 Steps
1
FoundationWhat is a migration file
🤔
Concept: A migration file is a Ruby script that describes a change to the database structure.
In Rails, migrations live in the db/migrate folder. Each migration has a timestamp and a descriptive name, like 20240610123456_create_users.rb. Inside, it has methods called up and down or a change method that tells Rails how to apply or undo the change. For example, creating a users table with columns for name and email.
Result
You get a clear, versioned file that Rails can run to update the database schema.
Understanding that migrations are just Ruby files helps you see they are code you can read, write, and control like any other part of your app.
2
FoundationRunning migrations with Rails commands
🤔
Concept: Rails provides commands to apply or revert migrations safely on your database.
You run migrations with `rails db:migrate` to apply all pending changes. To undo the last migration, use `rails db:rollback`. These commands update a special table called schema_migrations that tracks which migrations have run. This keeps your database and code in sync.
Result
The database structure changes as described by the migration files, and Rails knows which changes are done.
Knowing how to run and rollback migrations is key to safely evolving your database without losing data or causing errors.
3
IntermediateWriting change method for reversible migrations
🤔Before reading on: do you think all migrations need separate up and down methods, or can one method handle both?
Concept: The change method lets Rails automatically reverse migrations when possible, simplifying code.
Instead of writing separate up and down methods, you can write a single change method. For example, `create_table :users do |t| t.string :name end` inside change lets Rails know how to create and drop the table automatically. This reduces errors and makes migrations easier to maintain.
Result
Migrations become shorter and Rails can rollback changes without extra code.
Understanding reversible migrations saves time and prevents mistakes when undoing database changes.
4
IntermediateAdding columns and indexes safely
🤔Before reading on: do you think adding a column with a default value affects existing rows immediately or only new rows?
Concept: Migrations can add columns and indexes with options that affect performance and data safety.
You can add columns with `add_column :table, :column, :type, default: value, null: false`. Adding indexes with `add_index :table, :column` speeds up queries. But adding a column with a default and not null can lock the table during migration, so sometimes you add the column nullable first, fill data, then change constraints.
Result
The database structure improves with new columns and faster queries, but you must be careful to avoid downtime.
Knowing how to add columns and indexes properly helps keep your app fast and available during migrations.
5
IntermediateUsing migration generators for consistency
🤔
Concept: Rails provides generators to create migration files with correct naming and structure automatically.
Instead of writing migration files from scratch, use commands like `rails generate migration AddAgeToUsers age:integer`. This creates a migration file with the right timestamp and a change method adding the age column. Generators reduce errors and keep your migrations organized.
Result
You get ready-to-edit migration files that follow Rails conventions.
Using generators speeds up development and ensures your migrations follow best practices.
6
AdvancedManaging complex migrations in production
🤔Before reading on: do you think running large migrations always happens instantly without affecting users?
Concept: Large or complex migrations require careful planning to avoid downtime or data loss in live apps.
In production, migrations that lock tables or take long can cause app slowdowns or errors. Techniques like breaking migrations into smaller steps, adding columns nullable first, backfilling data in batches, and then adding constraints help. Also, avoid dropping columns immediately; instead, deprecate them first.
Result
Database changes happen smoothly without interrupting users or causing errors.
Understanding production migration strategies prevents costly downtime and keeps your app reliable.
7
ExpertHow Rails tracks and applies migrations internally
🤔Before reading on: do you think Rails runs all migration files every time or only the new ones?
Concept: Rails uses a special table to track which migrations have run and applies only new ones in order.
Rails stores applied migration versions in the schema_migrations table. When you run `rails db:migrate`, Rails compares migration files in db/migrate with this table and runs only those not recorded yet. This ensures migrations run once and in the correct order. The schema.rb file is updated to reflect the current database structure.
Result
Migrations run safely and only once, keeping database and code aligned.
Knowing how Rails tracks migrations helps debug issues and understand migration failures.
Under the Hood
When you run a migration, Rails loads the migration files in timestamp order. It checks the schema_migrations table to see which versions have already run. For each new migration, Rails executes the change, up, or down methods inside a database transaction if supported. After successful execution, it records the migration version in schema_migrations. This process ensures migrations are atomic and ordered.
Why designed this way?
Rails migrations were designed to provide a simple, code-driven way to evolve databases safely. Tracking applied migrations prevents duplicate changes and conflicts. Using Ruby code for migrations leverages the language's expressiveness and integrates tightly with Rails models. Alternatives like manual SQL scripts were error-prone and hard to manage across teams.
┌─────────────────────────────┐
│ db/migrate folder            │
│ ┌───────────────┐           │
│ │ Migration 001 │           │
│ └───────────────┘           │
│ ┌───────────────┐           │
│ │ Migration 002 │           │
│ └───────────────┘           │
└─────────────┬───────────────┘
              │
              ▼
┌─────────────────────────────┐
│ schema_migrations table      │
│ ┌───────────────┐           │
│ │ Version 001   │◄──────────┤
│ └───────────────┘           │
│ ┌───────────────┐           │
│ │ Version 002   │◄──────────┤
│ └───────────────┘           │
└─────────────┬───────────────┘
              │
              ▼
┌─────────────────────────────┐
│ Database schema updated      │
│ Tables, columns, indexes     │
└─────────────────────────────┘
Myth Busters - 4 Common Misconceptions
Quick: do you think running the same migration file twice will cause errors or is it safe?
Common Belief:Many believe migrations can be run multiple times safely without issues.
Tap to reveal reality
Reality:Rails runs each migration only once by tracking versions; rerunning the same migration manually causes errors or duplicate changes.
Why it matters:Not understanding this can lead to broken databases or failed deployments when migrations are applied incorrectly.
Quick: do you think migrations automatically update your Rails models to match the database?
Common Belief:Some think migrations change the Rails model code automatically.
Tap to reveal reality
Reality:Migrations only change the database structure; Rails models must be updated manually to reflect schema changes.
Why it matters:Assuming automatic updates can cause bugs where the app expects columns or tables that don't exist in code.
Quick: do you think adding a column with a default value always happens instantly without locking the table?
Common Belief:Many believe adding columns with defaults is always fast and safe in production.
Tap to reveal reality
Reality:Adding columns with defaults and NOT NULL constraints can lock large tables and cause downtime unless done carefully in steps.
Why it matters:Ignoring this can cause app outages during migrations on busy production databases.
Quick: do you think the order of migration files does not matter as long as all are applied?
Common Belief:Some believe migration order is unimportant if all migrations run eventually.
Tap to reveal reality
Reality:Migration order is critical; running them out of order can cause missing tables or columns and break the app.
Why it matters:Misordering migrations leads to errors and inconsistent database states.
Expert Zone
1
Migrations can be written to be idempotent, allowing safe re-runs in complex deployment scenarios.
2
Using raw SQL inside migrations is sometimes necessary for performance or features but requires careful rollback handling.
3
Schema.rb is a snapshot of the current database schema, but structure.sql is preferred for complex database features like triggers or views.
When NOT to use
Migrations are not suitable for large data transformations or cleanup tasks; use background jobs or scripts instead. Also, avoid using migrations for frequent small changes in production without planning, as they can cause downtime. For non-Rails databases or legacy systems, other schema management tools might be better.
Production Patterns
In production, teams use zero-downtime migration patterns like adding nullable columns first, backfilling data asynchronously, and then adding constraints. They also review migrations carefully, test on staging, and use feature flags to deploy code that depends on new schema changes safely.
Connections
Version Control Systems
Migrations build on the idea of version control by tracking database changes over time.
Understanding how migrations track changes like Git tracks code helps grasp why migrations prevent conflicts and keep teams synchronized.
Database Transactions
Migrations often run inside database transactions to ensure atomic changes.
Knowing how transactions work explains why migrations either fully apply or fully rollback, keeping the database consistent.
Project Management Change Logs
Migrations act like a change log for the database schema, documenting every structural update.
Seeing migrations as a formal record of changes helps appreciate their role in team communication and auditing.
Common Pitfalls
#1Running migrations without checking for pending schema changes causes conflicts.
Wrong approach:rails db:migrate # runs without verifying if schema.rb is updated or conflicts exist
Correct approach:git pull rails db:migrate # ensures code and migrations are up to date before running
Root cause:Not syncing code and database changes leads to migration conflicts and errors.
#2Adding a NOT NULL column with a default in one step on a large table causes downtime.
Wrong approach:add_column :users, :age, :integer, null: false, default: 0
Correct approach:add_column :users, :age, :integer User.update_all(age: 0) change_column_null :users, :age, false
Root cause:Misunderstanding how database engines handle defaults and constraints causes table locks.
#3Deleting columns immediately after removing code references causes data loss.
Wrong approach:remove_column :users, :old_field
Correct approach:# First remove code references, deploy, then remove column in a later migration
Root cause:Not separating code and schema changes leads to app errors or lost data.
Key Takeaways
Migrations are Ruby files that describe step-by-step changes to the database structure.
Rails tracks which migrations have run to apply changes safely and in order.
Using the change method allows reversible migrations that Rails can rollback automatically.
Careful planning of migrations in production avoids downtime and data loss.
Migrations do not update Rails models automatically; both schema and code must stay in sync.