Rails5.2.3+MySQL+Ridgepoleでテーブルの主キーにUUIDを使う

# 概要

主キーにUUIDを使用したいという場面は前からよくあるようで、ここではその是非には触れず、今回自分が採用した方法について残しておきます。

```
# ローカルでの実行
> ruby -v
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin18]

> mysql -uroot
# 〜省略〜
Server version: 5.7.20 Homebrew
# 〜省略〜

> rails -v
Rails 5.2.3

> ridgepole -v
ridgepole 0.7.7
```

# tl;dr

## schemaファイル

```
create_table "hoges", id: :string, limit: 36, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
  t.integer "foo", comment: "foo!!"
  t.string "bar", comment: "bar!!"
end
```

## モデル

```ruby:app/models/hoge.rb
class Hoge < ApplicationRecord
  include PrimaryKeyUuidAutoGeneratable
end
```

```ruby:concerns/primary_key_uuid_auto_generatable.rb
module PrimaryKeyUuidAutoGeneratable
  extend ActiveSupport::Concern

  included do
    before_create :generate_uuid
  end

  def generate_uuid
    return if self.id.present?

    self.id = SecureRandom.uuid
  end
end

```

# 他の方法？

## activeuuid

RailsとUUIDでググると[activeuuid](https://github.com/jashmenn/activeuuid)が散見されるのだけど、`bundle install`すると**全てのrailsコマンドが壊れる。**

```
NoMethodError: undefined method `alias_method_chain' for ActiveRecord::ConnectionAdapters::Column:Class
```

なんじゃあそりゃあと思ったら、どうやらRails5でdeprecatedになったメソッドのようだ。
まじかい、最近メンテされてないんかなと思ってよく見たら

```
Latest commit 4f8e0ef on 2 Sep 2015
```

😭

## Rails5から導入された、idの型がデフォルトでUUIDになる設定

[github.com/rails/rails/pull/21762](https://github.com/rails/rails/pull/21762)

ぽすぐれだったら良いかもだけど、MySQLでこれできてもUUID型が存在しないので意味がない...

# Ridgepoleでの書き方

最初は

```
create_table "other_services_posts", id: :false, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
  t.string "id", limit: 36, null: false, primary_key: true, comment: "主キー"
  # 〜省略〜
end
```

と書いていて、applyもできるしこれでいいやと思っていたら、２度目のapply実行時に下記エラー。

```
Apply `db/Schemafile`
[WARNING] Primary key definition of `hoges` differ but `allow_pk_change` option is false
  from: {:id=>:string, :limit=>36}
    to: {:id=>false}

[ERROR] Mysql2::Error: Duplicate column name 'id': ALTER TABLE `hoges` ADD `id` varchar(36) NOT NULL PRIMARY KEY AFTER `id`
```

えー
どうも`create_table`の`id: false`が２回目以降に矛盾を起こすらしい。

下記の様に`create_table`側の設定に書いたら２回目も問題なく実行されました。

```
create_table "hoges", id: :string, limit: 36, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
  # 〜省略〜
end
```

# レコード作成時のデフォルト値

MySQLにはデフォルト値に関数を指定できないので、ぽすぐれのようにDB側でUUIDのデフォルト値を持たせることができません。
ので、Rails側で値を入れる必要があります。


```ruby:concerns/primary_key_uuid_auto_generatable.rb
module PrimaryKeyUuidAutoGeneratable
  extend ActiveSupport::Concern

  included do
    before_create :generate_uuid
  end

  def generate_uuid
    # 既に値が入っている場合は何もしない
    return if self.id.present?

    # ここでは作成したUUIDが既に使用されているか否かのチェックをしない、理由は以下。
    # - 毎回レコードの作成時にSQLが余分に発行されるのを避けたい
    # - UUIDの性質上その重複をアプリケーションレイヤーで調べる必要はない
    # - 仮に重複したとしてもDBの主キー制約によって保存はできない
    self.id = SecureRandom.uuid
  end
end

```

# `limit: 36`

この設定が正しく動作しているかどうかについては、こう言う感じに細工すると確認できます。

```ruby:id_generator.rb
  self.id = SecureRandom.uuid + "1"
```

```
> Hoge.create
   (0.2ms)  BEGIN
  Hoge Create (0.5ms)  INSERT INTO `hoges` (`id`, `created_at`, `updated_at`) VALUES ('e92a6b37-9bec-4452-9205-163e3dad98111', '2019-05-02 13:31:56', '2019-05-02 13:31:56')
   (0.2ms)  ROLLBACK
ActiveRecord::ValueTooLong: Mysql2::Error: Data too long for column 'id' at row 1: INSERT INTO `hoges` (`id`, `created_at`, `updated_at`) VALUES ('e92a6b37-9bec-4452-9205-163e3dad98111', '2019-05-02 13:31:56', '2019-05-02 13:31:56')
Caused by Mysql2::Error: Data too long for column 'id' at row 1
```

１文字足しただけでちゃんとエラーになっています。


# 参考

- [Rails の UUIDプライマリキーを試す](https://qiita.com/HeRo/items/2816e27fb3066db6c4e6#mysql%E3%81%AE%E5%A0%B4%E5%90%88)
    - 最も参考になったのが@HeRoさんの記事でした、ありがとうございました！！

- [Railsでuuidを取り扱う時のTips](https://qiita.com/okutaku/items/97c651132796b94aad23)
- [RailsのモデルIDにUUIDを使う（翻訳）](https://techracho.bpsinc.jp/hachi8833/2018_01_04/50565)