From a2827ec9811b5012e8e366011fd44c8eb53fc714 Mon Sep 17 00:00:00 2001
From: eileencodes <eileencodes@gmail.com>
Date: Wed, 10 Jan 2018 10:25:13 -0500
Subject: Refactor migration to move migrations paths to connection

Rails has some support for multiple databases but it can be hard to
handle migrations with those. The easiest way to implement multiple
databases is to contain migrations into their own folder ("db/migrate"
for the primary db and "db/seconddb_migrate" for the second db). Without
this you would need to write code that allowed you to switch connections
in migrations. I can tell you from experience that is not a fun way to
implement multiple databases.

This refactoring is a pre-requisite for implementing other features
related to parallel testing and improved handling for multiple
databases.

The refactoring here moves the class methods from the `Migrator` class
into it's own new class `MigrationContext`. The goal was to move the
`migrations_paths` method off of the `Migrator` class and onto the
connection. This allows users to do the following in their
`database.yml`:

```
development:
  adapter: mysql2
  username: root
  password:

development_seconddb:
  adapter: mysql2
  username: root
  password:
  migrations_paths: "db/second_db_migrate"
```

Migrations for the `seconddb` can now be store in the
`db/second_db_migrate` directory. Migrations for the primary database
are stored in `db/migrate`".

The refactoring here drastically reduces the internal API for migrations
since we don't need to pass `migrations_paths` around to every single
method. Additionally this change does not require any Rails applications
to make changes unless they want to use the new public API. All of the
class methods from the `Migrator` class were `nodoc`'d except for the
`migrations_paths` and `migrations_path` getter/setters respectively.
---
 .../abstract/schema_statements.rb                  |   4 +-
 .../connection_adapters/abstract_adapter.rb        |   8 +
 .../connection_adapters/schema_cache.rb            |   4 +-
 activerecord/lib/active_record/migration.rb        | 256 +++++++++++----------
 activerecord/lib/active_record/railtie.rb          |   1 +
 .../lib/active_record/railties/databases.rake      |  19 +-
 activerecord/lib/active_record/schema.rb           |   2 +-
 activerecord/lib/active_record/schema_dumper.rb    |   2 +-
 .../lib/active_record/tasks/database_tasks.rb      |  12 +-
 .../adapters/mysql2/schema_migrations_test.rb      |   2 +-
 activerecord/test/cases/ar_schema_test.rb          |   4 +-
 .../cases/migration/pending_migrations_test.rb     |  50 ++--
 activerecord/test/cases/migration_test.rb          |  71 +++---
 activerecord/test/cases/migrator_test.rb           | 148 ++++++------
 .../test/cases/tasks/database_tasks_test.rb        | 124 ++++++----
 activestorage/test/database/setup.rb               |   2 +-
 railties/lib/rails/info.rb                         |   2 +-
 railties/test/application/rake/migrations_test.rb  |  12 +
 railties/test/railties/engine_test.rb              |   2 +-
 19 files changed, 417 insertions(+), 308 deletions(-)

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 4f58b0242c..c32a234be4 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -1049,8 +1049,8 @@ module ActiveRecord
         sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
 
         migrated = ActiveRecord::SchemaMigration.all_versions.map(&:to_i)
-        versions = ActiveRecord::Migrator.migration_files(migrations_paths).map do |file|
-          ActiveRecord::Migrator.parse_migration_filename(file).first.to_i
+        versions = migration_context.migration_files.map do |file|
+          migration_context.parse_migration_filename(file).first.to_i
         end
 
         unless migrated.include?(version)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 2655407d75..7bd54f777e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -119,6 +119,14 @@ module ActiveRecord
         end
       end
 
+      def migrations_paths
+        @config[:migrations_paths] || Migrator.migrations_paths
+      end
+
+      def migration_context
+        MigrationContext.new(migrations_paths)
+      end
+
       class Version
         include Comparable
 
diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
index f34b6733da..c29cf1f9a1 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -28,7 +28,7 @@ module ActiveRecord
         coder["columns_hash"] = @columns_hash
         coder["primary_keys"] = @primary_keys
         coder["data_sources"] = @data_sources
-        coder["version"] = ActiveRecord::Migrator.current_version
+        coder["version"] = connection.migration_context.current_version
       end
 
       def init_with(coder)
@@ -100,7 +100,7 @@ module ActiveRecord
 
       def marshal_dump
         # if we get current version during initialization, it happens stack over flow.
-        @version = ActiveRecord::Migrator.current_version
+        @version = connection.migration_context.current_version
         [@version, @columns, @columns_hash, @primary_keys, @data_sources]
       end
 
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index f6648a4e3d..4e36e49905 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -3,6 +3,7 @@
 require "set"
 require "zlib"
 require "active_support/core_ext/module/attribute_accessors"
+require "active_record/tasks/database_tasks"
 
 module ActiveRecord
   class MigrationError < ActiveRecordError#:nodoc:
@@ -550,7 +551,7 @@ module ActiveRecord
       end
 
       def call(env)
-        mtime = ActiveRecord::Migrator.last_migration.mtime.to_i
+        mtime = ActiveRecord::Base.connection.migration_context.last_migration.mtime.to_i
         if @last_check < mtime
           ActiveRecord::Migration.check_pending!(connection)
           @last_check = mtime
@@ -575,11 +576,11 @@ module ActiveRecord
 
       # Raises <tt>ActiveRecord::PendingMigrationError</tt> error if any migrations are pending.
       def check_pending!(connection = Base.connection)
-        raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration?(connection)
+        raise ActiveRecord::PendingMigrationError if connection.migration_context.needs_migration?
       end
 
       def load_schema_if_pending!
-        if ActiveRecord::Migrator.needs_migration? || !ActiveRecord::Migrator.any_migrations?
+        if Base.connection.migration_context.needs_migration? || !Base.connection.migration_context.any_migrations?
           # Roundtrip to Rake to allow plugins to hook into database initialization.
           root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root
           FileUtils.cd(root) do
@@ -876,10 +877,10 @@ module ActiveRecord
 
       FileUtils.mkdir_p(destination) unless File.exist?(destination)
 
-      destination_migrations = ActiveRecord::Migrator.migrations(destination)
+      destination_migrations = ActiveRecord::MigrationContext.new(destination).migrations
       last = destination_migrations.last
       sources.each do |scope, path|
-        source_migrations = ActiveRecord::Migrator.migrations(path)
+        source_migrations = ActiveRecord::MigrationContext.new(path).migrations
 
         source_migrations.each do |migration|
           source = File.binread(migration.filename)
@@ -997,132 +998,147 @@ module ActiveRecord
     end
   end
 
-  class Migrator#:nodoc:
-    class << self
-      attr_writer :migrations_paths
-      alias :migrations_path= :migrations_paths=
-
-      def migrate(migrations_paths, target_version = nil, &block)
-        case
-        when target_version.nil?
-          up(migrations_paths, target_version, &block)
-        when current_version == 0 && target_version == 0
-          []
-        when current_version > target_version
-          down(migrations_paths, target_version, &block)
-        else
-          up(migrations_paths, target_version, &block)
-        end
-      end
+  class MigrationContext # :nodoc:
+    attr_reader :migrations_paths
 
-      def rollback(migrations_paths, steps = 1)
-        move(:down, migrations_paths, steps)
-      end
+    def initialize(migrations_paths)
+      @migrations_paths = migrations_paths
+    end
 
-      def forward(migrations_paths, steps = 1)
-        move(:up, migrations_paths, steps)
+    def migrate(target_version = nil, &block)
+      case
+      when target_version.nil?
+        up(target_version, &block)
+      when current_version == 0 && target_version == 0
+        []
+      when current_version > target_version
+        down(target_version, &block)
+      else
+        up(target_version, &block)
       end
+    end
 
-      def up(migrations_paths, target_version = nil)
-        migrations = migrations(migrations_paths)
-        migrations.select! { |m| yield m } if block_given?
+    def rollback(steps = 1)
+      move(:down, steps)
+    end
 
-        new(:up, migrations, target_version).migrate
+    def forward(steps = 1)
+      move(:up, steps)
+    end
+
+    def up(target_version = nil)
+      selected_migrations = if block_given?
+        migrations.select { |m| yield m }
+      else
+        migrations
       end
 
-      def down(migrations_paths, target_version = nil)
-        migrations = migrations(migrations_paths)
-        migrations.select! { |m| yield m } if block_given?
+      Migrator.new(:up, selected_migrations, target_version).migrate
+    end
 
-        new(:down, migrations, target_version).migrate
+    def down(target_version = nil)
+      selected_migrations = if block_given?
+        migrations.select { |m| yield m }
+      else
+        migrations
       end
 
-      def run(direction, migrations_paths, target_version)
-        new(direction, migrations(migrations_paths), target_version).run
-      end
+      Migrator.new(:down, migrations, target_version).migrate
+    end
 
-      def open(migrations_paths)
-        new(:up, migrations(migrations_paths), nil)
-      end
+    def run(direction, target_version)
+      Migrator.new(direction, migrations, target_version).run
+    end
 
-      def get_all_versions
-        if SchemaMigration.table_exists?
-          SchemaMigration.all_versions.map(&:to_i)
-        else
-          []
-        end
-      end
+    def open
+      Migrator.new(:up, migrations, nil)
+    end
 
-      def current_version(connection = nil)
-        get_all_versions.max || 0
-      rescue ActiveRecord::NoDatabaseError
+    def get_all_versions
+      if SchemaMigration.table_exists?
+        SchemaMigration.all_versions.map(&:to_i)
+      else
+        []
       end
+    end
 
-      def needs_migration?(connection = nil)
-        (migrations(migrations_paths).collect(&:version) - get_all_versions).size > 0
-      end
+    def current_version(connection = nil)
+      get_all_versions.max || 0
+    rescue ActiveRecord::NoDatabaseError
+    end
 
-      def any_migrations?
-        migrations(migrations_paths).any?
-      end
+    def needs_migration?
+      (migrations.collect(&:version) - get_all_versions).size > 0
+    end
 
-      def last_migration #:nodoc:
-        migrations(migrations_paths).last || NullMigration.new
-      end
+    def any_migrations?
+      migrations.any?
+    end
 
-      def migrations_paths
-        @migrations_paths ||= ["db/migrate"]
-        # just to not break things if someone uses: migrations_path = some_string
-        Array(@migrations_paths)
-      end
+    def last_migration #:nodoc:
+      migrations.last || NullMigration.new
+    end
 
-      def parse_migration_filename(filename) # :nodoc:
-        File.basename(filename).scan(Migration::MigrationFilenameRegexp).first
+    def parse_migration_filename(filename) # :nodoc:
+      File.basename(filename).scan(Migration::MigrationFilenameRegexp).first
+    end
+
+    def migrations
+      migrations = migration_files.map do |file|
+        version, name, scope = parse_migration_filename(file)
+        raise IllegalMigrationNameError.new(file) unless version
+        version = version.to_i
+        name = name.camelize
+
+        MigrationProxy.new(name, version, file, scope)
       end
 
-      def migrations(paths)
-        paths = Array(paths)
+      migrations.sort_by(&:version)
+    end
 
-        migrations = migration_files(paths).map do |file|
-          version, name, scope = parse_migration_filename(file)
-          raise IllegalMigrationNameError.new(file) unless version
-          version = version.to_i
-          name = name.camelize
+    def migrations_status
+      db_list = ActiveRecord::SchemaMigration.normalized_versions
 
-          MigrationProxy.new(name, version, file, scope)
-        end
+      file_list = migration_files.map do |file|
+        version, name, scope = parse_migration_filename(file)
+        raise IllegalMigrationNameError.new(file) unless version
+        version = ActiveRecord::SchemaMigration.normalize_migration_number(version)
+        status = db_list.delete(version) ? "up" : "down"
+        [status, version, (name + scope).humanize]
+      end.compact
 
-        migrations.sort_by(&:version)
+      db_list.map! do |version|
+        ["up", version, "********** NO FILE **********"]
       end
 
-      def migrations_status(paths)
-        paths = Array(paths)
-
-        db_list = ActiveRecord::SchemaMigration.normalized_versions
+      (db_list + file_list).sort_by { |_, version, _| version }
+    end
 
-        file_list = migration_files(paths).map do |file|
-          version, name, scope = parse_migration_filename(file)
-          raise IllegalMigrationNameError.new(file) unless version
-          version = ActiveRecord::SchemaMigration.normalize_migration_number(version)
-          status = db_list.delete(version) ? "up" : "down"
-          [status, version, (name + scope).humanize]
-        end.compact
+    def migration_files
+      paths = Array(migrations_paths)
+      Dir[*paths.flat_map { |path| "#{path}/**/[0-9]*_*.rb" }]
+    end
 
-        db_list.map! do |version|
-          ["up", version, "********** NO FILE **********"]
-        end
+    def current_environment
+      ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+    end
 
-        (db_list + file_list).sort_by { |_, version, _| version }
-      end
+    def protected_environment?
+      ActiveRecord::Base.protected_environments.include?(last_stored_environment) if last_stored_environment
+    end
 
-      def migration_files(paths)
-        Dir[*paths.flat_map { |path| "#{path}/**/[0-9]*_*.rb" }]
-      end
+    def last_stored_environment
+      return nil if current_version == 0
+      raise NoEnvironmentInSchemaError unless ActiveRecord::InternalMetadata.table_exists?
 
-      private
+      environment = ActiveRecord::InternalMetadata[:environment]
+      raise NoEnvironmentInSchemaError unless environment
+      environment
+    end
 
-      def move(direction, migrations_paths, steps)
-        migrator = new(direction, migrations(migrations_paths))
+    private
+      def move(direction, steps)
+        migrator = Migrator.new(direction, migrations)
 
         if current_version != 0 && !migrator.current_migration
           raise UnknownMigrationVersionError.new(current_version)
@@ -1137,10 +1153,29 @@ module ActiveRecord
 
         finish = migrator.migrations[start_index + steps]
         version = finish ? finish.version : 0
-        send(direction, migrations_paths, version)
+        send(direction, version)
+      end
+  end
+
+  class Migrator # :nodoc:
+    class << self
+      attr_accessor :migrations_paths
+
+      def migrations_path=(path)
+        ActiveSupport::Deprecation.warn \
+          "ActiveRecord::Migrator.migrations_paths= is now deprecated and will be removed in Rails 6.0." \
+          "You can set the `migrations_paths` on the `connection` instead through the `database.yml`."
+        self.migrations_paths = [path]
+      end
+
+      # For cases where a table doesn't exist like loading from schema cache
+      def current_version(connection = nil)
+        MigrationContext.new(migrations_paths).current_version(connection)
       end
     end
 
+    self.migrations_paths = ["db/migrate"]
+
     def initialize(direction, migrations, target_version = nil)
       @direction         = direction
       @target_version    = target_version
@@ -1203,7 +1238,7 @@ module ActiveRecord
     end
 
     def load_migrated
-      @migrated_versions = Set.new(self.class.get_all_versions)
+      @migrated_versions = Set.new(Base.connection.migration_context.get_all_versions)
     end
 
     private
@@ -1235,7 +1270,7 @@ module ActiveRecord
       # Stores the current environment in the database.
       def record_environment
         return if down?
-        ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
+        ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Base.connection.migration_context.current_environment
       end
 
       def ran?(migration)
@@ -1294,23 +1329,6 @@ module ActiveRecord
         end
       end
 
-      def self.last_stored_environment
-        return nil if current_version == 0
-        raise NoEnvironmentInSchemaError unless ActiveRecord::InternalMetadata.table_exists?
-
-        environment = ActiveRecord::InternalMetadata[:environment]
-        raise NoEnvironmentInSchemaError unless environment
-        environment
-      end
-
-      def self.current_environment
-        ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
-      end
-
-      def self.protected_environment?
-        ActiveRecord::Base.protected_environments.include?(last_stored_environment) if last_stored_environment
-      end
-
       def up?
         @direction == :up
       end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 4538ed6a5f..7b4b59ac4b 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -91,6 +91,7 @@ module ActiveRecord
 
             if File.file?(filename)
               current_version = ActiveRecord::Migrator.current_version
+
               next if current_version.nil?
 
               cache = YAML.load(File.read(filename))
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index fce3e1c5cf..2e55713311 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -6,7 +6,7 @@ db_namespace = namespace :db do
   desc "Set the environment value for the database"
   task "environment:set" => :load_config do
     ActiveRecord::InternalMetadata.create_table
-    ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
+    ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Base.connection.migration_context.current_environment
   end
 
   task check_protected_environments: :load_config do
@@ -99,9 +99,8 @@ db_namespace = namespace :db do
 
       ActiveRecord::Tasks::DatabaseTasks.check_target_version
 
-      ActiveRecord::Migrator.run(
+      ActiveRecord::Base.connection.migration_context.run(
         :up,
-        ActiveRecord::Tasks::DatabaseTasks.migrations_paths,
         ActiveRecord::Tasks::DatabaseTasks.target_version
       )
       db_namespace["_dump"].invoke
@@ -113,9 +112,8 @@ db_namespace = namespace :db do
 
       ActiveRecord::Tasks::DatabaseTasks.check_target_version
 
-      ActiveRecord::Migrator.run(
+      ActiveRecord::Base.connection.migration_context.run(
         :down,
-        ActiveRecord::Tasks::DatabaseTasks.migrations_paths,
         ActiveRecord::Tasks::DatabaseTasks.target_version
       )
       db_namespace["_dump"].invoke
@@ -131,8 +129,7 @@ db_namespace = namespace :db do
       puts "\ndatabase: #{ActiveRecord::Base.connection_config[:database]}\n\n"
       puts "#{'Status'.center(8)}  #{'Migration ID'.ljust(14)}  Migration Name"
       puts "-" * 50
-      paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths
-      ActiveRecord::Migrator.migrations_status(paths).each do |status, version, name|
+      ActiveRecord::Base.connection.migration_context.migrations_status.each do |status, version, name|
         puts "#{status.center(8)}  #{version.ljust(14)}  #{name}"
       end
       puts
@@ -142,14 +139,14 @@ db_namespace = namespace :db do
   desc "Rolls the schema back to the previous version (specify steps w/ STEP=n)."
   task rollback: :load_config do
     step = ENV["STEP"] ? ENV["STEP"].to_i : 1
-    ActiveRecord::Migrator.rollback(ActiveRecord::Tasks::DatabaseTasks.migrations_paths, step)
+    ActiveRecord::Base.connection.migration_context.rollback(step)
     db_namespace["_dump"].invoke
   end
 
   # desc 'Pushes the schema to the next version (specify steps w/ STEP=n).'
   task forward: :load_config do
     step = ENV["STEP"] ? ENV["STEP"].to_i : 1
-    ActiveRecord::Migrator.forward(ActiveRecord::Tasks::DatabaseTasks.migrations_paths, step)
+    ActiveRecord::Base.connection.migration_context.forward(step)
     db_namespace["_dump"].invoke
   end
 
@@ -172,12 +169,12 @@ db_namespace = namespace :db do
 
   desc "Retrieves the current schema version number"
   task version: :load_config do
-    puts "Current version: #{ActiveRecord::Migrator.current_version}"
+    puts "Current version: #{ActiveRecord::Base.connection.migration_context.current_version}"
   end
 
   # desc "Raises an error if there are pending migrations"
   task abort_if_pending_migrations: :load_config do
-    pending_migrations = ActiveRecord::Migrator.open(ActiveRecord::Tasks::DatabaseTasks.migrations_paths).pending_migrations
+    pending_migrations = ActiveRecord::Base.connection.migration_context.open.pending_migrations
 
     if pending_migrations.any?
       puts "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}"
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index 1e121f2a09..216359867c 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -55,7 +55,7 @@ module ActiveRecord
       end
 
       ActiveRecord::InternalMetadata.create_table
-      ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
+      ActiveRecord::InternalMetadata[:environment] = connection.migration_context.current_environment
     end
 
     private
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 16ccba6b6c..b8d848b999 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -44,7 +44,7 @@ module ActiveRecord
 
       def initialize(connection, options = {})
         @connection = connection
-        @version = Migrator::current_version rescue nil
+        @version = connection.migration_context.current_version rescue nil
         @options = options
       end
 
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 4657e51e6d..d34fbfd4a9 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -54,10 +54,10 @@ module ActiveRecord
 
       def check_protected_environments!
         unless ENV["DISABLE_DATABASE_ENVIRONMENT_CHECK"]
-          current = ActiveRecord::Migrator.current_environment
-          stored  = ActiveRecord::Migrator.last_stored_environment
+          current = ActiveRecord::Base.connection.migration_context.current_environment
+          stored  = ActiveRecord::Base.connection.migration_context.last_stored_environment
 
-          if ActiveRecord::Migrator.protected_environment?
+          if ActiveRecord::Base.connection.migration_context.protected_environment?
             raise ActiveRecord::ProtectedEnvironmentError.new(stored)
           end
 
@@ -85,6 +85,10 @@ module ActiveRecord
         @migrations_paths ||= Rails.application.paths["db/migrate"].to_a
       end
 
+      def migration_context
+        MigrationContext.new(migrations_paths)
+      end
+
       def fixtures_path
         @fixtures_path ||= if ENV["FIXTURES_PATH"]
           File.join(root, ENV["FIXTURES_PATH"])
@@ -169,7 +173,7 @@ module ActiveRecord
         verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] != "false" : true
         scope = ENV["SCOPE"]
         verbose_was, Migration.verbose = Migration.verbose, verbose
-        Migrator.migrate(migrations_paths, target_version) do |migration|
+        Base.connection.migration_context.migrate(target_version) do |migration|
           scope.blank? || scope == migration.scope
         end
         ActiveRecord::Base.clear_cache!
diff --git a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
index 62abd694bb..d7d9a2d732 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
@@ -36,7 +36,7 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase
       assert connection.column_exists?(table_name, :key, :string)
     end
   ensure
-    ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
+    ActiveRecord::InternalMetadata[:environment] = connection.migration_context.current_environment
   end
 
   private
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index 83974f327e..140d7cbcae 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -48,7 +48,7 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
 
     assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" }
     assert_nothing_raised { @connection.select_all "SELECT * FROM schema_migrations" }
-    assert_equal 7, ActiveRecord::Migrator::current_version
+    assert_equal 7, @connection.migration_context.current_version
   end
 
   def test_schema_define_w_table_name_prefix
@@ -64,7 +64,7 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
         t.column :flavor, :string
       end
     end
-    assert_equal 7, ActiveRecord::Migrator::current_version
+    assert_equal 7, @connection.migration_context.current_version
   ensure
     ActiveRecord::Base.table_name_prefix = old_table_name_prefix
     ActiveRecord::SchemaMigration.table_name = table_name
diff --git a/activerecord/test/cases/migration/pending_migrations_test.rb b/activerecord/test/cases/migration/pending_migrations_test.rb
index d0066f68be..dedb5ea502 100644
--- a/activerecord/test/cases/migration/pending_migrations_test.rb
+++ b/activerecord/test/cases/migration/pending_migrations_test.rb
@@ -4,37 +4,37 @@ require "cases/helper"
 
 module ActiveRecord
   class Migration
-    class PendingMigrationsTest < ActiveRecord::TestCase
-      def setup
-        super
-        @connection = Minitest::Mock.new
-        @app = Minitest::Mock.new
-        conn = @connection
-        @pending = Class.new(CheckPending) {
-          define_method(:connection) { conn }
-        }.new(@app)
-        @pending.instance_variable_set :@last_check, -1 # Force checking
-      end
+    if current_adapter?(:SQLite3Adapter) && !in_memory_db?
+      class PendingMigrationsTest < ActiveRecord::TestCase
+        setup do
+          file = ActiveRecord::Base.connection.raw_connection.filename
+          @conn = ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:", migrations_paths: MIGRATIONS_ROOT + "/valid"
+          source_db = SQLite3::Database.new file
+          dest_db = ActiveRecord::Base.connection.raw_connection
+          backup = SQLite3::Backup.new(dest_db, "main", source_db, "main")
+          backup.step(-1)
+          backup.finish
+        end
 
-      def teardown
-        assert @connection.verify
-        assert @app.verify
-        super
-      end
+        teardown do
+          @conn.release_connection if @conn
+          ActiveRecord::Base.establish_connection :arunit
+        end
+
+        def test_errors_if_pending
+          ActiveRecord::Base.connection.drop_table "schema_migrations", if_exists: true
 
-      def test_errors_if_pending
-        ActiveRecord::Migrator.stub :needs_migration?, true do
-          assert_raise ActiveRecord::PendingMigrationError do
-            @pending.call(nil)
+          assert_raises ActiveRecord::PendingMigrationError do
+            CheckPending.new(Proc.new {}).call({})
           end
         end
-      end
 
-      def test_checks_if_supported
-        @app.expect :call, nil, [:foo]
+        def test_checks_if_supported
+          ActiveRecord::SchemaMigration.create_table
+          migrator = Base.connection.migration_context
+          capture(:stdout) { migrator.migrate }
 
-        ActiveRecord::Migrator.stub :needs_migration?, false do
-          @pending.call(:foo)
+          assert_nil CheckPending.new(Proc.new {}).call({})
         end
       end
     end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index a3ebc8070a..ffcf6a588e 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -71,6 +71,16 @@ class MigrationTest < ActiveRecord::TestCase
     ActiveRecord::Migration.verbose = @verbose_was
   end
 
+  def test_migrator_migrations_path_is_deprecated
+    assert_deprecated do
+      ActiveRecord::Migrator.migrations_path = "/whatever"
+    end
+  ensure
+    assert_deprecated do
+      ActiveRecord::Migrator.migrations_path = "db/migrate"
+    end
+  end
+
   def test_migration_version_matches_component_version
     assert_equal ActiveRecord::VERSION::STRING.to_f, ActiveRecord::Migration.current_version
   end
@@ -78,20 +88,20 @@ class MigrationTest < ActiveRecord::TestCase
   def test_migrator_versions
     migrations_path = MIGRATIONS_ROOT + "/valid"
     old_path = ActiveRecord::Migrator.migrations_paths
-    ActiveRecord::Migrator.migrations_paths = migrations_path
+    migrator = ActiveRecord::MigrationContext.new(migrations_path)
 
-    ActiveRecord::Migrator.up(migrations_path)
-    assert_equal 3, ActiveRecord::Migrator.current_version
-    assert_equal false, ActiveRecord::Migrator.needs_migration?
+    migrator.up
+    assert_equal 3, migrator.current_version
+    assert_equal false, migrator.needs_migration?
 
-    ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid")
-    assert_equal 0, ActiveRecord::Migrator.current_version
-    assert_equal true, ActiveRecord::Migrator.needs_migration?
+    migrator.down
+    assert_equal 0, migrator.current_version
+    assert_equal true, migrator.needs_migration?
 
     ActiveRecord::SchemaMigration.create!(version: 3)
-    assert_equal true, ActiveRecord::Migrator.needs_migration?
+    assert_equal true, migrator.needs_migration?
   ensure
-    ActiveRecord::Migrator.migrations_paths = old_path
+    ActiveRecord::MigrationContext.new(old_path)
   end
 
   def test_migration_detection_without_schema_migration_table
@@ -99,28 +109,31 @@ class MigrationTest < ActiveRecord::TestCase
 
     migrations_path = MIGRATIONS_ROOT + "/valid"
     old_path = ActiveRecord::Migrator.migrations_paths
-    ActiveRecord::Migrator.migrations_paths = migrations_path
+    migrator = ActiveRecord::MigrationContext.new(migrations_path)
 
-    assert_equal true, ActiveRecord::Migrator.needs_migration?
+    assert_equal true, migrator.needs_migration?
   ensure
-    ActiveRecord::Migrator.migrations_paths = old_path
+    ActiveRecord::MigrationContext.new(old_path)
   end
 
   def test_any_migrations
     old_path = ActiveRecord::Migrator.migrations_paths
-    ActiveRecord::Migrator.migrations_paths = MIGRATIONS_ROOT + "/valid"
+    migrator = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid")
 
-    assert ActiveRecord::Migrator.any_migrations?
+    assert migrator.any_migrations?
 
-    ActiveRecord::Migrator.migrations_paths = MIGRATIONS_ROOT + "/empty"
+    migrator_empty = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/empty")
 
-    assert_not ActiveRecord::Migrator.any_migrations?
+    assert_not migrator_empty.any_migrations?
   ensure
-    ActiveRecord::Migrator.migrations_paths = old_path
+    ActiveRecord::MigrationContext.new(old_path)
   end
 
   def test_migration_version
-    assert_nothing_raised { ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/version_check", 20131219224947) }
+    migrator = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/version_check")
+    assert_equal 0, migrator.current_version
+    migrator.up(20131219224947)
+    assert_equal 20131219224947, migrator.current_version
   end
 
   def test_create_table_with_force_true_does_not_drop_nonexisting_table
@@ -219,12 +232,13 @@ class MigrationTest < ActiveRecord::TestCase
     assert !Reminder.table_exists?
 
     name_filter = lambda { |migration| migration.name == "ValidPeopleHaveLastNames" }
-    ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", &name_filter)
+    migrator = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid")
+    migrator.up(&name_filter)
 
     assert_column Person, :last_name
     assert_raise(ActiveRecord::StatementInvalid) { Reminder.first }
 
-    ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", &name_filter)
+    migrator.down(&name_filter)
 
     assert_no_column Person, :last_name
     assert_raise(ActiveRecord::StatementInvalid) { Reminder.first }
@@ -382,9 +396,9 @@ class MigrationTest < ActiveRecord::TestCase
     current_env     = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
     migrations_path = MIGRATIONS_ROOT + "/valid"
     old_path        = ActiveRecord::Migrator.migrations_paths
-    ActiveRecord::Migrator.migrations_paths = migrations_path
+    migrator = ActiveRecord::MigrationContext.new(migrations_path)
 
-    ActiveRecord::Migrator.up(migrations_path)
+    migrator.up
     assert_equal current_env, ActiveRecord::InternalMetadata[:environment]
 
     original_rails_env  = ENV["RAILS_ENV"]
@@ -395,13 +409,13 @@ class MigrationTest < ActiveRecord::TestCase
     refute_equal current_env, new_env
 
     sleep 1 # mysql by default does not store fractional seconds in the database
-    ActiveRecord::Migrator.up(migrations_path)
+    migrator.up
     assert_equal new_env, ActiveRecord::InternalMetadata[:environment]
   ensure
-    ActiveRecord::Migrator.migrations_paths = old_path
+    migrator = ActiveRecord::MigrationContext.new(old_path)
     ENV["RAILS_ENV"] = original_rails_env
     ENV["RACK_ENV"]  = original_rack_env
-    ActiveRecord::Migrator.up(migrations_path)
+    migrator.up
   end
 
   def test_internal_metadata_stores_environment_when_other_data_exists
@@ -411,14 +425,15 @@ class MigrationTest < ActiveRecord::TestCase
     current_env     = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
     migrations_path = MIGRATIONS_ROOT + "/valid"
     old_path        = ActiveRecord::Migrator.migrations_paths
-    ActiveRecord::Migrator.migrations_paths = migrations_path
 
     current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
-    ActiveRecord::Migrator.up(migrations_path)
+    migrator = ActiveRecord::MigrationContext.new(migrations_path)
+    migrator.up
     assert_equal current_env, ActiveRecord::InternalMetadata[:environment]
     assert_equal "bar", ActiveRecord::InternalMetadata[:foo]
   ensure
-    ActiveRecord::Migrator.migrations_paths = old_path
+    migrator = ActiveRecord::MigrationContext.new(old_path)
+    migrator.up
   end
 
   def test_proper_table_name_on_migration
diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb
index 1047ba1367..d674e4506e 100644
--- a/activerecord/test/cases/migrator_test.rb
+++ b/activerecord/test/cases/migrator_test.rb
@@ -89,7 +89,7 @@ class MigratorTest < ActiveRecord::TestCase
   end
 
   def test_finds_migrations
-    migrations = ActiveRecord::Migrator.migrations(MIGRATIONS_ROOT + "/valid")
+    migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid").migrations
 
     [[1, "ValidPeopleHaveLastNames"], [2, "WeNeedReminders"], [3, "InnocentJointable"]].each_with_index do |pair, i|
       assert_equal migrations[i].version, pair.first
@@ -98,7 +98,8 @@ class MigratorTest < ActiveRecord::TestCase
   end
 
   def test_finds_migrations_in_subdirectories
-    migrations = ActiveRecord::Migrator.migrations(MIGRATIONS_ROOT + "/valid_with_subdirectories")
+    migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid_with_subdirectories").migrations
+
 
     [[1, "ValidPeopleHaveLastNames"], [2, "WeNeedReminders"], [3, "InnocentJointable"]].each_with_index do |pair, i|
       assert_equal migrations[i].version, pair.first
@@ -108,7 +109,7 @@ class MigratorTest < ActiveRecord::TestCase
 
   def test_finds_migrations_from_two_directories
     directories = [MIGRATIONS_ROOT + "/valid_with_timestamps", MIGRATIONS_ROOT + "/to_copy_with_timestamps"]
-    migrations = ActiveRecord::Migrator.migrations directories
+    migrations = ActiveRecord::MigrationContext.new(directories).migrations
 
     [[20090101010101, "PeopleHaveHobbies"],
      [20090101010202, "PeopleHaveDescriptions"],
@@ -121,14 +122,14 @@ class MigratorTest < ActiveRecord::TestCase
   end
 
   def test_finds_migrations_in_numbered_directory
-    migrations = ActiveRecord::Migrator.migrations [MIGRATIONS_ROOT + "/10_urban"]
+    migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/10_urban").migrations
     assert_equal 9, migrations[0].version
     assert_equal "AddExpressions", migrations[0].name
   end
 
   def test_relative_migrations
     list = Dir.chdir(MIGRATIONS_ROOT) do
-      ActiveRecord::Migrator.migrations("valid")
+      ActiveRecord::MigrationContext.new("valid").migrations
     end
 
     migration_proxy = list.find { |item|
@@ -157,7 +158,7 @@ class MigratorTest < ActiveRecord::TestCase
       ["up",   "002", "We need reminders"],
       ["down", "003", "Innocent jointable"],
       ["up",   "010", "********** NO FILE **********"],
-    ], ActiveRecord::Migrator.migrations_status(path)
+    ], ActiveRecord::MigrationContext.new(path).migrations_status
   end
 
   def test_migrations_status_in_subdirectories
@@ -171,24 +172,20 @@ class MigratorTest < ActiveRecord::TestCase
       ["up",   "002", "We need reminders"],
       ["down", "003", "Innocent jointable"],
       ["up",   "010", "********** NO FILE **********"],
-    ], ActiveRecord::Migrator.migrations_status(path)
+    ], ActiveRecord::MigrationContext.new(path).migrations_status
   end
 
   def test_migrations_status_with_schema_define_in_subdirectories
-    path = MIGRATIONS_ROOT + "/valid_with_subdirectories"
-    prev_paths = ActiveRecord::Migrator.migrations_paths
-    ActiveRecord::Migrator.migrations_paths = path
+    _, migrator = migrator_class(3)
+    migrator = migrator.new("valid_with_subdirectories")
 
-    ActiveRecord::Schema.define(version: 3) do
-    end
+    migrator.migrate
 
     assert_equal [
-      ["up", "001", "Valid people have last names"],
-      ["up", "002", "We need reminders"],
-      ["up", "003", "Innocent jointable"],
-    ], ActiveRecord::Migrator.migrations_status(path)
-  ensure
-    ActiveRecord::Migrator.migrations_paths = prev_paths
+      ["up", "001", "********** NO FILE **********"],
+      ["up", "002", "********** NO FILE **********"],
+      ["up", "003", "********** NO FILE **********"],
+    ], migrator.migrations_status
   end
 
   def test_migrations_status_from_two_directories
@@ -204,7 +201,7 @@ class MigratorTest < ActiveRecord::TestCase
       ["down", "20100201010101", "Valid with timestamps we need reminders"],
       ["down", "20100301010101", "Valid with timestamps innocent jointable"],
       ["up",   "20160528010101", "********** NO FILE **********"],
-    ], ActiveRecord::Migrator.migrations_status(paths)
+    ], ActiveRecord::MigrationContext.new(paths).migrations_status
   end
 
   def test_migrator_interleaved_migrations
@@ -232,25 +229,28 @@ class MigratorTest < ActiveRecord::TestCase
 
   def test_up_calls_up
     migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)]
-    ActiveRecord::Migrator.new(:up, migrations).migrate
+    migrator = ActiveRecord::Migrator.new(:up, migrations)
+    migrator.migrate
     assert migrations.all?(&:went_up)
     assert migrations.all? { |m| !m.went_down }
-    assert_equal 2, ActiveRecord::Migrator.current_version
+    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)]
-    ActiveRecord::Migrator.new(:down, migrations).migrate
+    migrator = ActiveRecord::Migrator.new(:down, migrations)
+    migrator.migrate
     assert migrations.all? { |m| !m.went_up }
     assert migrations.all?(&:went_down)
-    assert_equal 0, ActiveRecord::Migrator.current_version
+    assert_equal 0, migrator.current_version
   end
 
   def test_current_version
     ActiveRecord::SchemaMigration.create!(version: "1000")
-    assert_equal 1000, ActiveRecord::Migrator.current_version
+    migrator = ActiveRecord::MigrationContext.new("db/migrate")
+    assert_equal 1000, migrator.current_version
   end
 
   def test_migrator_one_up
@@ -289,33 +289,36 @@ class MigratorTest < ActiveRecord::TestCase
 
   def test_migrator_double_up
     calls, migrations = sensors(3)
-    assert_equal(0, ActiveRecord::Migrator.current_version)
+    migrator = ActiveRecord::Migrator.new(:up, migrations, 1)
+    assert_equal(0, migrator.current_version)
 
-    ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+    migrator.migrate
     assert_equal [[:up, 1]], calls
     calls.clear
 
-    ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+    migrator.migrate
     assert_equal [], calls
   end
 
   def test_migrator_double_down
     calls, migrations = sensors(3)
+    migrator = ActiveRecord::Migrator.new(:up, migrations, 1)
 
-    assert_equal(0, ActiveRecord::Migrator.current_version)
+    assert_equal 0, migrator.current_version
 
-    ActiveRecord::Migrator.new(:up, migrations, 1).run
+    migrator.run
     assert_equal [[:up, 1]], calls
     calls.clear
 
-    ActiveRecord::Migrator.new(:down, migrations, 1).run
+    migrator = ActiveRecord::Migrator.new(:down, migrations, 1)
+    migrator.run
     assert_equal [[:down, 1]], calls
     calls.clear
 
-    ActiveRecord::Migrator.new(:down, migrations, 1).run
+    migrator.run
     assert_equal [], calls
 
-    assert_equal(0, ActiveRecord::Migrator.current_version)
+    assert_equal 0, migrator.current_version
   end
 
   def test_migrator_verbosity
@@ -361,78 +364,85 @@ class MigratorTest < ActiveRecord::TestCase
 
   def test_migrator_going_down_due_to_version_target
     calls, migrator = migrator_class(3)
+    migrator = migrator.new("valid")
 
-    migrator.up("valid", 1)
+    migrator.up(1)
     assert_equal [[:up, 1]], calls
     calls.clear
 
-    migrator.migrate("valid", 0)
+    migrator.migrate(0)
     assert_equal [[:down, 1]], calls
     calls.clear
 
-    migrator.migrate("valid")
+    migrator.migrate
     assert_equal [[:up, 1], [:up, 2], [:up, 3]], calls
   end
 
   def test_migrator_output_when_running_multiple_migrations
     _, migrator = migrator_class(3)
+    migrator = migrator.new("valid")
 
-    result = migrator.migrate("valid")
+    result = migrator.migrate
     assert_equal(3, result.count)
 
     # Nothing migrated from duplicate run
-    result = migrator.migrate("valid")
+    result = migrator.migrate
     assert_equal(0, result.count)
 
-    result = migrator.rollback("valid")
+    result = migrator.rollback
     assert_equal(1, result.count)
   end
 
   def test_migrator_output_when_running_single_migration
     _, migrator = migrator_class(1)
-    result = migrator.run(:up, "valid", 1)
+    migrator = migrator.new("valid")
+
+    result = migrator.run(:up, 1)
 
     assert_equal(1, result.version)
   end
 
   def test_migrator_rollback
     _, migrator = migrator_class(3)
+    migrator = migrator.new("valid")
 
-    migrator.migrate("valid")
-    assert_equal(3, ActiveRecord::Migrator.current_version)
+    migrator.migrate
+    assert_equal(3, migrator.current_version)
 
-    migrator.rollback("valid")
-    assert_equal(2, ActiveRecord::Migrator.current_version)
+    migrator.rollback
+    assert_equal(2, migrator.current_version)
 
-    migrator.rollback("valid")
-    assert_equal(1, ActiveRecord::Migrator.current_version)
+    migrator.rollback
+    assert_equal(1, migrator.current_version)
 
-    migrator.rollback("valid")
-    assert_equal(0, ActiveRecord::Migrator.current_version)
+    migrator.rollback
+    assert_equal(0, migrator.current_version)
 
-    migrator.rollback("valid")
-    assert_equal(0, ActiveRecord::Migrator.current_version)
+    migrator.rollback
+    assert_equal(0, migrator.current_version)
   end
 
   def test_migrator_db_has_no_schema_migrations_table
     _, migrator = migrator_class(3)
+    migrator = migrator.new("valid")
 
     ActiveRecord::Base.connection.drop_table "schema_migrations", if_exists: true
     assert_not ActiveRecord::Base.connection.table_exists?("schema_migrations")
-    migrator.migrate("valid", 1)
+    migrator.migrate(1)
     assert ActiveRecord::Base.connection.table_exists?("schema_migrations")
   end
 
   def test_migrator_forward
     _, migrator = migrator_class(3)
-    migrator.migrate("/valid", 1)
-    assert_equal(1, ActiveRecord::Migrator.current_version)
+    migrator = migrator.new("/valid")
+    migrator.migrate(1)
+    assert_equal(1, migrator.current_version)
 
-    migrator.forward("/valid", 2)
-    assert_equal(3, ActiveRecord::Migrator.current_version)
+    migrator.forward(2)
+    assert_equal(3, migrator.current_version)
 
-    migrator.forward("/valid")
-    assert_equal(3, ActiveRecord::Migrator.current_version)
+    migrator.forward
+    assert_equal(3, migrator.current_version)
   end
 
   def test_only_loads_pending_migrations
@@ -440,25 +450,27 @@ class MigratorTest < ActiveRecord::TestCase
     ActiveRecord::SchemaMigration.create!(version: "1")
 
     calls, migrator = migrator_class(3)
-    migrator.migrate("valid", nil)
+    migrator = migrator.new("valid")
+    migrator.migrate
 
     assert_equal [[:up, 2], [:up, 3]], calls
   end
 
   def test_get_all_versions
     _, migrator = migrator_class(3)
+    migrator = migrator.new("valid")
 
-    migrator.migrate("valid")
-    assert_equal([1, 2, 3], ActiveRecord::Migrator.get_all_versions)
+    migrator.migrate
+    assert_equal([1, 2, 3], migrator.get_all_versions)
 
-    migrator.rollback("valid")
-    assert_equal([1, 2], ActiveRecord::Migrator.get_all_versions)
+    migrator.rollback
+    assert_equal([1, 2], migrator.get_all_versions)
 
-    migrator.rollback("valid")
-    assert_equal([1], ActiveRecord::Migrator.get_all_versions)
+    migrator.rollback
+    assert_equal([1], migrator.get_all_versions)
 
-    migrator.rollback("valid")
-    assert_equal([], ActiveRecord::Migrator.get_all_versions)
+    migrator.rollback
+    assert_equal([], migrator.get_all_versions)
   end
 
   private
@@ -483,11 +495,11 @@ class MigratorTest < ActiveRecord::TestCase
     def migrator_class(count)
       calls, migrations = sensors(count)
 
-      migrator = Class.new(ActiveRecord::Migrator).extend(Module.new {
-        define_method(:migrations) { |paths|
+      migrator = Class.new(ActiveRecord::MigrationContext) {
+        define_method(:migrations) { |*|
           migrations
         }
-      })
+      }
       [calls, migrator]
     end
 end
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index c114842dec..21226352ff 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -28,10 +28,10 @@ module ActiveRecord
 
   class DatabaseTasksUtilsTask < ActiveRecord::TestCase
     def test_raises_an_error_when_called_with_protected_environment
-      ActiveRecord::Migrator.stubs(:current_version).returns(1)
+      ActiveRecord::MigrationContext.any_instance.stubs(:current_version).returns(1)
 
       protected_environments = ActiveRecord::Base.protected_environments
-      current_env            = ActiveRecord::Migrator.current_environment
+      current_env            = ActiveRecord::Base.connection.migration_context.current_environment
       assert_not_includes protected_environments, current_env
       # Assert no error
       ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
@@ -45,10 +45,10 @@ module ActiveRecord
     end
 
     def test_raises_an_error_when_called_with_protected_environment_which_name_is_a_symbol
-      ActiveRecord::Migrator.stubs(:current_version).returns(1)
+      ActiveRecord::MigrationContext.any_instance.stubs(:current_version).returns(1)
 
       protected_environments = ActiveRecord::Base.protected_environments
-      current_env            = ActiveRecord::Migrator.current_environment
+      current_env            = ActiveRecord::Base.connection.migration_context.current_environment
       assert_not_includes protected_environments, current_env
       # Assert no error
       ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
@@ -63,7 +63,7 @@ module ActiveRecord
 
     def test_raises_an_error_if_no_migrations_have_been_made
       ActiveRecord::InternalMetadata.stubs(:table_exists?).returns(false)
-      ActiveRecord::Migrator.stubs(:current_version).returns(1)
+      ActiveRecord::MigrationContext.any_instance.stubs(:current_version).returns(1)
 
       assert_raise(ActiveRecord::NoEnvironmentInSchemaError) do
         ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
@@ -347,50 +347,92 @@ module ActiveRecord
     end
   end
 
-  class DatabaseTasksMigrateTest < ActiveRecord::TestCase
-    self.use_transactional_tests = false
+  if current_adapter?(:SQLite3Adapter) && !in_memory_db?
+    class DatabaseTasksMigrateTest < ActiveRecord::TestCase
+      self.use_transactional_tests = false
+
+      # Use a memory db here to avoid having to rollback at the end
+      setup do
+        migrations_path = MIGRATIONS_ROOT + "/valid"
+        file = ActiveRecord::Base.connection.raw_connection.filename
+        @conn = ActiveRecord::Base.establish_connection adapter: "sqlite3",
+          database: ":memory:", migrations_paths: migrations_path
+        source_db = SQLite3::Database.new file
+        dest_db = ActiveRecord::Base.connection.raw_connection
+        backup = SQLite3::Backup.new(dest_db, "main", source_db, "main")
+        backup.step(-1)
+        backup.finish
+      end
 
-    def setup
-      ActiveRecord::Tasks::DatabaseTasks.migrations_paths = "custom/path"
-    end
+      teardown do
+        @conn.release_connection if @conn
+        ActiveRecord::Base.establish_connection :arunit
+      end
 
-    def teardown
-      ActiveRecord::Tasks::DatabaseTasks.migrations_paths = nil
-    end
+      def test_migrate_set_and_unset_verbose_and_version_env_vars
+        verbose, version = ENV["VERBOSE"], ENV["VERSION"]
+        ENV["VERSION"] = "2"
+        ENV["VERBOSE"] = "false"
 
-    def test_migrate_receives_correct_env_vars
-      verbose, version = ENV["VERBOSE"], ENV["VERSION"]
+        # run down migration because it was already run on copied db
+        assert_empty capture_migration_output
 
-      ENV["VERBOSE"] = "false"
-      ENV["VERSION"] = "4"
-      ActiveRecord::Migrator.expects(:migrate).with("custom/path", 4)
-      ActiveRecord::Migration.expects(:verbose=).with(false)
-      ActiveRecord::Migration.expects(:verbose=).with(ActiveRecord::Migration.verbose)
-      ActiveRecord::Tasks::DatabaseTasks.migrate
+        ENV.delete("VERSION")
+        ENV.delete("VERBOSE")
 
-      ENV.delete("VERBOSE")
-      ENV.delete("VERSION")
-      ActiveRecord::Migrator.expects(:migrate).with("custom/path", nil)
-      ActiveRecord::Migration.expects(:verbose=).with(true)
-      ActiveRecord::Migration.expects(:verbose=).with(ActiveRecord::Migration.verbose)
-      ActiveRecord::Tasks::DatabaseTasks.migrate
+        # re-run up migration
+        assert_includes capture_migration_output, "migrating"
+      ensure
+        ENV["VERBOSE"], ENV["VERSION"] = verbose, version
+      end
 
-      ENV["VERBOSE"] = ""
-      ENV["VERSION"] = ""
-      ActiveRecord::Migrator.expects(:migrate).with("custom/path", nil)
-      ActiveRecord::Migration.expects(:verbose=).with(true)
-      ActiveRecord::Migration.expects(:verbose=).with(ActiveRecord::Migration.verbose)
-      ActiveRecord::Tasks::DatabaseTasks.migrate
+      def test_migrate_set_and_unset_empty_values_for_verbose_and_version_env_vars
+        verbose, version = ENV["VERBOSE"], ENV["VERSION"]
 
-      ENV["VERBOSE"] = "yes"
-      ENV["VERSION"] = "0"
-      ActiveRecord::Migrator.expects(:migrate).with("custom/path", 0)
-      ActiveRecord::Migration.expects(:verbose=).with(true)
-      ActiveRecord::Migration.expects(:verbose=).with(ActiveRecord::Migration.verbose)
-      ActiveRecord::Tasks::DatabaseTasks.migrate
-    ensure
-      ENV["VERBOSE"], ENV["VERSION"] = verbose, version
+        ENV["VERSION"] = "2"
+        ENV["VERBOSE"] = "false"
+
+        # run down migration because it was already run on copied db
+        assert_empty capture_migration_output
+
+        ENV["VERBOSE"] = ""
+        ENV["VERSION"] = ""
+
+        # re-run up migration
+        assert_includes capture_migration_output, "migrating"
+      ensure
+        ENV["VERBOSE"], ENV["VERSION"] = verbose, version
+      end
+
+      def test_migrate_set_and_unset_nonsense_values_for_verbose_and_version_env_vars
+        verbose, version = ENV["VERBOSE"], ENV["VERSION"]
+
+        # run down migration because it was already run on copied db
+        ENV["VERSION"] = "2"
+        ENV["VERBOSE"] = "false"
+
+        assert_empty capture_migration_output
+
+        ENV["VERBOSE"] = "yes"
+        ENV["VERSION"] = "2"
+
+        # run no migration because 2 was already run
+        assert_empty capture_migration_output
+      ensure
+        ENV["VERBOSE"], ENV["VERSION"] = verbose, version
+      end
+
+      private
+        def capture_migration_output
+          capture(:stdout) do
+            ActiveRecord::Tasks::DatabaseTasks.migrate
+          end
+        end
     end
+  end
+
+  class DatabaseTasksMigrateErrorTest < ActiveRecord::TestCase
+    self.use_transactional_tests = false
 
     def test_migrate_raise_error_on_invalid_version_format
       version = ENV["VERSION"]
diff --git a/activestorage/test/database/setup.rb b/activestorage/test/database/setup.rb
index 705650a25d..daeeb5695b 100644
--- a/activestorage/test/database/setup.rb
+++ b/activestorage/test/database/setup.rb
@@ -3,5 +3,5 @@
 require_relative "create_users_migration"
 
 ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
-ActiveRecord::Migrator.migrate File.expand_path("../../db/migrate", __dir__)
+ActiveRecord::Base.connection.migration_context.migrate
 ActiveStorageCreateUsers.migrate(:up)
diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb
index d8f361f524..d5c9973c6b 100644
--- a/railties/lib/rails/info.rb
+++ b/railties/lib/rails/info.rb
@@ -99,7 +99,7 @@ module Rails
     end
 
     property "Database schema version" do
-      ActiveRecord::Migrator.current_version rescue nil
+      ActiveRecord::Base.connection.migration_context.current_version rescue nil
     end
   end
 end
diff --git a/railties/test/application/rake/migrations_test.rb b/railties/test/application/rake/migrations_test.rb
index 788f9160d6..1b45e0645c 100644
--- a/railties/test/application/rake/migrations_test.rb
+++ b/railties/test/application/rake/migrations_test.rb
@@ -37,6 +37,18 @@ module ApplicationTests
         assert_match(/AMigration: reverted/, output)
       end
 
+      test "version outputs current version" do
+        app_file "db/migrate/01_one_migration.rb", <<-MIGRATION
+          class OneMigration < ActiveRecord::Migration::Current
+          end
+        MIGRATION
+
+        rails "db:migrate"
+
+        output = rails("db:version")
+        assert_match(/Current version: 1/, output)
+      end
+
       test "migrate with specified VERSION in different formats" do
         app_file "db/migrate/01_one_migration.rb", <<-MIGRATION
           class OneMigration < ActiveRecord::Migration::Current
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 339a56c34f..a59c63f343 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -34,7 +34,7 @@ module RailtiesTest
 
     def migrations
       migration_root = File.expand_path(ActiveRecord::Migrator.migrations_paths.first, app_path)
-      ActiveRecord::Migrator.migrations(migration_root)
+      ActiveRecord::MigrationContext.new(migration_root).migrations
     end
 
     test "serving sprocket's assets" do
-- 
cgit v1.2.3