Rails 6.1 added support for if_exists/if_not_exists on remove/add column and extended it further to support if_not_exists on add_index and if_exists on remove_index.
To maintain the same behavior across add/remove constraints of databases, Rails 7 added support for if_exists/if_not_exists on remove_foreign_key/add_foreign_key.
Before Rails 7
Add foreign key
Let’s say we have an e-commerce application with Order
and User
models.
An order belongs to a user,
and we want to add a user_id
foreign key constraint on the orders
table.
We would add a migration as shown below:
class AddUserReferenceToOrder < ActiveRecord::Migration[6.0]
def change
add_foreign_key :orders, :users
end
end
But if the orders
table already contains foreign_key constraint on the user,
the above DB migration will raise an error.
rails db:migrate
== 20210714080612 AddUserReferenceToOrder: migrating ================
-- add_foreign_key(:orders, :users)
rake aborted!
StandardError: An error has occurred, this and all later migrations canceled:
PG::DuplicateObject: ERROR: constraint "fk_rails_11f1189a77" for relation "orders" already exists
Remove foreign key
Similarly,
if we try to remove a foreign_key constraint that never existed on
orders
table the migration would also raise an error.
class RemoveProductReferenceFromOrder < ActiveRecord::Migration[6.0]
def change
remove_foreign_key :orders, :products
end
end
rails db:migrate
== 20210714080712 RemoveProductReferenceFromOrder: migrating ================
-- remove_foreign_key(:orders, :products)
rake aborted!
StandardError: An error has occurred, this and all later migrations canceled:
Table 'orders' has no foreign key for products
In Rails 7
To avoid above issues and to keep things consistent,
Rails team added support to pass if_exists/if_not_exists
options
to remove_foreign_key/add_foreign_key
.
With this change, the below migrations run successfully without raising any error.
Add foreign key
class AddUserReferenceToOrder < ActiveRecord::Migration[6.0]
def change
add_foreign_key :orders, :users, if_not_exists: true
end
end
rails db:migrate
== 20210714080612 AddUserReferenceToOrder: migrating ================
-- add_foreign_key(:orders, :users, {:if_not_exists=>true})
-> 0.0151s
== 20210714080612 AddUserReferenceToOrder: migrated (0.0153s) ==================
Remove foreign key
class RemoveProductReferenceFromOrder < ActiveRecord::Migration[6.0]
def change
remove_foreign_key :orders, :products, if_exists: true
end
end
rails db:migrate
== 20210714080712 RemoveProductReferenceFromOrder: migrating ================
-- remove_foreign_key(:orders, :products, {if_exists: true})
-> 0.0118s
== 20210714080712 RemoveProductReferenceFromOrder: migrated (0.0118s) ================
Check out this pull request for more details.