コード譜投稿アプリを作る#6 (railsのプライマリキーをuuidに変更)

[← 前回の記事](https://qiita.com/t_sekky/items/2fe386ba8b74e89b06b3)

# 今回やること
これまでプライマリキーは特にいじらなかったので、idは整数型のオートインクリメント（1, 2, 3 ...）になっています。
これから作る予定のサイトはユーザー投稿型のサイトにしたいので、idをuuidにして`/scores/1`のようなURLではなく`/scores/7a670050-7d74-473c-b322-0322b622b232`のようなURLでコード譜それぞれにアクセスできるようにします。

こうすることによるメリットは、
- URLが推定できてしまうのを防ぐ
- 全体のレコード数が把握できてしまうのを防ぐ

などがあると思います。
特にサービス立ち上げ当初は「ひとけ」がないのがバレやすいのもあるかなと。

トランザクションがめちゃくちゃ増えそうなサービス内容でもないですし、PostgreSQLであればuuidを採用してもパフォーマンスがめちゃくちゃ落ちるということもなさそうなので、今回は全面的にuuidを採用します。(mysqlだと昇順のuuidを設定しないとかなりパフォーマンスが落ちるらしい)

# DBをMySQLからPostgresQLに変更
MySQLでもuuidは使用できるらしいのですが、なんだかめんどくさそうなので簡単に実装できそうなPostgreSQLに乗り換えることにしました。（開発始めたばっかりなので今なら乗り換えも簡単）
今まで特にDBMSの違いについて意識することはなかったんですが、こういう違いもあるんですね…

まずdockerのvolumeを削除します。
```
$ docker volume ls
    ...
    local　　　　mysql-data
$ docker volume rm mysql-data
```

`docker-compose.yml`、`database.yml`を編集

```yml:docker-compose.yml
...
  db:
    image: postgres:9.6
    ports:
      - "5432:5432"
    volumes:
      - pg_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=password
volumes:
  pg_data:
...
```

```yml:config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  username: postgres
  password: password
  pool: 5
```

buildし直します
```
$ docker-compose build
```

Postgresがインストールされていないのでします(ついでにMysql使わないので削除)
```:Gemfile
gem 'pg'
```
インストールとDB作成
```
$ docker-compose run --rm web bundle install
$ docker-compose run --rm web bundle exec rails db:create
```

これでDBがPostgreSQLに変更できたと思います！

# uuidを使用できるようにする
uuidを使用するための拡張機能を有効化します
```
$ docker-compose run --rm web bundle exec rails g migration enable_extension_for_uuid
```

以下のようなmigrationファイルが作成されます
```rb
class EnableExtensionForUuid < ActiveRecord::Migration[7.0]
  def change
    enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto')
  end
end
```

これをmigrateすればuuidは使用できるようになるのですが、前々回作成した`Score`テーブルのmigrationファイルも一緒に編集してしまいます。
```rb:
class CreateScores < ActiveRecord::Migration[7.0]
  def change
   create_table :scores, id: :uuid do |t|
      t.string :title

      t.timestamps
    end
  end
end
```
`id: :uuid`という部分でプライマリキーをuuidにするのを指定しています。

ではmigration。

```
$ docker-compose run --rm web bundle exec rails db:migrate
```

エラーが出ました。
`create_scores`の方のマイグレーションファイルに追記
```rb
class CreateScores < ActiveRecord::Migration[7.0]
  def change
    enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto')
    create_table :scores, id: :uuid do |t|
      t.string :title

      t.timestamps
    end
  end
end
```

`enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto')`をこちらにも記載。

再度migrationしたところ通りました！


consoleでデータを作成してみます。
```
irb(main):002:0> Score.create(title: "title")
  TRANSACTION (0.4ms)  BEGIN
  Score Create (6.2ms)  INSERT INTO "scores" ("title", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id"  [["title", "title"], ["created_at", "2022-09-19 12:46:38.505748"], ["updated_at", "2022-09-19 12:46:38.505748"]]                                                          
  TRANSACTION (1.6ms)  COMMIT                                    
=>                                                               
#<Score:0x0000aaaaf824fe48                                       
 id: "7a670050-7d74-473c-b322-0322b622b232",                     
 title: "title",                                                 
 created_at: Mon, 19 Sep 2022 12:46:38.505748000 UTC +00:00,     
 updated_at: Mon, 19 Sep 2022 12:46:38.505748000 UTC +00:00>
```

ちゃんとidが`id: "7a670050-7d74-473c-b322-0322b622b232"`とuuidになっていますね！

# その他便利そうな設定
### プライマリキーを自動的にuuidにする
マイグレーションファイルを作成すると自動的にプライマリキーがuuidになるように設定します。
`config/initializers/generators.rb`ファイルを作成。以下のように編集します。

```rb:config/initializers/generators.rb
# frozen_string_literal: true

Rails.application.config.generators do |g|
  g.orm :active_record, primary_key_type: :uuid
end
```

これで新たにマイグレーションファイルをコマンドで作ると`id: :uuid`が自動的に挿入されます。

### first、 lastをいつも通り使えるようにする
ActiveRecordのメソッドに`first`、`last`がありますが、uuidを使用すると問題が起こります。
`first`はidを昇順に並べ、最初のレコードを取ってくるのでランダムに生成されたuuidだとうまく最初のレコードをとってこれません。
`Score.order(created_at: :asc).first`というふうに書けば同じことはできるのですが、いちいち書くのはめんどくさいので、`application_record.rb`を以下のように編集。

```rb:/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  primary_abstract_class
  self.implicit_order_column = "created_at"
end
```

追記したのは`self.implicit_order_column = "created_at"`の部分。これでuuidを使う以前と同じように`first`、`last`のメソッドが使えます。

記事とか見ると`application_record.rb`ではなく個別のモデルに`self.implicit_order_column = "created_at"`を書いてるのだけど、モデルごとにメソッドの意味が変わると混乱しそうだしここに書いていいんじゃないかなぁと判断。


今日はここまで！

# 参考にさせていただいたサイト・記事
- [RailsでPostgresqlを使用する](https://qiita.com/pagu_o28/items/2f5b620499d44c85b3be)
- [[Rails] モデルのIDにUUIDを使って玄人感を出す](https://tech.mof-mof.co.jp/blog/rails-use-uuid/)
- [Postgres と MySQL における id, created_at, updated_at に関するベストプラクティス](https://zenn.dev/mpyw/articles/rdb-ids-and-timestamps-best-practices)
- [MySQLとPostgreSQLと主キー](https://speakerdeck.com/hmatsu47/mysqltopostgresqltozhu-ki)
    - auto incrementとuuid、 MySQLとPostgreSQLを実測値で比べていて納得感があった。
- [Active Record’s first and last may not mean what you think](https://andycroll.com/ruby/first-and-last-may-not-mean-what-you-think)
- [Rails 6: UUIDでfirstやlastを使う（翻訳）](https://techracho.bpsinc.jp/hachi8833/2020_10_14/90759)