【Rails】マイグレーションファイルでidではなくuuidを外部キーに設定する方法

# はじめに
こんにちは！オンラインスクールでRuby on Railsを中心に学習中のくりと申します🐣

uuidを使用するテーブルを外部キーとして参照しようとした際、
マイグレーションファイルの適用に失敗し書き方につまずいたため、備忘録としてまとめます。

:::note warn
初学者のアウトプット記事です。間違いやより良い方法がありましたら、コメント等で教えていただけますと幸いです🙇‍♀️
:::

# 前置き
travel_booksテーブルとnotesテーブルはリレーションを組むようにします。
travel_booksテーブルにはuuidを採用しており、notesテーブルに外部キーとして
travel_booksテーブルのuuidを参照させます。

<details><summary>補足：uuid導入の経緯</summary>
開発中の旅行のしおりを投稿するサービスでは、ユーザーがしおりを投稿する際に公開・非公開を選択できるようにしています。
通常データベースのidには連番が用いられますが、連番のidはURLから容易に推測されやすく、非公開のしおりでも第三者から閲覧されるリスクがありました。
それを回避するためにランダムな文字列であるuuidを導入しました。
</details>


```mermaid
erDiagram
    TravelBooks ||--o{ Notes : contains

    TravelBooks {
        uuid id PK
        string title
        datetime start_date
        datetime end_date
    }

    Notes {
        uuid id PK
        uuid travel_book_id FK
        string title
        text body
    }
```
※上記のテーブルは簡易的な構造に書き換えています

### 環境
- macOS（Apple Silicon）
- Docker
- Ruby 3.2.3
- Rails 7.2.1
- PostgreSQL

# 結論
references型カラムに、参照する外部キーの型(`type: :uuid`)を指定し、
foreign_keyオプションで明示的に外部キーとして参照するテーブルとカラム(`foreign_key: { to_table: :<参照先テーブル名>, primary_key: :<カラム名> }`)を指定する必要がありました。

```ruby
class CreateNotes < ActiveRecord::Migration[7.2]
  def change
    create_table :notes, id: :uuid do |t|
      t.references :travel_book, null: false, type: :uuid, foreign_key: { to_table: :travel_books, primary_key: :uuid }
      t.string :title, null: false
      t.text :body
      t.timestamps
    end
  end
end
```
Railsガイドに既存のカラムに外部キー制約をつける場合記載があったため、こちらを参考にしました。
Railsガイド引用(※1)：
> 参照される主キーを持つテーブルから、外部キーを追加するテーブルのカラム名を導出できない場合は、:columnオプションでカラム名を指定できます。また、参照される主キーが:idでない場合は、:primary_keyオプションを利用できます。
> ```
> add_foreign_key :articles, :authors, column: :reviewer, primary_key: :email
> ```
> 上はarticlesテーブルに制約を追加します。この制約は、emailカラムがarticles.reviewerフィールドと一致する行が、authorsテーブルに存在することを保証します。
> たとえば、authors.emailを参照するarticles.reviewerに外部キーを追加するには以下のようにします。

# つまったポイント

### foreign_keyオプションでテーブルとカラムを指定しない場合、外部キーがuuidを参照できずエラーになる

最初はRilsガイドの内容から、referencesカラムに`type: :uuid`をつけるだけで、参照先のテーブルのidではなく、uuidを参照するようになるのかと思いました。

```ruby
class CreateNotes < ActiveRecord::Migration[7.2]
  def change
    create_table :notes, id: :uuid do |t|
      t.references :travel_book, type: :uuid, foreign_key: true ## ここ
      t.string :title, null: false
      t.text :body
      t.timestamps
    end
  end
end
```

Railsガイド引用(※2)：
> 2. UUIDで参照を追加する
> モデル間の関連付けを参照で作成するときは、主キーの種別との一貫性を維持するために、以下のようにデータ型を:uuidとして指定します。
>```
>create_table :posts, id: :uuid do |t|
>  t.references :author, type: :uuid, foreign_key: true
>  # 他のカラム...
>  t.timestamps
>end
>```
> この例では、postsテーブルのauthor_idカラムはauthorsテーブルのidカラムを参照しています。主キーの種別を明示的に:uuidに設定することで、外部キーカラムが参照する主キーのデータ型と一致することが保証されます。他の関連付けやデータベースに合わせて構文を調整してください。


しかし、マイグレーションファイルを適用すると、以下のエラーが出力されてしまいました。
id以外のカラムを外部キーとして参照する際は、foreign_keyオプションに明示的に参照するテーブルとカラムを記述する必要があるようでした。

```
PG::UndefinedColumn: ERROR:  column "id" referenced in foreign key constraint does not exist
```

# 参考・引用資料
### 引用
Railsガイド引用(※1)

https://railsguides.jp/v7.2/active_record_migrations.html#%E5%A4%96%E9%83%A8%E3%82%AD%E3%83%BC

Railsガイド引用(※2)

https://railsguides.jp/v7.2/active_record_migrations.html#%E3%81%9D%E3%81%AE%E4%BB%96


### 参考
https://qiita.com/kymmt90/items/03cb9366ff87db69f539