Migrations are versioned in Rails 5

Abhishek Jain

By Abhishek Jain

on March 1, 2016

This blog is part of our  Rails 5 series.

We will see how migrations in Rails 5 differ by looking at different cases.

Case I

In Rails 4.x command

1rails g model User name:string

will generate migration as shown below.

1class CreateUsers < ActiveRecord::Migration
2  def change
3    create_table :users do |t|
4      t.string :name
5      t.timestamps null: false
6    end
7  end
8end

In Rails 5 the same command will generate following migration.

1class CreateUsers < ActiveRecord::Migration[5.0]
2  def change
3    create_table :users do |t|
4      t.string :name
5      t.timestamps
6    end
7  end
8end

Let's see the generated schema after running migration generated in Rails 5.

1sqlite> .schema users
2CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);
3sqlite>

Rails 5 added the NOT NULL constraints on the timestamps columns even though not null constraint was not specified in the migration.

Case II

Let's look at another example.

In Rails 4.x command

1rails g model Task user:references

would generate following migration.

1class CreateTasks < ActiveRecord::Migration
2  def change
3    create_table :tasks do |t|
4      t.references :user, index: true, foreign_key: true
5      t.timestamps null: false
6    end
7  end
8end

In Rails 5.0, same command will generate following migration.

1class CreateTasks < ActiveRecord::Migration[5.0]
2  def change
3    create_table :tasks do |t|
4      t.references :user, foreign_key: true
5
6      t.timestamps
7    end
8  end
9end

There is no mention of index: true in the above migration. Let's see the generated schema after running Rails 5 migration.

1sqlite> .schema tasks
2CREATE TABLE "tasks" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "user_id" integer, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);
3
4CREATE INDEX "index_tasks_on_user_id" ON "tasks" ("user_id");

As you can see, an index on user_id column is added even though it's not present in the migration.

Migration API has changed in Rails 5

Rails 5 has changed migration API because of which even though null: false options is not passed to timestamps when migrations are run then not null is automatically added for timestamps.

Similarly, we want indexes for referenced columns in almost all cases. So Rails 5 does not need references to have index: true. When migrations are run then index is automatically created.

Now let's assume that an app was created in Rails 4.x. It has a bunch of migrations. Later the app was upgraded to Rails 5. Now when older migrations are run then those migrations will behave differently and will create a different schema file. This is a problem.

Solution is versioned migrations.

Versioned migrations in Rails 5

Let's look at the migration generated in Rails 5 closely.

1class CreateTasks < ActiveRecord::Migration[5.0]
2  def change
3    create_table :tasks do |t|
4      t.references :user, index: true, foreign_key: true
5      t.timestamps null: false
6    end
7  end
8end

In this case CreateUsers class is now inheriting from ActiveRecord::Migration[5.0] instead of ActiveRecord::Migration.

Here [5.0] is Rails version that generated this migration.

Solving the issue with older migrations

Whenever Rails 5 runs migrations, it checks the class of the current migration file being run. If it's 5.0, it uses the new migration API which has changes like automatically adding null: false to timestamps.

But whenever the class of migration file is other than ActiveRecord::Migration[5.0], Rails will use a compatibility layer of migrations API. Currently this compatibility layer is present for Rails 4.2. What it means is that all migration generated prior to usage of Rails 5 will be treated as if they were generate in Rails 4.2.

You will also see a deprecation warning asking user to add the version of the migration to the class name for older migrations.

So if you are migrating a Rails 4.2 app, all of your migrations will have class ActiveRecord::Migration. If you run those migrations in Rails 5, you will see a warning asking to add version name to the class name so that class name looks like ActiveRecord::Migration[4.2].

Stay up to date with our blogs. Sign up for our newsletter.

We write about Ruby on Rails, ReactJS, React Native, remote work,open source, engineering & design.