マイグレーションファイルのdef upとdef self.upの違いについて調べてみた②

## はじめに
[前回の記事](https://qiita.com/knog/items/288ad6d53a9a145924a8)で`nearest_delagate`がなんなのか分からず、中途半端に終わってしまいました。
今回はその続きです。migrateタスクが定義されているところから追いかけてみようと思います。

ちなみに前回と同じくマイグレーションファイルが下記のようなものだとして話を進めます。

```
class CreateUsers < ActiveRecord::Migration[5.2]
  def up
    create_table :users do |t|
      t.string :name

      t.timestamps
    end
  end
end
```

## 環境
ruby 2.6.3
rails 5.2.3


## active_record/railties/databases.rake
```

db_namespace = namespace :db do

  desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)."
  task migrate: :load_config do
    ActiveRecord::Tasks::DatabaseTasks.migrate
    db_namespace["_dump"].invoke
  end
```

コマンドライン上で`rails db:migrate`と入力してEnterを押すと、railsのモジュールが色々読み込まれたのちに、まず上記が実行されます。



## ActiveRecord::Tasks::DatabaseTasks#migrate
```
module ActiveRecord
  module Tasks
    module DatabaseTasks

      def migrate
        check_target_version

        verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] != "false" : true
        scope = ENV["SCOPE"]
        verbose_was, Migration.verbose = Migration.verbose, verbose
        Base.connection.migration_context.migrate(target_version) do |migration|
          scope.blank? || scope == migration.scope
        end
        ActiveRecord::Base.clear_cache!
      ensure
        Migration.v
```

`Base.connection.migration_context.migrate`がマイグレーションの中身っぽいです。`migrate`のレシーバを確認してみます。

```
> Base.connection.migration_context
=> #<ActiveRecord::MigrationContext:0x0000000005c3bcc0
 @migrations_paths=["/path/to/app/db/migrate"]>
```

`MigrationContext`がなんなのかちょっとわからなかったですが、マイグレーションファイルがあるパスをインスタンス変数に持っているようです。
`MigrationContext#migrate`を見にいきます。


## ActiveRecord::MigrationContext#migrate
```
module ActiveRecord

  class MigrationContext

    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
```

`target_version`は、`rails db:migrate VERSION=xxxx`で指定できる値で、マイグレーションファイルの先頭にある数字10桁のことです。何もオプションをつけないで実行した場合、`nil`が返ります。

今回は`nil`の場合を見ていくので、`MigrationContext#up`が実行されます。

## ActiveRecord::MigrationContext#up

```
def up(target_version = nil)
  selected_migrations = if block_given?
    migrations.select { |m| yield m }
  else
    migrations
  end

  Migrator.new(:up, selected_migrations, target_version).migrate
end
```

`Migrator`のインスタンスを作成しているので`Migrator#initialize`を確認します。

```
module ActiveRecord

  class Migrator

    def initialize(direction, migrations, target_version = nil)
      @direction         = direction
      @target_version    = target_version
      @migrated_versions = nil
      @migrations        = migrations

      validate(@migrations)

      ActiveRecord::SchemaMigration.create_table
      ActiveRecord::InternalMetadata.create_table
    end
```

`selected_migrations`は、例えば以下のようになります。

```
> selected_migrations
=> [#<struct ActiveRecord::MigrationProxy
  name="CreateUsers",
  version=20191020160206,
  filename="/home/ec2-user/environment/sample/db/migrate/20191020160206_create_users.rb",
  scope="">]
```

マイグレーションファイルに記述したクラス名、ファイル名の先頭の数字、ファイルパスを保持した、`MigrationProxy`というクラスのインスタンスの配列です。

上記を踏まえると`Migrator`は以下のようになります。

```
Migrator.new(:up, selected_migrations, target_version)
=> #<ActiveRecord::Migrator:0x0000000004f87740
 @direction=:up,
 @migrated_versions=#<Set: {}>,
 @migrations=[#<struct ActiveRecord::MigrationProxy name="CreateUsers", version=20191020160206, filename="/home/ec2-user/environment/sample/db/migrate/20191020160206_create_users.rb", scope="">],
 @target_version=nil>
```


`Migrator#migrate`を見にいきます。



## ActiveRecord::Migrator#migrate
```
def migrate
  if use_advisory_lock?
    with_advisory_lock { migrate_without_lock }
  else
    migrate_without_lock
  end
end
```

`Migrator#migrate_without_lock`を見にいきます。


## ActiveRecord::Migrator#migrate_without_lock
```
def migrate_without_lock
  if invalid_target?
    raise UnknownMigrationVersionError.new(@target_version)
  end

  result = runnable.each do |migration|
    execute_migration_in_transaction(migration, @direction)
  end

  record_environment
  result
end
```

`Migrator#runnable`で、全マイグレーションの中から指定したバージョンまでのマイグレーションに限定しています。バージョン指定がない場合は全てのマイグレーションが入ります。尚、中身は`MigrationProxy`クラスのインスタンスです。

```
def runnable
  runnable = migrations[start..finish]
  if up?
    runnable.reject { |m| ran?(m) }
  else
    ...
  end
end

def start    # migrateのときは0を返します。
  up? ? 0 : (migrations.index(current) || 0)
end

def finish   # バージョン指定がある場合は、そのバージョンのインデックスを返します。
  migrations.index(target) || migrations.size - 1
end

def target
  migrations.detect { |m| m.version == @target_version }
end
```

`Migrator#execute_migration_in_transaction`を見にいきます。



## ActiveRecord::Migrator#execute_migration_in_transaction

```
def execute_migration_in_transaction(migration, direction)
  return if down? && !migrated.include?(migration.version.to_i)
  return if up?   &&  migrated.include?(migration.version.to_i)

  Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger

  ddl_transaction(migration) do
->  migration.migrate(direction)
    record_version_state_after_migrating(migration.version)
  end
rescue => e
  msg = "An error has occurred, ".dup
  msg << "this and " if use_transaction?(migration)
  msg << "all later migrations canceled:\n\n#{e}"
  raise StandardError, msg, e.backtrace
end
```

上記で示した矢印の`migration`には、前で確認したように`MigartionProxy`クラスのインスタンスが格納されています。

```
> migration
=> #<struct ActiveRecord::MigrationProxy
  name="CreateUsers",
  version=20191020160206,
  filename="/home/ec2-user/environment/sample/db/migrate/20191020160206_create_users.rb",
  scope="">
```

本来ならば、`migration.migrate`で、`MigartionProxy`クラスのインスタンスメソッドである`migrate`が実行されるはずですが、`delegate`(下記参照)により、privateメソッドの`migration`を実行した後、`migrate`を実行します。

```
MigrationProxy = Struct.new(:name, :version, :filename, :scope) do

  delegate :migrate, :announce, :write, :disable_ddl_transaction, to: :migration

  private
    def migration
      @migration ||= load_migration
    end

    def load_migration
      require(File.expand_path(filename))
      name.constantize.new(name, version)
    end
end
```

本来ならば`migration.migrate`となるところが、`delegate :migrate, to: :migration`という記述があることによって、`migration.migration.migrate`となります（1つ目の`migration`は`MigrationProxy`クラスのインスタンス、2つ目の`migration`は`MigrationProxy`クラスのprivateメソッドです）。
（`delegate`に関して、詳しくは[こちら](https://qiita.com/jkr_2255/items/fcbe263b4b546ba76d8e)などを参考にしてください。）


この`load_migration`内の`name.constantize.new`で、マイグレーションファイルに記述したクラスのインスタンスが作成されます。

```
> name
=> "CreateUser"

> name.constantize.new(name, version)
=> #<CreateUsers:0x00000000050de260 @connection=nil, @name="CreateUsers", @version=20191020160206>
```

このインスタンスが`migrate`メソッドを実行します。

ただ、マイグレーションファイルに`migrate`メソッドは記述していないので、以下のような継承により`ActiveRecord::Migration#migrate`が実行されます。

```
class CreateUsers < ActiveRecord::Migration[5.2]
```


## ActiveRecord::Migration#migrate

```
def migrate(direction)
  return unless respond_to?(direction)

  case direction
  when :up   then announce "migrating"
  when :down then announce "reverting"
  end

  time = nil
  ActiveRecord::Base.connection_pool.with_connection do |conn|
    time = Benchmark.measure do
      exec_migration(conn, direction)
    end
  end

  case direction
  when :up   then announce "migrated (%.4fs)" % time.real; write
  when :down then announce "reverted (%.4fs)" % time.real; write
  end
end
```



`exec_migration`をみにいきます。

## ActiveRecord::Migration#exec_migration

```
def exec_migration(conn, direction)
  @connection = conn
  if respond_to?(:change)
    if direction == :down
      revert { change }
    else
      change
    end
  else
    send(direction)
  end
ensure
  @connection = nil
end
```

今、このメソッドのレシーバは、マイグレーションファイルに記述した`CreateUsers`のインスタンスです。
ファイル内で`change`を定義していれば`respond_to?(:change)`がtrueになり`change`が実行され、そうでなければ`send(direction)`が実行されます。

今回は`change`ではなく、`up`もしくは`self.up`なので、

①`up`の場合 → マイグレーションファイル内の`up`が実行される
②`self.up`の場合 → `ActiveRecord::Migration#up`が実行される

といった感じです。
<br>
`self.up`の場合を見ていきます。


## ActiveRecord::Migration#up
```
def up
  self.class.delegate = self
  return unless self.class.respond_to?(:up)
  self.class.up
end
```

マイグレーションファイルに`self.up`と定義しているので、`self.class.respond_to?(:up)`がtrueになります。
そして`self.class.up`、つまり、マイグレーションファイルに記述した`self.up`が実行されます。


この後は[前回の記事](https://qiita.com/knog/items/288ad6d53a9a145924a8)に書いたように、`self.up`ならクラスメソッドの`method_missing`が、`up`ならインスタンスメソッドの`method_missing`が実行されます。



## 参考
https://qiita.com/jkr_2255/items/fcbe263b4b546ba76d8e