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.
In Rails 4.x command
rails g model User name:string
will generate migration as shown below.
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name
t.timestamps null: false
end
end
end
In Rails 5 the same command will generate following migration.
class CreateUsers < ActiveRecord::Migration[5.0]
def change
create_table :users do |t|
t.string :name
t.timestamps
end
end
end
Let's see the generated schema after running migration generated in Rails 5.
sqlite> .schema users
CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);
sqlite>
Rails 5 added the NOT NULL
constraints on the timestamps columns even though
not null constraint was not specified in the migration.
Let's look at another example.
In Rails 4.x command
rails g model Task user:references
would generate following migration.
class CreateTasks < ActiveRecord::Migration
def change
create_table :tasks do |t|
t.references :user, index: true, foreign_key: true
t.timestamps null: false
end
end
end
In Rails 5.0, same command will generate following migration.
class CreateTasks < ActiveRecord::Migration[5.0]
def change
create_table :tasks do |t|
t.references :user, foreign_key: true
t.timestamps
end
end
end
There is no mention of index: true
in the above migration. Let's see the
generated schema after running Rails 5 migration.
sqlite> .schema tasks
CREATE TABLE "tasks" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "user_id" integer, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);
CREATE 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.
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.
Let's look at the migration generated in Rails 5 closely.
class CreateTasks < ActiveRecord::Migration[5.0]
def change
create_table :tasks do |t|
t.references :user, index: true, foreign_key: true
t.timestamps null: false
end
end
end
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.
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]
.
If this blog was helpful, check out our full blog archive.