May 22, 2019
This blog is part of our Rails 6 series.
Rails 6 added if_not_exists to create_table option to create a table if it doesn't exist.
Before Rails 6, we could use ActiveRecord::Base.connection.table_exists?.
Default value of if_not_exists
option is false
.
Let's create users
table in Rails 5.2.
>> class CreateUsers < ActiveRecord::Migration[6.0]
>> def change
>> create_table :users do |t|
>> t.string :name, index: { unique: true }
>>
>> t.timestamps
>> end
>> end
>> end
>> CreateUsers.new.change
-- create_table(:users)
CREATE TABLE "users" ("id" bigserial primary key, "name" character varying, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL)
=> #<PG::Result:0x00007fd73e711cf0 status=PGRES_COMMAND_OK ntuples=0 nfields=0 cmd_tuples=0>
Now let's try creating users
table again with
if_not_exists option.
>> class CreateUsers < ActiveRecord::Migration[6.0]
>> def change
>> create_table :users, if_not_exists: true do |t|
>> t.string :name, index: { unique: true }
>>
>> t.timestamps
>> end
>> end
>> end
>> CreateUsers.new.change
-- create_table(:users, {:if_not_exists=>true})
CREATE TABLE "users" ("id" bigserial primary key, "name" character varying, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL)
=> Traceback (most recent call last):
2: from (irb):121
1: from (irb):114:in 'change'
ActiveRecord::StatementInvalid (PG::DuplicateTable: ERROR: relation "users" already exists)
: CREATE TABLE "users" ("id" bigserial primary key, "name" character varying, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL)
We can see that Rails 5.2 ignored if_not_exists option and tried creating the table again.
Now let's try ActiveRecord::Base.connection.table_exists? with Rails 5.2.
>> class CreateUsers < ActiveRecord::Migration[5.2]
>> def change
>> unless ActiveRecord::Base.connection.table_exists?('users')
>> create_table :users do |t|
>> t.string :name
>>
>> t.timestamps
>> end
>> end
>> end
>> end
>> CreateUsers.new.change
=> nil
We can see that create_table :users
never executed because
ActiveRecord::Base.connection.table_exists?('users')
returned true.
Let's create users
table in Rails 6 with
if_not_exists option set as true.
>> class CreateUsers < ActiveRecord::Migration[6.0]
>> def change
>> create_table :users, if_not_exists: true do |t|
>> t.string :name, index: { unique: true }
>>
>> t.timestamps
>> end
>> end
>> end
>> CreateUsers.new.change
-- create_table(:users, {:if_not_exists=>true})
CREATE TABLE IF NOT EXISTS "users" ("id" bigserial primary key, "name" character varying, "created_at" timestamp(6) NOT NULL, "updated_at" timestamp(6) NOT NULL)
=> #<PG::Result:0x00007fc4614fef48 status=PGRES_COMMAND_OK ntuples=0 nfields=0 cmd_tuples=0>
>> CreateUsers.new.change
-- create_table(:users, {:if_not_exists=>true})
CREATE TABLE IF NOT EXISTS "users" ("id" bigserial primary key, "name" character varying, "created_at" timestamp(6) NOT NULL, "updated_at" timestamp(6) NOT NULL)
=> #<PG::Result:0x00007fc46513fde0 status=PGRES_COMMAND_OK ntuples=0 nfields=0 cmd_tuples=0>
We can see that no exception was raised when we tried creating users
table the
second time.
Now let's see what happens if we set if_not_exists to false.
>> class CreateUsers < ActiveRecord::Migration[6.0]
>> def change
>> create_table :users, if_not_exists: false do |t|
>> t.string :name, index: { unique: true }
>>
>> t.timestamps
>> end
>> end
>> end
>> CreateUsers.new.change
-- create_table(:users, {:if_not_exists=>false})
CREATE TABLE "users" ("id" bigserial primary key, "name" character varying, "created_at" timestamp(6) NOT NULL, "updated_at" timestamp(6) NOT NULL)
=> Traceback (most recent call last):
2: from (irb):23
1: from (irb):15:in `change'
ActiveRecord::StatementInvalid (PG::DuplicateTable: ERROR: relation "users" already exists
)
As we can see, Rails raised an exception here because if_not_exists was set to false.
Here is the relevant pull request.
If this blog was helpful, check out our full blog archive.