マイグレーション【Railsガイド v7.0を読む】


なんとなく使っているので、ちゃんと理解を深めようということでRailsガイドを読んでまとめてみようと思います。

# マイグレーションとは
[Railsガイド](https://railsguides.jp/active_record_migrations.html)より引用
> マイグレーションは、データベーススキーマの継続的な変更（英語）を、統一的かつ簡単に行なうための便利な手法です。マイグレーションではRubyのDSLが使われているので、生のSQLを作成する必要がなく、スキーマおよびスキーマ変更がデータベースに依存しなくなります。
>
> 個別のマイグレーションは、データベースの新しい「バージョン」とみなせます。スキーマは空の状態から始まり、マイグレーションによる変更が加わるたびにテーブル、カラム、エントリが追加または削除されます。Active Recordはマイグレーションの時系列に沿ってスキーマを更新する方法を知っているので、履歴のどの時点からでも最新バージョンのスキーマに更新できます。Active Recordはdb/schema.rbファイルを更新し、データベースの最新の構造と一致するようにします。


# マイグレーションファイル
マイグレーションファイルは、`db/migrate`フォルダ内にある。
ファイル名は、`YYYYMMDDHHMMSS_create_tasks.rb`のような形式。

前半部分`YYYYMMDDHHMMSS`は、マイグレーションを識別するUTCタイムスタンプ
後半部分`create_tasks`は、マイグレーション名

ファイルの後半部分(snake_case)`create_tasks`は、マイグレーションのクラス名(CamelCase)`CreateTasks`と一致する必要がある。

``` YYYYMMDDHHMMSS_create_tasks.rb
class CreateTasks < ActiveRecord::Migration[7.0]
end
```

## 作成
マイグレーションファイルは、以下のコマンド実行時に`db/migrate`フォルダに作成される。

#### モデルを作成したとき
```
rails generate model モデル名
```
モデルを作成した時には、`YYYYMMDDHHMMSS_create_モデル名s.rb`が作成される。

#### 個別にマイグレーションファイルを作成したとき
```
rails generate migration マイグレーションのクラス名
```
個別にマイグレーションファイルを作成したときは、`YYYYMMDDHHMMSS_マイグレーションのクラス名(snake_case).rb`が作成される。

## 編集
### `change`
```rb
def change
# 変更を記述
end
```
`change`メソッドを使用すると、ActiveRecordがマイグレーションをロールバックする方法を自動的に認識してくれる。
`change_column`など不可逆的なものもあるので、注意すること。
```rb
change_column :products, :part_number, :text
```
上記の例では`part_number`をtext型に変更しているが、この記述からは変更前の型が分からないのでロールバックできない。（ = 不可逆的）

`change`メソッドでサポートされている可逆的なメソッドは[Railsガイドv7.0 3.9 changeメソッドを使う
](https://railsguides.jp/active_record_migrations.html#change%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%82%92%E4%BD%BF%E3%81%86)を参照。

不可逆的なメソッドを使用する場合は、`reversible`メソッドを使用するか、`up`/`down`メソッドを使用し、明示的に通常通りに実行される処理とロールバックするときに実行する処理を指定する必要がある。

#### `reversible`
`reversible`を使用すると、`change`メソッド内で不可逆的な処理を記述する際に、通常通りマイグレーションする場合とロールバックする場合の処理を分けることができる。
```rb
class ExampleMigration < ActiveRecord::Migration[7.0]
  def change
    create_table :distributors do |t|
      t.string :zipcode
    end

    reversible do |dir|
      dir.up do
        # CHECK制約を追加
        execute <<-SQL
          ALTER TABLE distributors
            ADD CONSTRAINT zipchk
              CHECK (char_length(zipcode) = 5) NO INHERIT;
        SQL
      end
      dir.down do
        execute <<-SQL
          ALTER TABLE distributors
            DROP CONSTRAINT zipchk
        SQL
      end
    end

    add_column :users, :home_page_url, :string
    rename_column :users, :email, :email_address
  end
end
```
このマイグレーションをロールバックした場合、`down`ブロックはカラムの削除とテーブルの削除の間に実行される。

### `up`/`down`
```rb
def up
# 通常の変更を記述
end
def down
# upメソッドで行った変更をロールバックする処理を記述
end
```
`up`にはスキーマに対する変更方法を、`down`には`up`メソッドによって行われた変更をロールバックする方法を記述する。

```rb
class ExampleMigration < ActiveRecord::Migration [7.0]
  def up
    create_table :distributors do |t|
      t.string :zipcode
    end

    # CHECK制約を追加
    execute <<-SQL
      ALTER TABLE distributors
        ADD CONSTRAINT zipchk
        CHECK (char_length(zipcode) = 5);
    SQL

    add_column :users, :home_page_url, :string
    rename_column :users, :email, :email_address
  end

  def down
    rename_column :users, :email_address, :email
    remove_column :users, :home_page_url

    execute <<-SQL
      ALTER TABLE distributors
        DROP CONSTRAINT zipchk
    SQL

    drop_table :distributors
  end
end
```
`up`メソッドでは、
* テーブルの作成
* CHECK制約を追加
* カラムを追加
* カラム名を変更

`down`メソッドでは、
* カラム名を変更
* カラムを削除
* CHECK制約を削除
* テーブルの削除

が行われる。（`reversible`に記載のマーグレーションと同等の内容）

`down`メソッド内の処理の順序は、`up`メソッド内の処理順と真逆にする。

### その他のメソッド
その他`add_column`, `create_table`などのメソッドは書き出すと長くなりそうなので以下を参照。
[ActiveRecord::ConnectionAdapters::SchemaStatements Methods](https://api.rubyonrails.org/v7.0/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html)


## 実行
以下のコマンドでマイグレーションを実行できる
```
rails db:migrate
```

未実行のマイグレーションファイルが複数存在する場合は、ファイル名のタイムスタンプが古い順に実行される。

特定のバージョンまでマイグレーションを実行するには以下のようにバージョンを指定する。
```
rails db:migrate VERSION=YYYYMMDDHHMMSS
```
指定したバージョンが現在のバージョンより大きい場合、指定バージョンに到達するまで`change`メソッド、`up`メソッドが実行される。
（指定バージョンのマイグレーションファイルも**実行される**）

指定したバージョンが現在のバージョンより小さい場合、指定バージョンに到達するまでロールバックされる。
（指定バージョンのマイグレーションファイルは**実行されない**）

#### db/schema.rb
`db:migrate`を実行すると`db:schema:dump`コマンドも同時に呼び出され、`db/schema.rb`の内容がデータベースの構造と一致するように更新される。

`db/schema.rb`にバージョンが含まれているので、`version: 2023_02_28_130514`を見ればどのマイグレーションファイルまで実行されているかが確認できる。
```db/schema.rb
ActiveRecord::Schema[7.0].define(version: 2023_02_28_130514) do
```
### ロールバック
マイグレーションファイルに誤りがあり訂正したい場合など、直前に行ったマイグレーションをロールバックするには以下のコマンドを実行する。
```
rails db:rollback
```

このコマンドにより`change`メソッドを逆転実行するか、`down`メソッドを実行する形で直前のマイグレーションにロールバックする。

2つ以上のマイグレーションをロールバックするには、`STEP`パラメータでロールバックする数を指定する。
以下のコマンドで最後に行った3つのマイグレーションをロールバックできる。
```
rails db:rollback STEP=3
```

### データベースを設定する
```
rails db:setup
```
このコマンドにより、以下がまとめて実行される
* データベースの作成`db:create`
* スキーマの読み込み`db:schema:load`
* seedデータを用いたデータベースの初期化`db:seed`

### データベースをリセットする
```
rails db:reset
```
データベースをdropして、再度設定する。（`rails db:drop db:setup`と同等）
このコマンドは現在の`db/schema.rb`をそのまま使い回すので、マイグレーションファイルを編集していてもその内容は反映されない。
マイグレーションファイルの内容を反映させるには、次のコマンドを実行する。
```
rails db:migrate:reset
```

# 参考文献
## Railsガイド v7.0 - Active Record マイグレーション
https://railsguides.jp/active_record_migrations.html

## ActiveRecord::ConnectionAdapters::SchemaStatements
https://api.rubyonrails.org/v7.0/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html