diff options
Diffstat (limited to 'activerecord/test/cases/invertible_migration_test.rb')
-rw-r--r-- | activerecord/test/cases/invertible_migration_test.rb | 425 |
1 files changed, 425 insertions, 0 deletions
diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb new file mode 100644 index 0000000000..6cf17ac15d --- /dev/null +++ b/activerecord/test/cases/invertible_migration_test.rb @@ -0,0 +1,425 @@ +# frozen_string_literal: true + +require "cases/helper" + +class Horse < ActiveRecord::Base +end + +module ActiveRecord + class InvertibleMigrationTest < ActiveRecord::TestCase + class SilentMigration < ActiveRecord::Migration::Current + def write(text = "") + # sssshhhhh!! + end + end + + class InvertibleMigration < SilentMigration + def change + create_table("horses") do |t| + t.column :content, :text + t.column :remind_at, :datetime + end + end + end + + class InvertibleTransactionMigration < InvertibleMigration + def change + transaction do + super + end + end + end + + class InvertibleRevertMigration < SilentMigration + def change + revert do + create_table("horses") do |t| + t.column :content, :text + t.column :remind_at, :datetime + end + end + end + end + + class InvertibleByPartsMigration < SilentMigration + attr_writer :test + def change + create_table("new_horses") do |t| + t.column :breed, :string + end + reversible do |dir| + @test.yield :both + dir.up { @test.yield :up } + dir.down { @test.yield :down } + end + revert do + create_table("horses") do |t| + t.column :content, :text + t.column :remind_at, :datetime + end + end + end + end + + class NonInvertibleMigration < SilentMigration + def change + create_table("horses") do |t| + t.column :content, :text + t.column :remind_at, :datetime + end + remove_column "horses", :content + end + end + + class RemoveIndexMigration1 < SilentMigration + def self.up + create_table("horses") do |t| + t.column :name, :string + t.column :color, :string + t.index [:name, :color] + end + end + end + + class RemoveIndexMigration2 < SilentMigration + def change + change_table("horses") do |t| + t.remove_index [:name, :color] + end + end + end + + class ChangeColumnDefault1 < SilentMigration + def change + create_table("horses") do |t| + t.column :name, :string, default: "Sekitoba" + end + end + end + + class ChangeColumnDefault2 < SilentMigration + def change + change_column_default :horses, :name, from: "Sekitoba", to: "Diomed" + end + end + + class DisableExtension1 < SilentMigration + def change + enable_extension "hstore" + end + end + + class DisableExtension2 < SilentMigration + def change + disable_extension "hstore" + end + end + + class LegacyMigration < ActiveRecord::Migration::Current + def self.up + create_table("horses") do |t| + t.column :content, :text + t.column :remind_at, :datetime + end + end + + def self.down + drop_table("horses") + end + end + + class RevertWholeMigration < SilentMigration + def initialize(name = self.class.name, version = nil, migration) + @migration = migration + super(name, version) + end + + def change + revert @migration + end + end + + class NestedRevertWholeMigration < RevertWholeMigration + def change + revert { super } + end + end + + class RevertNamedIndexMigration1 < SilentMigration + def change + create_table("horses") do |t| + t.column :content, :string + t.column :remind_at, :datetime + end + add_index :horses, :content + end + end + + class RevertNamedIndexMigration2 < SilentMigration + def change + add_index :horses, :content, name: "horses_index_named" + end + end + + class RevertCustomForeignKeyTable < SilentMigration + def change + change_table(:horses) do |t| + t.references :owner, foreign_key: { to_table: :developers } + end + end + end + + class UpOnlyMigration < SilentMigration + def change + add_column :horses, :oldie, :integer, default: 0 + up_only { execute "update horses set oldie = 1" } + end + end + + self.use_transactional_tests = false + + setup do + @verbose_was, ActiveRecord::Migration.verbose = ActiveRecord::Migration.verbose, false + end + + teardown do + %w[horses new_horses].each do |table| + if ActiveRecord::Base.connection.table_exists?(table) + ActiveRecord::Base.connection.drop_table(table) + end + end + ActiveRecord::Migration.verbose = @verbose_was + end + + def test_no_reverse + migration = NonInvertibleMigration.new + migration.migrate(:up) + assert_raises(IrreversibleMigration) do + migration.migrate(:down) + end + end + + def test_exception_on_removing_index_without_column_option + index_definition = ["horses", [:name, :color]] + migration1 = RemoveIndexMigration1.new + migration1.migrate(:up) + assert migration1.connection.index_exists?(*index_definition) + + migration2 = RemoveIndexMigration2.new + migration2.migrate(:up) + assert_not migration2.connection.index_exists?(*index_definition) + + migration2.migrate(:down) + assert migration2.connection.index_exists?(*index_definition) + end + + def test_migrate_up + migration = InvertibleMigration.new + migration.migrate(:up) + assert migration.connection.table_exists?("horses"), "horses should exist" + end + + def test_migrate_down + migration = InvertibleMigration.new + migration.migrate :up + migration.migrate :down + assert_not migration.connection.table_exists?("horses") + end + + def test_migrate_revert + migration = InvertibleMigration.new + revert = InvertibleRevertMigration.new + migration.migrate :up + revert.migrate :up + assert_not migration.connection.table_exists?("horses") + revert.migrate :down + assert migration.connection.table_exists?("horses") + migration.migrate :down + assert_not migration.connection.table_exists?("horses") + end + + def test_migrate_revert_by_part + InvertibleMigration.new.migrate :up + received = [] + migration = InvertibleByPartsMigration.new + migration.test = ->(dir) { + assert migration.connection.table_exists?("horses") + assert migration.connection.table_exists?("new_horses") + received << dir + } + migration.migrate :up + assert_equal [:both, :up], received + assert_not migration.connection.table_exists?("horses") + assert migration.connection.table_exists?("new_horses") + migration.migrate :down + assert_equal [:both, :up, :both, :down], received + assert migration.connection.table_exists?("horses") + assert_not migration.connection.table_exists?("new_horses") + end + + def test_migrate_revert_whole_migration + migration = InvertibleMigration.new + [LegacyMigration, InvertibleMigration].each do |klass| + revert = RevertWholeMigration.new(klass) + migration.migrate :up + revert.migrate :up + assert_not migration.connection.table_exists?("horses") + revert.migrate :down + assert migration.connection.table_exists?("horses") + migration.migrate :down + assert_not migration.connection.table_exists?("horses") + end + end + + def test_migrate_nested_revert_whole_migration + revert = NestedRevertWholeMigration.new(InvertibleRevertMigration) + revert.migrate :down + assert revert.connection.table_exists?("horses") + revert.migrate :up + assert_not revert.connection.table_exists?("horses") + end + + def test_migrate_revert_transaction + migration = InvertibleTransactionMigration.new + migration.migrate :up + assert migration.connection.table_exists?("horses") + migration.migrate :down + assert_not migration.connection.table_exists?("horses") + end + + def test_migrate_revert_change_column_default + migration1 = ChangeColumnDefault1.new + migration1.migrate(:up) + assert_equal "Sekitoba", Horse.new.name + + migration2 = ChangeColumnDefault2.new + migration2.migrate(:up) + Horse.reset_column_information + assert_equal "Diomed", Horse.new.name + + migration2.migrate(:down) + Horse.reset_column_information + assert_equal "Sekitoba", Horse.new.name + end + + if current_adapter?(:PostgreSQLAdapter) + def test_migrate_enable_and_disable_extension + migration1 = InvertibleMigration.new + migration2 = DisableExtension1.new + migration3 = DisableExtension2.new + + migration1.migrate(:up) + migration2.migrate(:up) + assert_equal true, Horse.connection.extension_enabled?("hstore") + + migration3.migrate(:up) + assert_equal false, Horse.connection.extension_enabled?("hstore") + + migration3.migrate(:down) + assert_equal true, Horse.connection.extension_enabled?("hstore") + + migration2.migrate(:down) + assert_equal false, Horse.connection.extension_enabled?("hstore") + ensure + enable_extension!("hstore", ActiveRecord::Base.connection) + end + end + + def test_revert_order + block = Proc.new { |t| t.string :name } + recorder = ActiveRecord::Migration::CommandRecorder.new(ActiveRecord::Base.connection) + recorder.instance_eval do + create_table("apples", &block) + revert do + create_table("bananas", &block) + revert do + create_table("clementines") + create_table("dates") + end + create_table("elderberries") + end + revert do + create_table("figs") + create_table("grapes") + end + end + assert_equal [[:create_table, ["apples"], block], [:drop_table, ["elderberries"], nil], + [:create_table, ["clementines"], nil], [:create_table, ["dates"], nil], + [:drop_table, ["bananas"], block], [:drop_table, ["grapes"], nil], + [:drop_table, ["figs"], nil]], recorder.commands + end + + def test_legacy_up + LegacyMigration.migrate :up + assert ActiveRecord::Base.connection.table_exists?("horses"), "horses should exist" + end + + def test_legacy_down + LegacyMigration.migrate :up + LegacyMigration.migrate :down + assert_not ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist" + end + + def test_up + LegacyMigration.up + assert ActiveRecord::Base.connection.table_exists?("horses"), "horses should exist" + end + + def test_down + LegacyMigration.up + LegacyMigration.down + assert_not ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist" + end + + def test_migrate_down_with_table_name_prefix + ActiveRecord::Base.table_name_prefix = "p_" + ActiveRecord::Base.table_name_suffix = "_s" + migration = InvertibleMigration.new + migration.migrate(:up) + assert_nothing_raised { migration.migrate(:down) } + assert_not ActiveRecord::Base.connection.table_exists?("p_horses_s"), "p_horses_s should not exist" + ensure + ActiveRecord::Base.table_name_prefix = ActiveRecord::Base.table_name_suffix = "" + end + + def test_migrations_can_handle_foreign_keys_to_specific_tables + migration = RevertCustomForeignKeyTable.new + InvertibleMigration.migrate(:up) + migration.migrate(:up) + migration.migrate(:down) + end + + # MySQL 5.7 and Oracle do not allow to create duplicate indexes on the same columns + unless current_adapter?(:Mysql2Adapter, :OracleAdapter) + def test_migrate_revert_add_index_with_name + RevertNamedIndexMigration1.new.migrate(:up) + RevertNamedIndexMigration2.new.migrate(:up) + RevertNamedIndexMigration2.new.migrate(:down) + + connection = ActiveRecord::Base.connection + assert connection.index_exists?(:horses, :content), + "index on content should exist" + assert_not connection.index_exists?(:horses, :content, name: "horses_index_named"), + "horses_index_named index should not exist" + end + end + + def test_up_only + InvertibleMigration.new.migrate(:up) + horse1 = Horse.create + # populates existing horses with oldie = 1 but new ones have default 0 + UpOnlyMigration.new.migrate(:up) + Horse.reset_column_information + horse1.reload + horse2 = Horse.create + + assert 1, horse1.oldie # created before migration + assert 0, horse2.oldie # created after migration + + UpOnlyMigration.new.migrate(:down) # should be no error + connection = ActiveRecord::Base.connection + assert_not connection.column_exists?(:horses, :oldie) + Horse.reset_column_information + end + end +end |