September 24, 2019
This blog is part of our Rails 6 series.
Rails provides add_foreign_key to add foreign key constraint for a column on a table.
It also provides remove_foreign_key to remove the foreign key constraint.
Before Rails 6, add_foreign_key and remove_foreign_key were not supported for SQLite3.
Rails 6 now adds this support. Now, we can create and remove foreign key constraints using add_foreign_key and remove_foreign_key in SQLite3.
Let's checkout how it works.
We have two tables named as orders
and users
. Now, let's add foreign key
constraint of users
in orders
table using
add_foreign_key
and then try removing it using
remove_foreign_key.
>> class AddUserReferenceToOrders < ActiveRecord::Migration[6.0]
>> def change
>> add_column :orders, :user_id, :integer
>> add_foreign_key :orders, :users
>> end
>> end
=> :change
>> AddUserReferenceToOrders.new.change
-- add_column(:orders, :user_id, :integer)
(1.2ms) ALTER TABLE "orders" ADD "user_id" integer
-> 0.0058s
-- add_foreign_key(:orders, :users)
-> 0.0000s
=> nil
>> class RemoveUserForeignKeyFromOrders < ActiveRecord::Migration[6.0]
>> def change
>> remove_foreign_key :orders, :users
>> end
>> end
=> :change
>> RemoveUserForeignKeyFromOrders.new.change
-- remove_foreign_key(:orders, :users)
-> 0.0001s
=> nil
We can see that
add_foreign_key
and
remove_foreign_key
are ignored by Rails 5.2
with SQLite3.
We have two tables named as orders
and users
. Now, let's add foreign key
constraint of users
in orders
table using
add_foreign_key.
>> class AddUserReferenceToOrders < ActiveRecord::Migration[6.0]
>> def change
>> add_column :orders, :user_id, :integer
>> add_foreign_key :orders, :users
>> end
>> end
=> :change
>> AddUserReferenceToOrders.new.change
-- add_column(:orders, :user_id, :integer)
(1.0ms) SELECT sqlite_version(*)
(2.9ms) ALTER TABLE "orders" ADD "user_id" integer
-> 0.0091s
-- add_foreign_key(:orders, :users)
(0.0ms) begin transaction
(0.1ms) PRAGMA foreign_keys
(0.1ms) PRAGMA defer_foreign_keys
(0.0ms) PRAGMA defer_foreign_keys = ON
(0.1ms) PRAGMA foreign_keys = OFF
(0.2ms) CREATE TEMPORARY TABLE "aorders" ("id" integer NOT NULL PRIMARY KEY, "number" varchar DEFAULT NULL, "total" decimal DEFAULT NULL, "completed_at" datetime DEFAULT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "user_id" integer DEFAULT NULL)
(0.1ms) INSERT INTO "aorders" ("id","number","total","completed_at","created_at","updated_at","user_id")
SELECT "id","number","total","completed_at","created_at","updated_at","user_id" FROM "orders"
(0.3ms) DROP TABLE "orders"
(0.1ms) CREATE TABLE "orders" ("id" integer NOT NULL PRIMARY KEY, "number" varchar DEFAULT NULL, "total" decimal DEFAULT NULL, "completed_at" datetime DEFAULT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "user_id" integer DEFAULT NULL, CONSTRAINT "fk_rails_f868b47f6a"
FOREIGN KEY ("user_id")
REFERENCES "users" ("id")
)
(0.1ms) INSERT INTO "orders" ("id","number","total","completed_at","created_at","updated_at","user_id")
SELECT "id","number","total","completed_at","created_at","updated_at","user_id" FROM "aorders"
(0.1ms) DROP TABLE "aorders"
(0.0ms) PRAGMA defer_foreign_keys = 0
(0.0ms) PRAGMA foreign_keys = 1
(0.6ms) commit transaction
-> 0.0083s
=> []
>> class RemoveUserForeignKeyFromOrders < ActiveRecord::Migration[6.0]
>> def change
>> remove_foreign_key :orders, :users
>> end
>> end
=> :change
>> RemoveUserForeignKeyFromOrders.new.change
-- remove_foreign_key(:orders, :users)
(1.4ms) SELECT sqlite_version(*)
(0.0ms) begin transaction
(0.0ms) PRAGMA foreign_keys
(0.0ms) PRAGMA defer_foreign_keys
(0.0ms) PRAGMA defer_foreign_keys = ON
(0.0ms) PRAGMA foreign_keys = OFF
(0.2ms) CREATE TEMPORARY TABLE "aorders" ("id" integer NOT NULL PRIMARY KEY, "number" varchar DEFAULT NULL, "total" decimal DEFAULT NULL, "completed_at" datetime DEFAULT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "user_id" integer DEFAULT NULL)
(0.3ms) INSERT INTO "aorders" ("id","number","total","completed_at","created_at","updated_at","user_id")
SELECT "id","number","total","completed_at","created_at","updated_at","user_id" FROM "orders"
(0.4ms) DROP TABLE "orders"
(0.1ms) CREATE TABLE "orders" ("id" integer NOT NULL PRIMARY KEY, "number" varchar DEFAULT NULL, "total" decimal DEFAULT NULL, "completed_at" datetime DEFAULT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "user_id" integer DEFAULT NULL)
(0.1ms) INSERT INTO "orders" ("id","number","total","completed_at","created_at","updated_at","user_id")
SELECT "id","number","total","completed_at","created_at","updated_at","user_id" FROM "aorders"
(0.1ms) DROP TABLE "aorders"
(0.0ms) PRAGMA defer_foreign_keys = 0
(0.0ms) PRAGMA foreign_keys = 1
(0.7ms) commit transaction
-> 0.0179s
=> []
Now, let's remove foreign key constraint of users
from orders
table using
remove_foreign_key.
>> class RemoveUserForeignKeyFromOrders < ActiveRecord::Migration[6.0]
>> def change
>> remove_foreign_key :orders, :users
>> end
>> end
=> :change
>> RemoveUserForeignKeyFromOrders.new.change
-- remove_foreign_key(:orders, :users)
(1.4ms) SELECT sqlite_version(*)
(0.0ms) begin transaction
(0.0ms) PRAGMA foreign_keys
(0.0ms) PRAGMA defer_foreign_keys
(0.0ms) PRAGMA defer_foreign_keys = ON
(0.0ms) PRAGMA foreign_keys = OFF
(0.2ms) CREATE TEMPORARY TABLE "aorders" ("id" integer NOT NULL PRIMARY KEY, "number" varchar DEFAULT NULL, "total" decimal DEFAULT NULL, "completed_at" datetime DEFAULT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "user_id" integer DEFAULT NULL)
(0.3ms) INSERT INTO "aorders" ("id","number","total","completed_at","created_at","updated_at","user_id")
SELECT "id","number","total","completed_at","created_at","updated_at","user_id" FROM "orders"
(0.4ms) DROP TABLE "orders"
(0.1ms) CREATE TABLE "orders" ("id" integer NOT NULL PRIMARY KEY, "number" varchar DEFAULT NULL, "total" decimal DEFAULT NULL, "completed_at" datetime DEFAULT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "user_id" integer DEFAULT NULL)
(0.1ms) INSERT INTO "orders" ("id","number","total","completed_at","created_at","updated_at","user_id")
SELECT "id","number","total","completed_at","created_at","updated_at","user_id" FROM "aorders"
(0.1ms) DROP TABLE "aorders"
(0.0ms) PRAGMA defer_foreign_keys = 0
(0.0ms) PRAGMA foreign_keys = 1
(0.7ms) commit transaction
-> 0.0179s
=> []
We can see here that with Rails 6, add_foreign_key and remove_foreign_key work and were able to add and remove foreign key constraint respectively.
Here is the relevant pull request.
If this blog was helpful, check out our full blog archive.