Add support for `if_exists/if_not_exists` on `remove_foreign_key/add_foreign_key`
Applications can set their migrations to ignore exceptions raised when adding a foreign key that already exists or when removing a foreign key that does not exist.
Add test cases
💇♀️
This commit is contained in:
parent
36aee3f544
commit
63c2efaa16
|
@ -1,3 +1,28 @@
|
|||
* Adds support for `if_not_exists` to `add_foreign_key` and `if_exists` to `remove_foreign_key`.
|
||||
|
||||
Applications can set their migrations to ignore exceptions raised when adding a foreign key
|
||||
that already exists or when removing a foreign key that does not exist.
|
||||
|
||||
Example Usage:
|
||||
|
||||
```ruby
|
||||
class AddAuthorsForeignKeyToArticles < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_foreign_key :articles, :authors, if_not_exists: true
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
```ruby
|
||||
class RemoveAuthorsForeignKeyFromArticles < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
remove_foreign_key :articles, :authors, if_exists: true
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
*Roberto Miranda*
|
||||
|
||||
* Prevent polluting ENV during postgresql structure dump/load
|
||||
|
||||
Some configuration parameters were provided to pg_dump / psql via
|
||||
|
|
|
@ -1039,6 +1039,10 @@ module ActiveRecord
|
|||
#
|
||||
# ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
|
||||
#
|
||||
# ====== Creating a foreign key, ignoring method call if the foreign key exists
|
||||
#
|
||||
# add_foreign_key(:articles, :authors, if_not_exists: true)
|
||||
#
|
||||
# ====== Creating a foreign key on a specific column
|
||||
#
|
||||
# add_foreign_key :articles, :users, column: :author_id, primary_key: "lng_id"
|
||||
|
@ -1066,10 +1070,14 @@ module ActiveRecord
|
|||
# Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
|
||||
# [<tt>:on_update</tt>]
|
||||
# Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
|
||||
# [<tt>:if_not_exists</tt>]
|
||||
# Specifies if the foreign key already exists to not try to re-add it. This will avoid
|
||||
# duplicate column errors.
|
||||
# [<tt>:validate</tt>]
|
||||
# (PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+.
|
||||
def add_foreign_key(from_table, to_table, **options)
|
||||
return unless supports_foreign_keys?
|
||||
return if options[:if_not_exists] == true && foreign_key_exists?(from_table, to_table)
|
||||
|
||||
options = foreign_key_options(from_table, to_table, options)
|
||||
at = create_alter_table from_table
|
||||
|
@ -1099,12 +1107,18 @@ module ActiveRecord
|
|||
#
|
||||
# remove_foreign_key :accounts, name: :special_fk_name
|
||||
#
|
||||
# Checks if the foreign key exists before trying to remove it. Will silently ignore indexes that
|
||||
# don't exist.
|
||||
#
|
||||
# remove_foreign_key :accounts, :branches, if_exists: true
|
||||
#
|
||||
# The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key
|
||||
# with an addition of
|
||||
# [<tt>:to_table</tt>]
|
||||
# The name of the table that contains the referenced primary key.
|
||||
def remove_foreign_key(from_table, to_table = nil, **options)
|
||||
return unless supports_foreign_keys?
|
||||
return if options[:if_exists] == true && !foreign_key_exists?(from_table, to_table)
|
||||
|
||||
fk_name_to_delete = foreign_key_for!(from_table, to_table: to_table, **options).name
|
||||
|
||||
|
|
|
@ -60,6 +60,8 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def remove_foreign_key(from_table, to_table = nil, **options)
|
||||
return if options[:if_exists] == true && !foreign_key_exists?(from_table, to_table)
|
||||
|
||||
to_table ||= options[:to_table]
|
||||
options = options.except(:name, :to_table, :validate)
|
||||
foreign_keys = foreign_keys(from_table)
|
||||
|
|
|
@ -575,6 +575,67 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
|
|||
silence_stream($stdout) { migration.migrate(:down) }
|
||||
ActiveRecord::Base.table_name_suffix = nil
|
||||
end
|
||||
|
||||
def test_remove_foreign_key_with_if_exists_not_set
|
||||
@connection.add_foreign_key :astronauts, :rockets
|
||||
assert_equal 1, @connection.foreign_keys("astronauts").size
|
||||
|
||||
@connection.remove_foreign_key :astronauts, :rockets
|
||||
assert_equal [], @connection.foreign_keys("astronauts")
|
||||
|
||||
error = assert_raises do
|
||||
@connection.remove_foreign_key :astronauts, :rockets
|
||||
end
|
||||
|
||||
assert_equal("Table 'astronauts' has no foreign key for rockets", error.message)
|
||||
end
|
||||
|
||||
def test_remove_foreign_key_with_if_exists_set
|
||||
@connection.add_foreign_key :astronauts, :rockets
|
||||
assert_equal 1, @connection.foreign_keys("astronauts").size
|
||||
|
||||
@connection.remove_foreign_key :astronauts, :rockets
|
||||
assert_equal [], @connection.foreign_keys("astronauts")
|
||||
|
||||
assert_nothing_raised do
|
||||
@connection.remove_foreign_key :astronauts, :rockets, if_exists: true
|
||||
end
|
||||
end
|
||||
|
||||
def test_add_foreign_key_with_if_not_exists_not_set
|
||||
@connection.add_foreign_key :astronauts, :rockets
|
||||
assert_equal 1, @connection.foreign_keys("astronauts").size
|
||||
|
||||
if current_adapter?(:SQLite3Adapter)
|
||||
assert_nothing_raised do
|
||||
@connection.add_foreign_key :astronauts, :rockets
|
||||
end
|
||||
else
|
||||
error = assert_raises do
|
||||
@connection.add_foreign_key :astronauts, :rockets
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter)
|
||||
if ActiveRecord::Base.connection.mariadb?
|
||||
assert_match(/Duplicate key on write or update/, error.message)
|
||||
else
|
||||
assert_match(/Duplicate foreign key constraint name/, error.message)
|
||||
end
|
||||
else
|
||||
assert_match(/PG::DuplicateObject: ERROR:.*for relation "astronauts" already exists/, error.message)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
def test_add_foreign_key_with_if_not_exists_set
|
||||
@connection.add_foreign_key :astronauts, :rockets
|
||||
assert_equal 1, @connection.foreign_keys("astronauts").size
|
||||
|
||||
assert_nothing_raised do
|
||||
@connection.add_foreign_key :astronauts, :rockets, if_not_exists: true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue