diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 4372e9f139636..439395297e1b0 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,13 @@
+* Allow `drop_table` to accept an array of table names.
+
+ This will let you to drop multiple tables in a single call.
+
+ ```ruby
+ ActiveRecord::Base.connection.drop_table(:users, :posts)
+ ```
+
+ *Gabriel Sobrinho*
+
* Add support for PostgreSQL `IF NOT EXISTS` via the `:if_not_exists` option
on the `add_enum_value` method.
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index eb409532c33cf..1af897584135c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -525,7 +525,7 @@ def rename_table(table_name, new_name, **)
raise NotImplementedError, "rename_table is not implemented"
end
- # Drops a table from the database.
+ # Drops a table or tables from the database.
#
# [:force]
# Set to +:cascade+ to drop dependent objects as well.
@@ -536,10 +536,12 @@ def rename_table(table_name, new_name, **)
#
# Although this command ignores most +options+ and the block if one is given,
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
- # In that case, +options+ and the block will be used by #create_table.
- def drop_table(table_name, **options)
- schema_cache.clear_data_source_cache!(table_name.to_s)
- execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
+ # In that case, +options+ and the block will be used by #create_table except if you provide more than one table which is not supported.
+ def drop_table(*table_names, **options)
+ table_names.each do |table_name|
+ schema_cache.clear_data_source_cache!(table_name.to_s)
+ execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
+ end
end
# Add a new +type+ column named +column_name+ to +table_name+.
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index c2b9257bd91a2..0144f6b402c39 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -332,7 +332,7 @@ def rename_table(table_name, new_name, **options)
rename_table_indexes(table_name, new_name, **options)
end
- # Drops a table from the database.
+ # Drops a table or tables from the database.
#
# [:force]
# Set to +:cascade+ to drop dependent objects as well.
@@ -346,10 +346,10 @@ def rename_table(table_name, new_name, **options)
#
# Although this command ignores most +options+ and the block if one is given,
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
- # In that case, +options+ and the block will be used by create_table.
- def drop_table(table_name, **options)
- schema_cache.clear_data_source_cache!(table_name.to_s)
- execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
+ # In that case, +options+ and the block will be used by #create_table except if you provide more than one table which is not supported.
+ def drop_table(*table_names, **options)
+ table_names.each { |table_name| schema_cache.clear_data_source_cache!(table_name.to_s) }
+ execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{table_names.map { |table_name| quote_table_name(table_name) }.join(', ')}#{' CASCADE' if options[:force] == :cascade}"
end
def rename_index(table_name, old_name, new_name)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 294a7c4a8fb94..5812946ff2f72 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -54,9 +54,9 @@ def drop_database(name) # :nodoc:
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
end
- def drop_table(table_name, **options) # :nodoc:
- schema_cache.clear_data_source_cache!(table_name.to_s)
- execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
+ def drop_table(*table_names, **options) # :nodoc:
+ table_names.each { |table_name| schema_cache.clear_data_source_cache!(table_name.to_s) }
+ execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{table_names.map { |table_name| quote_table_name(table_name) }.join(', ')}#{' CASCADE' if options[:force] == :cascade}"
end
# Returns true if schema exists.
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 79394819e9768..1ca6c4908ee58 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -355,7 +355,7 @@ def initialize
#
# === Deletion
#
- # * drop_table(name): Drops the table called +name+.
+ # * drop_table(*names): Drops the given tables.
# * drop_join_table(table_1, table_2, options): Drops the join table
# specified by the given arguments.
# * remove_column(table_name, column_name, type, options): Removes the column
@@ -602,7 +602,7 @@ def create_join_table(table_1, table_2, **options)
end
end
- def drop_table(table_name, **options)
+ def drop_table(*table_names, **options)
if block_given?
super { |t| yield compatible_table_definition(t) }
else
diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
index aea2568df0a5a..28866e65ba38d 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -202,13 +202,18 @@ def invert_create_table(args, &block)
end
def invert_drop_table(args, &block)
- if args.last.is_a?(Hash)
- args.last.delete(:if_exists)
+ options = args.extract_options!
+ options.delete(:if_exists)
+
+ if args.size > 1
+ raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given a single table name."
end
- if args.size == 1 && block == nil
+
+ if args.size == 1 && options == {} && block == nil
raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given options or a block (can be empty)."
end
- super
+
+ super(args.push(options), &block)
end
def invert_rename_table(args)
diff --git a/activerecord/test/cases/adapters/abstract_mysql_adapter/active_schema_test.rb b/activerecord/test/cases/adapters/abstract_mysql_adapter/active_schema_test.rb
index 9e60a3e60dc59..6d25e26a54a00 100644
--- a/activerecord/test/cases/adapters/abstract_mysql_adapter/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/abstract_mysql_adapter/active_schema_test.rb
@@ -118,6 +118,10 @@ def test_drop_table
assert_equal "DROP TABLE `people`", drop_table(:people)
end
+ def test_drop_tables
+ assert_equal "DROP TABLE `people`, `sobrinho`", drop_table(:people, :sobrinho)
+ end
+
def test_create_mysql_database_with_encoding
if ActiveRecord::Base.lease_connection.send(:row_format_dynamic_by_default?)
assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8mb4`", create_database(:matt)
@@ -147,6 +151,10 @@ def test_drop_table_with_specific_database
assert_equal "DROP TABLE `otherdb`.`people`", drop_table("otherdb.people")
end
+ def test_drop_tables_with_specific_database
+ assert_equal "DROP TABLE `otherdb`.`people`, `otherdb`.`sobrinho`", drop_table("otherdb.people", "otherdb.sobrinho")
+ end
+
def test_add_timestamps
with_real_execute do
ActiveRecord::Base.lease_connection.create_table :delete_me
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index b5dce30202bca..0ac81f055f4d4 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -467,10 +467,24 @@ def test_drop_table_if_exists
assert_not connection.table_exists?(:testings)
end
+ def test_drop_tables_if_exists
+ connection.create_table(:testings)
+ connection.create_table(:sobrinho)
+ assert connection.table_exists?(:testings)
+ assert connection.table_exists?(:sobrinho)
+ connection.drop_table(:testings, :sobrinho, if_exists: true)
+ assert_not connection.table_exists?(:testings)
+ assert_not connection.table_exists?(:sobrinho)
+ end
+
def test_drop_table_if_exists_nothing_raised
assert_nothing_raised { connection.drop_table(:nonexistent, if_exists: true) }
end
+ def test_drop_tables_if_exists_nothing_raised
+ assert_nothing_raised { connection.drop_table(:nonexistent, :nonexistent_sobrinho, if_exists: true) }
+ end
+
private
def testing_table_with_only_foo_attribute
connection.create_table :testings, id: false do |t|
diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb
index 2f4a00d5bbff5..2d66fb7bc9dd1 100644
--- a/activerecord/test/cases/migration/command_recorder_test.rb
+++ b/activerecord/test/cases/migration/command_recorder_test.rb
@@ -172,11 +172,31 @@ def test_invert_drop_table_with_if_exists
end
def test_invert_drop_table_without_a_block_nor_option
- assert_raises(ActiveRecord::IrreversibleMigration) do
+ assert_raises(ActiveRecord::IrreversibleMigration, match: "To avoid mistakes, drop_table is only reversible if given options or a block (can be empty).") do
@recorder.inverse_of :drop_table, [:people_reminders]
end
end
+ def test_invert_drop_table_with_multiple_tables
+ assert_raises(ActiveRecord::IrreversibleMigration, match: "To avoid mistakes, drop_table is only reversible if given a single table name.") do
+ @recorder.inverse_of :drop_table, [:musics, :artists]
+ end
+ end
+
+ def test_invert_drop_table_with_multiple_tables_and_options
+ assert_raises(ActiveRecord::IrreversibleMigration, match: "To avoid mistakes, drop_table is only reversible if given a single table name.") do
+ @recorder.inverse_of :drop_table, [:musics, :artists, id: false]
+ end
+ end
+
+ def test_invert_drop_table_with_multiple_tables_and_block
+ block = Proc.new { }
+
+ assert_raises(ActiveRecord::IrreversibleMigration, match: "To avoid mistakes, drop_table is only reversible if given a single table name.") do
+ @recorder.inverse_of :drop_table, [:musics, :artists], &block
+ end
+ end
+
def test_invert_create_join_table
drop_join_table = @recorder.inverse_of :create_join_table, [:musics, :artists]
assert_equal [:drop_join_table, [:musics, :artists], nil], drop_join_table