mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Raise an exception when using unrecognized options in change_table block
In a database migration, the expressions `add_column`, `remove_index`, etc. accept as keyword options `if_exists: true`/`if_not_exists: true` which will skip that table alteration if the column or index does or does not already exist. This might lead some to think that within a change_table block, ``` change_table(:table) do |t| t.column :new_column, if_not_exists: true t.remove_index :old_column, if_exists: true end ``` also works, but it doesn't. Or rather, it is silently ignored when change_table is called with `bulk: true`, and it works accidentally otherwise. This commit raises an exception when these options are used in a change_table block, which suggests the similar syntax: `t.column :new_column unless t.column_exists?(:new_column)`. This suggestion is already made in the documentation to `ActiveRecord::ConnectionAdapters::Table`. https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html#method-i-column_exists-3F Do not raise these new exceptions on migrations before 7.0
This commit is contained in:
parent
a47c821322
commit
d536ffd591
5 changed files with 72 additions and 4 deletions
|
@ -622,6 +622,7 @@ module ActiveRecord
|
||||||
#
|
#
|
||||||
# See TableDefinition#column for details of the options you can use.
|
# See TableDefinition#column for details of the options you can use.
|
||||||
def column(column_name, type, index: nil, **options)
|
def column(column_name, type, index: nil, **options)
|
||||||
|
raise_on_if_exist_options(options)
|
||||||
@base.add_column(name, column_name, type, **options)
|
@base.add_column(name, column_name, type, **options)
|
||||||
if index
|
if index
|
||||||
index_options = index.is_a?(Hash) ? index : {}
|
index_options = index.is_a?(Hash) ? index : {}
|
||||||
|
@ -647,6 +648,7 @@ module ActiveRecord
|
||||||
#
|
#
|
||||||
# See {connection.add_index}[rdoc-ref:SchemaStatements#add_index] for details of the options you can use.
|
# See {connection.add_index}[rdoc-ref:SchemaStatements#add_index] for details of the options you can use.
|
||||||
def index(column_name, **options)
|
def index(column_name, **options)
|
||||||
|
raise_on_if_exist_options(options)
|
||||||
@base.add_index(name, column_name, **options)
|
@base.add_index(name, column_name, **options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -657,8 +659,8 @@ module ActiveRecord
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# See {connection.index_exists?}[rdoc-ref:SchemaStatements#index_exists?]
|
# See {connection.index_exists?}[rdoc-ref:SchemaStatements#index_exists?]
|
||||||
def index_exists?(column_name, options = {})
|
def index_exists?(column_name, **options)
|
||||||
@base.index_exists?(name, column_name, options)
|
@base.index_exists?(name, column_name, **options)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Renames the given index on the table.
|
# Renames the given index on the table.
|
||||||
|
@ -676,6 +678,7 @@ module ActiveRecord
|
||||||
#
|
#
|
||||||
# See {connection.add_timestamps}[rdoc-ref:SchemaStatements#add_timestamps]
|
# See {connection.add_timestamps}[rdoc-ref:SchemaStatements#add_timestamps]
|
||||||
def timestamps(**options)
|
def timestamps(**options)
|
||||||
|
raise_on_if_exist_options(options)
|
||||||
@base.add_timestamps(name, **options)
|
@base.add_timestamps(name, **options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -686,6 +689,7 @@ module ActiveRecord
|
||||||
#
|
#
|
||||||
# See TableDefinition#column for details of the options you can use.
|
# See TableDefinition#column for details of the options you can use.
|
||||||
def change(column_name, type, **options)
|
def change(column_name, type, **options)
|
||||||
|
raise_on_if_exist_options(options)
|
||||||
@base.change_column(name, column_name, type, **options)
|
@base.change_column(name, column_name, type, **options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -717,6 +721,7 @@ module ActiveRecord
|
||||||
#
|
#
|
||||||
# See {connection.remove_columns}[rdoc-ref:SchemaStatements#remove_columns]
|
# See {connection.remove_columns}[rdoc-ref:SchemaStatements#remove_columns]
|
||||||
def remove(*column_names, **options)
|
def remove(*column_names, **options)
|
||||||
|
raise_on_if_exist_options(options)
|
||||||
@base.remove_columns(name, *column_names, **options)
|
@base.remove_columns(name, *column_names, **options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -729,6 +734,7 @@ module ActiveRecord
|
||||||
#
|
#
|
||||||
# See {connection.remove_index}[rdoc-ref:SchemaStatements#remove_index]
|
# See {connection.remove_index}[rdoc-ref:SchemaStatements#remove_index]
|
||||||
def remove_index(column_name = nil, **options)
|
def remove_index(column_name = nil, **options)
|
||||||
|
raise_on_if_exist_options(options)
|
||||||
@base.remove_index(name, column_name, **options)
|
@base.remove_index(name, column_name, **options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -757,6 +763,7 @@ module ActiveRecord
|
||||||
#
|
#
|
||||||
# See {connection.add_reference}[rdoc-ref:SchemaStatements#add_reference] for details of the options you can use.
|
# See {connection.add_reference}[rdoc-ref:SchemaStatements#add_reference] for details of the options you can use.
|
||||||
def references(*args, **options)
|
def references(*args, **options)
|
||||||
|
raise_on_if_exist_options(options)
|
||||||
args.each do |ref_name|
|
args.each do |ref_name|
|
||||||
@base.add_reference(name, ref_name, **options)
|
@base.add_reference(name, ref_name, **options)
|
||||||
end
|
end
|
||||||
|
@ -770,6 +777,7 @@ module ActiveRecord
|
||||||
#
|
#
|
||||||
# See {connection.remove_reference}[rdoc-ref:SchemaStatements#remove_reference]
|
# See {connection.remove_reference}[rdoc-ref:SchemaStatements#remove_reference]
|
||||||
def remove_references(*args, **options)
|
def remove_references(*args, **options)
|
||||||
|
raise_on_if_exist_options(options)
|
||||||
args.each do |ref_name|
|
args.each do |ref_name|
|
||||||
@base.remove_reference(name, ref_name, **options)
|
@base.remove_reference(name, ref_name, **options)
|
||||||
end
|
end
|
||||||
|
@ -783,6 +791,7 @@ module ActiveRecord
|
||||||
#
|
#
|
||||||
# See {connection.add_foreign_key}[rdoc-ref:SchemaStatements#add_foreign_key]
|
# See {connection.add_foreign_key}[rdoc-ref:SchemaStatements#add_foreign_key]
|
||||||
def foreign_key(*args, **options)
|
def foreign_key(*args, **options)
|
||||||
|
raise_on_if_exist_options(options)
|
||||||
@base.add_foreign_key(name, *args, **options)
|
@base.add_foreign_key(name, *args, **options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -793,6 +802,7 @@ module ActiveRecord
|
||||||
#
|
#
|
||||||
# See {connection.remove_foreign_key}[rdoc-ref:SchemaStatements#remove_foreign_key]
|
# See {connection.remove_foreign_key}[rdoc-ref:SchemaStatements#remove_foreign_key]
|
||||||
def remove_foreign_key(*args, **options)
|
def remove_foreign_key(*args, **options)
|
||||||
|
raise_on_if_exist_options(options)
|
||||||
@base.remove_foreign_key(name, *args, **options)
|
@base.remove_foreign_key(name, *args, **options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -822,6 +832,22 @@ module ActiveRecord
|
||||||
def remove_check_constraint(*args)
|
def remove_check_constraint(*args)
|
||||||
@base.remove_check_constraint(name, *args)
|
@base.remove_check_constraint(name, *args)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def raise_on_if_exist_options(options)
|
||||||
|
unrecognized_option = options.keys.find do |key|
|
||||||
|
key == :if_exists || key == :if_not_exists
|
||||||
|
end
|
||||||
|
if unrecognized_option
|
||||||
|
conditional = unrecognized_option == :if_exists ? "if" : "unless"
|
||||||
|
message = <<~TXT
|
||||||
|
Option #{unrecognized_option} will be ignored. If you are calling an expression like
|
||||||
|
`t.column(.., #{unrecognized_option}: true)` from inside a change_table block, try a
|
||||||
|
conditional clause instead, as in `t.column(..) #{conditional} t.column_exists?(..)`
|
||||||
|
TXT
|
||||||
|
raise ArgumentError.new(message)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -67,6 +67,14 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def change_table(table_name, **options)
|
||||||
|
if block_given?
|
||||||
|
super { |t| yield compatible_table_definition(t) }
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
module TableDefinition
|
module TableDefinition
|
||||||
def new_column_definition(name, type, **options)
|
def new_column_definition(name, type, **options)
|
||||||
type = PostgreSQLCompat.compatible_timestamp_type(type, @conn)
|
type = PostgreSQLCompat.compatible_timestamp_type(type, @conn)
|
||||||
|
@ -77,6 +85,10 @@ module ActiveRecord
|
||||||
options[:precision] ||= nil
|
options[:precision] ||= nil
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def raise_on_if_exist_options(options)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -96,7 +96,7 @@ module ActiveRecord
|
||||||
def change
|
def change
|
||||||
change_table("horses") do |t|
|
change_table("horses") do |t|
|
||||||
t.remove_index [:name, :color]
|
t.remove_index [:name, :color]
|
||||||
t.remove_index [:color], if_exists: true
|
t.remove_index [:color] if t.index_exists?(:color)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -188,7 +188,7 @@ module ActiveRecord
|
||||||
|
|
||||||
def test_index_exists
|
def test_index_exists
|
||||||
with_change_table do |t|
|
with_change_table do |t|
|
||||||
@connection.expect :index_exists?, nil, [:delete_me, :bar, {}]
|
@connection.expect :index_exists?, nil, [:delete_me, :bar]
|
||||||
t.index_exists?(:bar)
|
t.index_exists?(:bar)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -289,6 +289,22 @@ module ActiveRecord
|
||||||
t.remove_check_constraint name: "price_check"
|
t.remove_check_constraint name: "price_check"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_remove_column_with_if_exists_raises_error
|
||||||
|
assert_raises(ArgumentError) do
|
||||||
|
with_change_table do |t|
|
||||||
|
t.remove :name, if_exists: true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_add_column_with_if_not_exists_raises_error
|
||||||
|
assert_raises(ArgumentError) do
|
||||||
|
with_change_table do |t|
|
||||||
|
t.string :nickname, if_not_exists: true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -519,6 +519,20 @@ module ActiveRecord
|
||||||
assert connection.column_exists?(:testings, :published_at, **precision_implicit_default)
|
assert connection.column_exists?(:testings, :published_at, **precision_implicit_default)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_change_table_allows_if_exists_option_on_6_1
|
||||||
|
migration = Class.new(ActiveRecord::Migration[6.1]) {
|
||||||
|
def migrate(x)
|
||||||
|
change_table(:testings) do |t|
|
||||||
|
t.remove :foo, if_exists: true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}.new
|
||||||
|
|
||||||
|
ActiveRecord::Migrator.new(:up, [migration], @schema_migration).migrate
|
||||||
|
|
||||||
|
assert_not connection.column_exists?(:testings, :foo)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def precision_implicit_default
|
def precision_implicit_default
|
||||||
if current_adapter?(:Mysql2Adapter)
|
if current_adapter?(:Mysql2Adapter)
|
||||||
|
|
Loading…
Reference in a new issue