【MySQL】Mysql2::Error: Specified key was too long; max key length is 767 bytes

## はじめに
Railsアプリケーション作成中、MySQLで以下エラーが発生したため解決した方法を残します。

### エラー内容
`rails db:migrate`を実行したときに発生。

```shell
Mysql2::Error: Specified key was too long; max key length is 767 bytes: <キー名>
```

key長の制限が最大**767バイト**になっているのに、それを超えてしまったことで発生したようです。

### 解決法は大きく２つ

1. 767バイトを**超えない**ように何かを削る
2. 767バイトを**超えてもいい**ようにする

今回は**1.**で対応してみます。

## 環境
```yaml
OS: macOS Catalina 10.15.3
Ruby: 2.6.5
Rails: 6.0.2.1
MySQL: 5.7
```
## 結論:解決法
以下のような`mysql.rb`を`config/initializer`配下に新規作成します。

```ruby:config/initializer/mysql.rb
require 'active_record/connection_adapters/abstract_mysql_adapter'

module ActiveRecord
  module ConnectionAdapters
    class AbstractMysqlAdapter
      NATIVE_DATABASE_TYPES[:string] = { :name => "varchar", :limit => 191 }
    end
  end
end
```

## なぜ解決するのか？
解決はしたのですが、**理由を理解しておきたかった**ので深堀ります。

### 前提：文字コード`utf8`と`utf8mb4`の違い
[こちらの記事](https://qiita.com/jkr_2255/items/74fc79e764378b59355a)で詳しく解説されていました。

> RailsやCakePHPのデフォルトで生成される255文字のVARCHARでは、3バイトのutf8では255×3=765でほぼぴったり収まります。一方、utf8mb4ではオーバーしてしまうので、エラーとなりました。

今回自分の環境でも`utf8mb4`を使用していました。

`utf8mb4`では1文字4バイト、
そのため、**255 × 4 = 1020バイト**となり、冒頭の767バイトを超えてエラーが発生したということですね。

その他、`utf8`と`utf8mb4`の違いについては以下記事もわかりやすく解説をされていました。

[MySQL で utf8 と utf8mb4 の混在で起きること - @tmtms のメモ](https://tmtms.hatenablog.com/entry/2016/09/06/mysql-utf8)

では、これを踏まえて今回の`mysql.rb`でなぜ解決したのかを確認します。

### 今回作成したファイルで変わる設定
今回の`mysql.rb`は**ActiveRecord**内の
`NATIVE_DATABASE_TYPES`
という設定を上書きするファイルになっています。

該当のソースは[こちら](https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb)

ソースから当記事に関連する部分だけ抜き出すと、

```ruby
module ActiveRecord
  module ConnectionAdapters
    class AbstractMysqlAdapter < AbstractAdapter

      #...略

      NATIVE_DATABASE_TYPES = {
        primary_key: "bigint auto_increment PRIMARY KEY",
        string:      { name: "varchar", limit: 255 }, #ここ
        text:        { name: "text" },
        integer:     { name: "int", limit: 4 },
        float:       { name: "float", limit: 24 },
        decimal:     { name: "decimal" },
        datetime:    { name: "datetime" },
        timestamp:   { name: "timestamp" },
        time:        { name: "time" },
        date:        { name: "date" },
        binary:      { name: "blob" },
        blob:        { name: "blob" },
        boolean:     { name: "tinyint", limit: 1 },
        json:        { name: "json" },
      }

      #...略
      #その他、change_columnメソッドのソース(どんなSQLを発行するかの定義)などが記載
    end
  end
end
```

先程の、`mysql.rb`ではここの一行を上書きしていることになります。

```ruby
string:      { name: "varchar", limit: 255 }
```

つまり、**Rails**で

```ruby
$ rails g model User name:string
```
などで**String型**を設定したら**MySQL**では自動的に**VARCHAR(255)**で設定されるということです。

これを`mysql.rb`を追加することで、**VARCHAR(191)**に変更が出来ます。

つまり、**767バイト ÷ 4バイト = 191.75文字**を超えないので、エラー解決となります。

では、設定変更が出来ているかを確認します。

### 確認
`rails console`にて確認します。

```ruby:Before(mysql.rb作成前)
[1] pry(main)> ActiveRecord::Base.connection.native_database_types
=> {:primary_key=>"bigint auto_increment PRIMARY KEY",
 :string=>{:name=>"varchar", :limit=>255}, #limitが255になっている
#...以下略
```
↓

```ruby:After(mysql.rb作成後)
[2] pry(main)> ActiveRecord::Base.connection.native_database_types
=> {:primary_key=>"bigint auto_increment PRIMARY KEY",
 :string=>{:name=>"varchar", :limit=>191}, #ここがmysql.rbで設定した内容に変わる
#...以下略
```

`mysql.rb`によって設定が変更されていることが確認出来ました！

以上です！

## おわりに
最後まで読んで頂きありがとうございました:bow_tone1:

どなたかの参考になれば幸いです:relaxed:

## 参考にさせて頂いたサイト（いつもありがとうございます）
- [MySQL 5.5 / Redmine 3.4.3 / Ruby23-x64 — Mysql2::Error: Specified key was too long; max key length is 767 bytes - Redmine](https://www.redmine.org/boards/2/topics/54308)
- [MySQLのインデックスサイズに767byteまでしかつかえない問題と対策 - ハマログ](https://blog.e2info.co.jp/2017/04/17/mysql%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%B5%E3%82%A4%E3%82%BA%E3%81%AB767byte%E3%81%BE%E3%81%A7%E3%81%97%E3%81%8B%E3%81%A4%E3%81%8B%E3%81%88%E3%81%AA%E3%81%84/)
- [utf8mb4で巻き起こったトラブル - Qiita](https://qiita.com/jkr_2255/items/74fc79e764378b59355a)
- [MySQL で utf8 と utf8mb4 の混在で起きること - @tmtms のメモ](https://tmtms.hatenablog.com/entry/2016/09/06/mysql-utf8)