mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
7cc27d749c
This PR moves the `schema_migration` to `migration_context` so that we can access the `schema_migration` per connection. This does not change behavior of the SchemaMigration if you are using one database. This also does not change behavior of any public APIs. `Migrator` is private as is `MigrationContext` so we can change these as needed. We now need to pass a `schema_migration` to `Migrator` so that we can run migrations on the right connection outside the context of a rake task. The bugs this fixes were discovered while debugging the issues around the SchemaCache on initialization with multiple database. It was clear that `get_all_versions` wouldn't work without these changes outside the context of a rake task (because in the rake task we establish a connection and change AR::Base.connection to the db we're running on). Because the `SchemaCache` relies on the `SchemaMigration` information we need to make sure we store it per-connection rather than on ActiveRecord::Base. [Eileen M. Uchitelle & Aaron Patterson]
527 lines
17 KiB
Ruby
527 lines
17 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "cases/helper"
|
|
require "cases/migration/helper"
|
|
|
|
class MigratorTest < ActiveRecord::TestCase
|
|
self.use_transactional_tests = false
|
|
|
|
# Use this class to sense if migrations have gone
|
|
# up or down.
|
|
class Sensor < ActiveRecord::Migration::Current
|
|
attr_reader :went_up, :went_down
|
|
|
|
def initialize(name = self.class.name, version = nil)
|
|
super
|
|
@went_up = false
|
|
@went_down = false
|
|
end
|
|
|
|
def up; @went_up = true; end
|
|
def down; @went_down = true; end
|
|
end
|
|
|
|
def setup
|
|
super
|
|
@schema_migration = ActiveRecord::Base.connection.schema_migration
|
|
@schema_migration.create_table
|
|
@schema_migration.delete_all rescue nil
|
|
@verbose_was = ActiveRecord::Migration.verbose
|
|
ActiveRecord::Migration.message_count = 0
|
|
ActiveRecord::Migration.class_eval do
|
|
undef :puts
|
|
def puts(*)
|
|
ActiveRecord::Migration.message_count += 1
|
|
end
|
|
end
|
|
end
|
|
|
|
teardown do
|
|
@schema_migration.delete_all rescue nil
|
|
ActiveRecord::Migration.verbose = @verbose_was
|
|
ActiveRecord::Migration.class_eval do
|
|
undef :puts
|
|
def puts(*)
|
|
super
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_migrator_with_duplicate_names
|
|
e = assert_raises(ActiveRecord::DuplicateMigrationNameError) do
|
|
list = [ActiveRecord::Migration.new("Chunky"), ActiveRecord::Migration.new("Chunky")]
|
|
ActiveRecord::Migrator.new(:up, list, @schema_migration)
|
|
end
|
|
assert_match(/Multiple migrations have the name Chunky/, e.message)
|
|
end
|
|
|
|
def test_migrator_with_duplicate_versions
|
|
assert_raises(ActiveRecord::DuplicateMigrationVersionError) do
|
|
list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 1)]
|
|
ActiveRecord::Migrator.new(:up, list, @schema_migration)
|
|
end
|
|
end
|
|
|
|
def test_migrator_with_missing_version_numbers
|
|
assert_raises(ActiveRecord::UnknownMigrationVersionError) do
|
|
list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 2)]
|
|
ActiveRecord::Migrator.new(:up, list, @schema_migration, 3).run
|
|
end
|
|
|
|
assert_raises(ActiveRecord::UnknownMigrationVersionError) do
|
|
list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 2)]
|
|
ActiveRecord::Migrator.new(:up, list, @schema_migration, -1).run
|
|
end
|
|
|
|
assert_raises(ActiveRecord::UnknownMigrationVersionError) do
|
|
list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 2)]
|
|
ActiveRecord::Migrator.new(:up, list, @schema_migration, 0).run
|
|
end
|
|
|
|
assert_raises(ActiveRecord::UnknownMigrationVersionError) do
|
|
list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 2)]
|
|
ActiveRecord::Migrator.new(:up, list, @schema_migration, 3).migrate
|
|
end
|
|
|
|
assert_raises(ActiveRecord::UnknownMigrationVersionError) do
|
|
list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 2)]
|
|
ActiveRecord::Migrator.new(:up, list, @schema_migration, -1).migrate
|
|
end
|
|
end
|
|
|
|
def test_finds_migrations
|
|
schema_migration = ActiveRecord::Base.connection.schema_migration
|
|
migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid", schema_migration).migrations
|
|
|
|
[[1, "ValidPeopleHaveLastNames"], [2, "WeNeedReminders"], [3, "InnocentJointable"]].each_with_index do |pair, i|
|
|
assert_equal migrations[i].version, pair.first
|
|
assert_equal migrations[i].name, pair.last
|
|
end
|
|
end
|
|
|
|
def test_finds_migrations_in_subdirectories
|
|
schema_migration = ActiveRecord::Base.connection.schema_migration
|
|
migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid_with_subdirectories", schema_migration).migrations
|
|
|
|
[[1, "ValidPeopleHaveLastNames"], [2, "WeNeedReminders"], [3, "InnocentJointable"]].each_with_index do |pair, i|
|
|
assert_equal migrations[i].version, pair.first
|
|
assert_equal migrations[i].name, pair.last
|
|
end
|
|
end
|
|
|
|
def test_finds_migrations_from_two_directories
|
|
schema_migration = ActiveRecord::Base.connection.schema_migration
|
|
directories = [MIGRATIONS_ROOT + "/valid_with_timestamps", MIGRATIONS_ROOT + "/to_copy_with_timestamps"]
|
|
migrations = ActiveRecord::MigrationContext.new(directories, schema_migration).migrations
|
|
|
|
[[20090101010101, "PeopleHaveHobbies"],
|
|
[20090101010202, "PeopleHaveDescriptions"],
|
|
[20100101010101, "ValidWithTimestampsPeopleHaveLastNames"],
|
|
[20100201010101, "ValidWithTimestampsWeNeedReminders"],
|
|
[20100301010101, "ValidWithTimestampsInnocentJointable"]].each_with_index do |pair, i|
|
|
assert_equal pair.first, migrations[i].version
|
|
assert_equal pair.last, migrations[i].name
|
|
end
|
|
end
|
|
|
|
def test_finds_migrations_in_numbered_directory
|
|
schema_migration = ActiveRecord::Base.connection.schema_migration
|
|
migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/10_urban", schema_migration).migrations
|
|
assert_equal 9, migrations[0].version
|
|
assert_equal "AddExpressions", migrations[0].name
|
|
end
|
|
|
|
def test_relative_migrations
|
|
schema_migration = ActiveRecord::Base.connection.schema_migration
|
|
list = Dir.chdir(MIGRATIONS_ROOT) do
|
|
ActiveRecord::MigrationContext.new("valid", schema_migration).migrations
|
|
end
|
|
|
|
migration_proxy = list.find { |item|
|
|
item.name == "ValidPeopleHaveLastNames"
|
|
}
|
|
assert migration_proxy, "should find pending migration"
|
|
end
|
|
|
|
def test_finds_pending_migrations
|
|
@schema_migration.create!(version: "1")
|
|
migration_list = [ActiveRecord::Migration.new("foo", 1), ActiveRecord::Migration.new("bar", 3)]
|
|
migrations = ActiveRecord::Migrator.new(:up, migration_list, @schema_migration).pending_migrations
|
|
|
|
assert_equal 1, migrations.size
|
|
assert_equal migration_list.last, migrations.first
|
|
end
|
|
|
|
def test_migrations_status
|
|
path = MIGRATIONS_ROOT + "/valid"
|
|
schema_migration = ActiveRecord::Base.connection.schema_migration
|
|
|
|
@schema_migration.create(version: 2)
|
|
@schema_migration.create(version: 10)
|
|
|
|
assert_equal [
|
|
["down", "001", "Valid people have last names"],
|
|
["up", "002", "We need reminders"],
|
|
["down", "003", "Innocent jointable"],
|
|
["up", "010", "********** NO FILE **********"],
|
|
], ActiveRecord::MigrationContext.new(path, schema_migration).migrations_status
|
|
end
|
|
|
|
def test_migrations_status_in_subdirectories
|
|
path = MIGRATIONS_ROOT + "/valid_with_subdirectories"
|
|
schema_migration = ActiveRecord::Base.connection.schema_migration
|
|
|
|
@schema_migration.create(version: 2)
|
|
@schema_migration.create(version: 10)
|
|
|
|
assert_equal [
|
|
["down", "001", "Valid people have last names"],
|
|
["up", "002", "We need reminders"],
|
|
["down", "003", "Innocent jointable"],
|
|
["up", "010", "********** NO FILE **********"],
|
|
], ActiveRecord::MigrationContext.new(path, schema_migration).migrations_status
|
|
end
|
|
|
|
def test_migrations_status_with_schema_define_in_subdirectories
|
|
path = MIGRATIONS_ROOT + "/valid_with_subdirectories"
|
|
prev_paths = ActiveRecord::Migrator.migrations_paths
|
|
schema_migration = ActiveRecord::Base.connection.schema_migration
|
|
ActiveRecord::Migrator.migrations_paths = path
|
|
|
|
ActiveRecord::Schema.define(version: 3) do
|
|
end
|
|
|
|
assert_equal [
|
|
["up", "001", "Valid people have last names"],
|
|
["up", "002", "We need reminders"],
|
|
["up", "003", "Innocent jointable"],
|
|
], ActiveRecord::MigrationContext.new(path, schema_migration).migrations_status
|
|
ensure
|
|
ActiveRecord::Migrator.migrations_paths = prev_paths
|
|
end
|
|
|
|
def test_migrations_status_from_two_directories
|
|
paths = [MIGRATIONS_ROOT + "/valid_with_timestamps", MIGRATIONS_ROOT + "/to_copy_with_timestamps"]
|
|
schema_migration = ActiveRecord::Base.connection.schema_migration
|
|
|
|
@schema_migration.create(version: "20100101010101")
|
|
@schema_migration.create(version: "20160528010101")
|
|
|
|
assert_equal [
|
|
["down", "20090101010101", "People have hobbies"],
|
|
["down", "20090101010202", "People have descriptions"],
|
|
["up", "20100101010101", "Valid with timestamps people have last names"],
|
|
["down", "20100201010101", "Valid with timestamps we need reminders"],
|
|
["down", "20100301010101", "Valid with timestamps innocent jointable"],
|
|
["up", "20160528010101", "********** NO FILE **********"],
|
|
], ActiveRecord::MigrationContext.new(paths, schema_migration).migrations_status
|
|
end
|
|
|
|
def test_migrator_interleaved_migrations
|
|
pass_one = [Sensor.new("One", 1)]
|
|
|
|
ActiveRecord::Migrator.new(:up, pass_one, @schema_migration).migrate
|
|
assert pass_one.first.went_up
|
|
assert_not pass_one.first.went_down
|
|
|
|
pass_two = [Sensor.new("One", 1), Sensor.new("Three", 3)]
|
|
ActiveRecord::Migrator.new(:up, pass_two, @schema_migration).migrate
|
|
assert_not pass_two[0].went_up
|
|
assert pass_two[1].went_up
|
|
assert pass_two.all? { |x| !x.went_down }
|
|
|
|
pass_three = [Sensor.new("One", 1),
|
|
Sensor.new("Two", 2),
|
|
Sensor.new("Three", 3)]
|
|
|
|
ActiveRecord::Migrator.new(:down, pass_three, @schema_migration).migrate
|
|
assert pass_three[0].went_down
|
|
assert_not pass_three[1].went_down
|
|
assert pass_three[2].went_down
|
|
end
|
|
|
|
def test_up_calls_up
|
|
migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)]
|
|
migrator = ActiveRecord::Migrator.new(:up, migrations, @schema_migration)
|
|
migrator.migrate
|
|
assert migrations.all?(&:went_up)
|
|
assert migrations.all? { |m| !m.went_down }
|
|
assert_equal 2, migrator.current_version
|
|
end
|
|
|
|
def test_down_calls_down
|
|
test_up_calls_up
|
|
|
|
migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)]
|
|
migrator = ActiveRecord::Migrator.new(:down, migrations, @schema_migration)
|
|
migrator.migrate
|
|
assert migrations.all? { |m| !m.went_up }
|
|
assert migrations.all?(&:went_down)
|
|
assert_equal 0, migrator.current_version
|
|
end
|
|
|
|
def test_current_version
|
|
@schema_migration.create!(version: "1000")
|
|
schema_migration = ActiveRecord::Base.connection.schema_migration
|
|
migrator = ActiveRecord::MigrationContext.new("db/migrate", schema_migration)
|
|
assert_equal 1000, migrator.current_version
|
|
end
|
|
|
|
def test_migrator_one_up
|
|
calls, migrations = sensors(3)
|
|
|
|
ActiveRecord::Migrator.new(:up, migrations, @schema_migration, 1).migrate
|
|
assert_equal [[:up, 1]], calls
|
|
calls.clear
|
|
|
|
ActiveRecord::Migrator.new(:up, migrations, @schema_migration, 2).migrate
|
|
assert_equal [[:up, 2]], calls
|
|
end
|
|
|
|
def test_migrator_one_down
|
|
calls, migrations = sensors(3)
|
|
|
|
ActiveRecord::Migrator.new(:up, migrations, @schema_migration).migrate
|
|
assert_equal [[:up, 1], [:up, 2], [:up, 3]], calls
|
|
calls.clear
|
|
|
|
ActiveRecord::Migrator.new(:down, migrations, @schema_migration, 1).migrate
|
|
|
|
assert_equal [[:down, 3], [:down, 2]], calls
|
|
end
|
|
|
|
def test_migrator_one_up_one_down
|
|
calls, migrations = sensors(3)
|
|
|
|
ActiveRecord::Migrator.new(:up, migrations, @schema_migration, 1).migrate
|
|
assert_equal [[:up, 1]], calls
|
|
calls.clear
|
|
|
|
ActiveRecord::Migrator.new(:down, migrations, @schema_migration, 0).migrate
|
|
assert_equal [[:down, 1]], calls
|
|
end
|
|
|
|
def test_migrator_double_up
|
|
calls, migrations = sensors(3)
|
|
migrator = ActiveRecord::Migrator.new(:up, migrations, @schema_migration, 1)
|
|
assert_equal(0, migrator.current_version)
|
|
|
|
migrator.migrate
|
|
assert_equal [[:up, 1]], calls
|
|
calls.clear
|
|
|
|
migrator.migrate
|
|
assert_equal [], calls
|
|
end
|
|
|
|
def test_migrator_double_down
|
|
calls, migrations = sensors(3)
|
|
migrator = ActiveRecord::Migrator.new(:up, migrations, @schema_migration, 1)
|
|
|
|
assert_equal 0, migrator.current_version
|
|
|
|
migrator.run
|
|
assert_equal [[:up, 1]], calls
|
|
calls.clear
|
|
|
|
migrator = ActiveRecord::Migrator.new(:down, migrations, @schema_migration, 1)
|
|
migrator.run
|
|
assert_equal [[:down, 1]], calls
|
|
calls.clear
|
|
|
|
migrator.run
|
|
assert_equal [], calls
|
|
|
|
assert_equal 0, migrator.current_version
|
|
end
|
|
|
|
def test_migrator_verbosity
|
|
_, migrations = sensors(3)
|
|
|
|
ActiveRecord::Migration.verbose = true
|
|
ActiveRecord::Migrator.new(:up, migrations, @schema_migration, 1).migrate
|
|
assert_not_equal 0, ActiveRecord::Migration.message_count
|
|
|
|
ActiveRecord::Migration.message_count = 0
|
|
|
|
ActiveRecord::Migrator.new(:down, migrations, @schema_migration, 0).migrate
|
|
assert_not_equal 0, ActiveRecord::Migration.message_count
|
|
end
|
|
|
|
def test_migrator_verbosity_off
|
|
_, migrations = sensors(3)
|
|
|
|
ActiveRecord::Migration.verbose = false
|
|
ActiveRecord::Migrator.new(:up, migrations, @schema_migration, 1).migrate
|
|
assert_equal 0, ActiveRecord::Migration.message_count
|
|
ActiveRecord::Migrator.new(:down, migrations, @schema_migration, 0).migrate
|
|
assert_equal 0, ActiveRecord::Migration.message_count
|
|
end
|
|
|
|
def test_target_version_zero_should_run_only_once
|
|
calls, migrations = sensors(3)
|
|
|
|
# migrate up to 1
|
|
ActiveRecord::Migrator.new(:up, migrations, @schema_migration, 1).migrate
|
|
assert_equal [[:up, 1]], calls
|
|
calls.clear
|
|
|
|
# migrate down to 0
|
|
ActiveRecord::Migrator.new(:down, migrations, @schema_migration, 0).migrate
|
|
assert_equal [[:down, 1]], calls
|
|
calls.clear
|
|
|
|
# migrate down to 0 again
|
|
ActiveRecord::Migrator.new(:down, migrations, @schema_migration, 0).migrate
|
|
assert_equal [], calls
|
|
end
|
|
|
|
def test_migrator_going_down_due_to_version_target
|
|
schema_migration = ActiveRecord::Base.connection.schema_migration
|
|
calls, migrator = migrator_class(3)
|
|
migrator = migrator.new("valid", schema_migration)
|
|
|
|
migrator.up(1)
|
|
assert_equal [[:up, 1]], calls
|
|
calls.clear
|
|
|
|
migrator.migrate(0)
|
|
assert_equal [[:down, 1]], calls
|
|
calls.clear
|
|
|
|
migrator.migrate
|
|
assert_equal [[:up, 1], [:up, 2], [:up, 3]], calls
|
|
end
|
|
|
|
def test_migrator_output_when_running_multiple_migrations
|
|
schema_migration = ActiveRecord::Base.connection.schema_migration
|
|
_, migrator = migrator_class(3)
|
|
migrator = migrator.new("valid", schema_migration)
|
|
|
|
result = migrator.migrate
|
|
assert_equal(3, result.count)
|
|
|
|
# Nothing migrated from duplicate run
|
|
result = migrator.migrate
|
|
assert_equal(0, result.count)
|
|
|
|
result = migrator.rollback
|
|
assert_equal(1, result.count)
|
|
end
|
|
|
|
def test_migrator_output_when_running_single_migration
|
|
schema_migration = ActiveRecord::Base.connection.schema_migration
|
|
_, migrator = migrator_class(1)
|
|
migrator = migrator.new("valid", schema_migration)
|
|
|
|
result = migrator.run(:up, 1)
|
|
|
|
assert_equal(1, result.version)
|
|
end
|
|
|
|
def test_migrator_rollback
|
|
schema_migration = ActiveRecord::Base.connection.schema_migration
|
|
_, migrator = migrator_class(3)
|
|
migrator = migrator.new("valid", schema_migration)
|
|
|
|
migrator.migrate
|
|
assert_equal(3, migrator.current_version)
|
|
|
|
migrator.rollback
|
|
assert_equal(2, migrator.current_version)
|
|
|
|
migrator.rollback
|
|
assert_equal(1, migrator.current_version)
|
|
|
|
migrator.rollback
|
|
assert_equal(0, migrator.current_version)
|
|
|
|
migrator.rollback
|
|
assert_equal(0, migrator.current_version)
|
|
end
|
|
|
|
def test_migrator_db_has_no_schema_migrations_table
|
|
schema_migration = ActiveRecord::Base.connection.schema_migration
|
|
_, migrator = migrator_class(3)
|
|
migrator = migrator.new("valid", schema_migration)
|
|
|
|
ActiveRecord::SchemaMigration.drop_table
|
|
assert_not_predicate ActiveRecord::SchemaMigration, :table_exists?
|
|
migrator.migrate(1)
|
|
assert_predicate ActiveRecord::SchemaMigration, :table_exists?
|
|
end
|
|
|
|
def test_migrator_forward
|
|
schema_migration = ActiveRecord::Base.connection.schema_migration
|
|
_, migrator = migrator_class(3)
|
|
migrator = migrator.new("/valid", schema_migration)
|
|
migrator.migrate(1)
|
|
assert_equal(1, migrator.current_version)
|
|
|
|
migrator.forward(2)
|
|
assert_equal(3, migrator.current_version)
|
|
|
|
migrator.forward
|
|
assert_equal(3, migrator.current_version)
|
|
end
|
|
|
|
def test_only_loads_pending_migrations
|
|
# migrate up to 1
|
|
@schema_migration.create!(version: "1")
|
|
|
|
schema_migration = ActiveRecord::Base.connection.schema_migration
|
|
calls, migrator = migrator_class(3)
|
|
migrator = migrator.new("valid", schema_migration)
|
|
migrator.migrate
|
|
|
|
assert_equal [[:up, 2], [:up, 3]], calls
|
|
end
|
|
|
|
def test_get_all_versions
|
|
schema_migration = ActiveRecord::Base.connection.schema_migration
|
|
_, migrator = migrator_class(3)
|
|
migrator = migrator.new("valid", schema_migration)
|
|
|
|
migrator.migrate
|
|
assert_equal([1, 2, 3], migrator.get_all_versions)
|
|
|
|
migrator.rollback
|
|
assert_equal([1, 2], migrator.get_all_versions)
|
|
|
|
migrator.rollback
|
|
assert_equal([1], migrator.get_all_versions)
|
|
|
|
migrator.rollback
|
|
assert_equal([], migrator.get_all_versions)
|
|
end
|
|
|
|
private
|
|
def m(name, version)
|
|
x = Sensor.new name, version
|
|
x.extend(Module.new {
|
|
define_method(:up) { yield(:up, x); super() }
|
|
define_method(:down) { yield(:down, x); super() }
|
|
}) if block_given?
|
|
end
|
|
|
|
def sensors(count)
|
|
calls = []
|
|
migrations = count.times.map { |i|
|
|
m(nil, i + 1) { |c, migration|
|
|
calls << [c, migration.version]
|
|
}
|
|
}
|
|
[calls, migrations]
|
|
end
|
|
|
|
def migrator_class(count)
|
|
calls, migrations = sensors(count)
|
|
|
|
migrator = Class.new(ActiveRecord::MigrationContext) {
|
|
define_method(:migrations) { |*|
|
|
migrations
|
|
}
|
|
}
|
|
[calls, migrator]
|
|
end
|
|
end
|