【初学者の教訓】gem Sorceryを使う時の注意点とmigrationについて

## はじめに

Railsでアプリを作成中、gem sorceryでユーザー認証機能を追加しようとしたときに起きた出来事と、そのときに学んだ教訓です。

## 結論

- `rails generate sorcery:install`を実行するとUserモデルも自動的に作成されるので、すでにUserモデルを作っている場合は要注意
- 公式をよく読みましょう。
https://github.com/Sorcery/sorcery

## 何があった？

Railsで掲示板アプリを作成していました。User、Post、Commentなどのモデルを作成したところで、「そういえばsorceryってgemでログイン機能作れるんだったな〜」と軽い気持ちでgemをbundle installし、`rails generate sorcery:install`したところ…

```ruby
web-1  | == 20240827051328 SorceryCore: migrating ======================================
web-1  | -- create_table(:users)
web-1  | bin/rails aborted!
web-1  | StandardError: An error has occurred, all later migrations canceled: (StandardError)
web-1  | 
web-1  | Mysql2::Error: Table 'users' already exists
web-1  | /myapp/db/migrate/20240827051328_sorcery_core.rb:3:in `change'
web-1  | 
web-1  | Caused by:
web-1  | ActiveRecord::StatementInvalid: Mysql2::Error: Table 'users' already exists (ActiveRecord::StatementInvalid)
web-1  | /myapp/db/migrate/20240827051328_sorcery_core.rb:3:in `change'
web-1  | 
web-1  | Caused by:
web-1  | Mysql2::Error: Table 'users' already exists (Mysql2::Error)
web-1  | /myapp/db/migrate/20240827051328_sorcery_core.rb:3:in `change'
web-1  | Tasks: TOP => db:prepare
web-1  | (See full trace by running task with --trace)
web-1 exited with code 1
```

「usersテーブルはすでに存在しているためmigrateできませんでした」

え、sorceryてUserモデルも自動で作るの？？

→公式に書いてあった〜〜〜

> Run the following command to **generate the core migration file, the initializer file and the `User` model class**.
> 
> 
> `$ rails generate sorcery:install`
> https://github.com/Sorcery/sorcery
> 

きちんと読みましょうね。

そんなわけで、Userモデルをすでに作成した状態でSorceryをインストールしてしまったためにマイグレーションエラーが発生しました。

## 対処方法

【方針】

最初にusersテーブルを作成したmigrationを取り下げ、Sorceryで作成されたmigrationファイルを採用する

【やったこと】

usersテーブルを作成した時のmigrationを取り下げようとしました。

```ruby
$ rails db:migrate:down VERSION=usersテーブル作成時のmigrationID
```

が、他のpostsテーブルなどでusersのforeign keyを設定していたためエラーが発生。

```ruby
Mysql2::Error: Cannot drop table 'users' referenced by a foreign key constraint 'fk_rails_5b5ddfd518' on table 'posts'.
```

そこで、関連するテーブルのmigrationを1つずつdownしていき、既存のusersテーブルもdown後、Sorceryのusersテーブルのmigrationをupし、関連するテーブルを再度up。

```ruby
# 作業前
 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20240826063633  Create users
   up     20240826064309  Create posts
   up     20240826064450  Create likes
   up     20240827003449  Create comments
  down    20240827051328  Sorcery core
  
# 作業後
 Status   Migration ID    Migration Name
--------------------------------------------------
  down    20240826063633  Create users
   up     20240826064309  Create posts
   up     20240826064450  Create likes
   up     20240827003449  Create comments
   up     20240827051328  Sorcery core
```

この時点でlocalhost:3000にアクセスすると、`ActiveRecord::PendingMigrationError`が発生しました。適用されていないmigrationがあるとエラーになるのですね。

よくないとは思いつつ、今回は該当のmigrationファイルを削除して対応しました。

```ruby
 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20240826064309  Create posts
   up     20240826064450  Create likes
   up     20240827003449  Create comments
   up     20240827051328  Sorcery core
```

## migrationのおさらい

### migrationのupとdown

下記コマンドを打つとmigrationファイルが作成されます。

```ruby
$ rails generate migration ファイル名 カラム名:型
```

この時点では、migrationファイルが作成されただけで、DBに適用はされていない(down)状態です。

```ruby
$ rails g migration AddNameToUser name:string
      invoke  active_record
      create    db/migrate/20240828022300_add_name_to_user.rb
      
$ rails db:migrate:status                    

database: myapp_development

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20240826064309  Create posts
   up     20240826064450  Create likes
   up     20240827003449  Create comments
   up     20240827051328  Sorcery core
  down    20240828022300  Add name to user
```

ここで`rails db:migrate`を実行することでStatusがupになり、migrationが適用されます。

### migrationを実行するコマンド

上記の通り、migrationファイルが作成されていても、migrationの状態がupになっていないとDBには適用されません。このup/downを実行するコマンドには例えば以下のようなものがあります。

```ruby
$ rails db:migrate
# まだ実行されていないchangeまたはupメソッドを日付が古い順から実行していく

$ rails db:rollback
# 直前に行ったマイグレーションをロールバックして取り消す(down状態にする)
$ rails db:rollback STEP=3
# 最後に行った3つのマイグレーションをdown状態にする
$ rails db:migrate:down VERSION=migrationID
# 特定のマイグレーションをdown状態にする
$ rails db:migrate:up VERSION=migrationID
# 特定のマイグレーションをup状態にする
```

### 補足

up状態のmigrationファイルを削除してはいけません。←やった人

```ruby
 $ rails db:migrate:status
 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20240826063633  Create users
   up     20240826064309  Create posts
   up     20240826064450  Create likes
   up     20240827003449  Create comments
   up     20240827045550  ********** NO FILE **********
```

migration up状態でファイルを削除してしまうと、rollbackでmigrationをdownすることもできなくなってしまいます。

```ruby
$ rails db:rollback      
bin/rails aborted!
ActiveRecord::UnknownMigrationVersionError:  (ActiveRecord::UnknownMigrationVersionError)

No migration with version number 20240827045550.
```

ファイルを元に戻してロールバックするなどで対処しましょう。

## 今後気をつけること

- gemを使う時は、公式をよく読む！
- 通常migrationファイルを修正したい時は、新規でmigrationファイルを作成して変更を適用する
- 本来、migrationファイルは更新履歴として残しておくべきものなので、基本削除しない

## 参考

https://github.com/Sorcery/sorcery

[Active Record マイグレーション - Railsガイド](https://railsguides.jp/active_record_migrations.html)

[初心者目線のmigrationまとめ(1)【migration消しちゃった?!】 - Qiita](https://qiita.com/MIDO-ruby7/items/52ad957105ac2e77d437)