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
endBut 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 existsRemove 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 productsIn 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.
