diff options
9 files changed, 60 insertions, 5 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 28441e598e..6494c7374e 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,10 @@ +* Introduce `force: :cascade` option for `create_table`. Using this option + will recreate tables even if they have dependent objects (like foreign keys). + `db/schema.rb` now uses `force: :cascade`. This makes it possible to + reload the schema when foreign keys are in place. + + *Matthew Draper*, *Yves Senn* + * `db:schema:load` and `db:structure:load` no longer purge the database before loading the schema. This is left for the user to do. `db:test:prepare` will still purge the database. 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 fd52cdf716..b340e8334b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -132,6 +132,7 @@ module ActiveRecord # Make a temporary table. # [<tt>:force</tt>] # Set to true to drop the table before creating it. + # Set to +:cascade+ to drop dependent objects as well. # Defaults to false. # [<tt>:as</tt>] # SQL to use to generate the table. When this option is used, the block is @@ -361,8 +362,12 @@ module ActiveRecord # Drops a table from the database. # - # Although this command ignores +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. + # [<tt>:force</tt>] + # Set to +:cascade+ to drop dependent objects as well. + # Defaults to false. + # + # 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 = {}) execute "DROP TABLE #{quote_table_name(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 69582ebb6f..ced80bacc8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -487,7 +487,7 @@ module ActiveRecord end def drop_table(table_name, options = {}) - execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}" + execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}#{' 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 f29b793a3f..7ba5437474 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -120,6 +120,10 @@ module ActiveRecord SQL end + def drop_table(table_name, options = {}) + execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}" + end + # Returns true if schema exists. def schema_exists?(name) exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0 diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 77aa2efc47..3c44a625cc 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -126,7 +126,7 @@ HEADER else tbl.print ", id: false" end - tbl.print ", force: true" + tbl.print ", force: :cascade" tbl.puts " do |t|" # then dump all non-primary key columns diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb index 7b7532993c..d6deb6fb1f 100644 --- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb +++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb @@ -130,7 +130,7 @@ class PostgresqlLargeKeysTest < ActiveRecord::TestCase def test_omg schema = dump_table_schema "big_serials" - assert_match "create_table \"big_serials\", id: :bigserial, force: true", schema + assert_match "create_table \"big_serials\", id: :bigserial", schema end def teardown diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb index d91e7142b3..d774cfebc4 100644 --- a/activerecord/test/cases/migration/change_schema_test.rb +++ b/activerecord/test/cases/migration/change_schema_test.rb @@ -415,5 +415,36 @@ module ActiveRecord yield end end + + if ActiveRecord::Base.connection.supports_foreign_keys? + class ChangeSchemaWithDependentObjectsTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false + + setup do + @connection = ActiveRecord::Base.connection + @connection.create_table :trains + @connection.create_table(:wagons) { |t| t.references :train } + @connection.add_foreign_key :wagons, :trains + end + + teardown do + [:wagons, :trains].each do |table| + @connection.drop_table(table) if @connection.table_exists?(table) + end + end + + def test_create_table_with_force_cascade_drops_dependent_objects + skip "MySQL > 5.5 does not drop dependent objects with DROP TABLE CASCADE" if current_adapter?(:MysqlAdapter, :Mysql2Adapter) + # can't re-create table referenced by foreign key + assert_raises(ActiveRecord::StatementInvalid) do + @connection.create_table :trains, force: true + end + + # can recreate referenced table with force: :cascade + @connection.create_table :trains, force: :cascade + assert_equal [], @connection.foreign_keys(:wagons) + end + end + end end end diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 01c686f934..a094136766 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -40,6 +40,11 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_no_match %r{create_table "schema_migrations"}, output end + def test_schema_dump_uses_force_cascade_on_create_table + output = dump_table_schema "authors" + assert_match %r{create_table "authors", force: :cascade}, output + end + def test_schema_dump_excludes_sqlite_sequence output = standard_dump assert_no_match %r{create_table "sqlite_sequence"}, output diff --git a/guides/source/4_2_release_notes.md b/guides/source/4_2_release_notes.md index d8700539c4..f5575ac523 100644 --- a/guides/source/4_2_release_notes.md +++ b/guides/source/4_2_release_notes.md @@ -662,6 +662,9 @@ Please refer to the [Changelog][active-record] for detailed changes. ### Notable changes +* `SchemaDumper` uses `force: :cascade` on `create_table`. This makes it + possible to reload a schema when foreign keys are in place. + * Added a `:required` option to singular associations, which defines a presence validation on the association. ([Pull Request](https://github.com/rails/rails/pull/16056)) |