【学習・エラー】Perlのinflateで解決！DBのID→名前変換を実装してみた

## はじめに
以前、職業と飲み物のidを設定することで、更新を容易にできるようにしました。

`mysql`のファイルを変更しただけでは反映されないので`DB\Schema.pm`ファイルも編集したのですが、いざサーバを起動しようとしたタイミングでエラーとなりました。

今回はエラーとそこからの学び、という内容でまとめたいと思います。
👇`DB\Schema.pm`ファイルの内容は下記記事になります。

https://qiita.com/wa-chan222/items/e54476388219c05e3d3b


## エラー内容把握
さて、早速ですが、なにやら大量のメッセージが表示されています。
```
$ carton exec perl -Ilib script/questionary1-server
String found where operator expected at script/../lib/questionary1/DB/Schema.pm line 19, near "belongs_to 'occupation'"
        (Do you need to predeclare belongs_to?)
String found where operator expected at script/../lib/questionary1/DB/Schema.pm line 20, near "belongs_to 'drink'"
        (Do you need to predeclare belongs_to?)
syntax error at script/../lib/questionary1/DB/Schema.pm line 19, near "belongs_to 'occupation'"
Compilation failed in require at script/../lib/questionary1.pm line 7.
BEGIN failed--compilation aborted at script/../lib/questionary1.pm line 7.
Compilation failed in require at /usr/lib/x86_64-linux-gnu/perl-base/parent.pm line 16.
BEGIN failed--compilation aborted at script/../lib/questionary1/Web.pm line 5.
Compilation failed in require at script/questionary1-server line 10.
BEGIN failed--compilation aborted at script/questionary1-server line 10.
```

どうやら、`belongs_to` の使い方が間違っている のが原因で、Perl が正しく解析できていないことが読み取れます😥


## 問題のファイルを確認
問題のファイルと指定された文字の箇所を確認します
```.DB\Schema.pm
table {
    name 'member';
    pk 'id';
    columns qw(id name birthdate occupation_id drink_id remarks created_at updated_at);
    
    # 外部キー制約を設定
    belongs_to 'occupation' => 'questionary1::DB::Occupation', 'occupation_id';
    belongs_to 'drink' => 'questionary1::DB::Drink', 'drink_id';
};
```

`belongs_to` の使い方が間違っている、という事はメッセージからわかっているので解決に向け3つ試してみました。


### 試したこと1
- **belongs_to を カラム定義の外側 で定義してみる**
`belongs_to`が間違った場所にある事で単なる文字列として認識していることが原因かと思ったから。
    ```perl
    table {
        name 'member';
        pk 'id';
        columns qw(id name birthdate occupation_id drink_id remarks created_at updated_at);
    };
    
    # 外部キー制約を設定
    belongs_to 'occupation' => 'questionary1::DB::Occupation', 'occupation_id';
    belongs_to 'drink' => 'questionary1::DB::Drink', 'drink_id';
    ```
    結果➡️エラー解消ならず😥


### 試したこと2
- **memberテーブルと同じように記述してみる**
そもそもTengの機能に`belongs_to`というものは定義されていないと思った。（Rubyの感覚で記述していた💦）

    ```perl
    # member テーブルのカラムを定義
    table {
        name 'member';
        pk 'id';
        columns qw(id name birthdate occupation_id drink_id remarks created_at updated_at);
    };
    
    # occupation テーブルのカラムを定義
    table {
        name 'occupation';
        pk 'id';
        columns qw(id name created_at updated_at);
    };
    
    # drink テーブルのカラムを定義
    table {
        name 'drink';
        pk 'id';
        columns qw(id name created_at updated_at);
    };
    ```
    結果➡️エラー解消😊


### 試したこと3
-  **Teng（PerlのORM）の`inflate`を使用**

https://github.com/perl-entrance-org/Perl-Entrance-Textbook/blob/master/orm.md

 👇上記URLから一部抜粋
> inflateとdeflate
    Tengには, inflateとdeflateという仕組みがあります. inflateを利用することで, Tengがデータベースからデータを取り出してオブジェクトにする際に任意のカラムの値に対して任意の処理を挟むことが出来ます. deflateはその逆で, Tengからデータベースにデータを書き込む際に任意のカラムの値に対して任意の処理を挟むことが出来ます.

以下のように`inflate`を使って`occupation_id`と`drink_id` に関連するデータを取得できるように修正していきます

```perl
    table {
        name 'member';
        pk 'id';
        columns qw(id name birthdate occupation_id drink_id remarks created_at updated_at);
    
        # occupation_id から occupation テーブルのデータを取得
        inflate 'occupation_id' => sub {
            my ($col_value, $row) = @_;
            return $row->handle->single('occupation', { id => $col_value });
        };
    
        # drink_id から drink テーブルのデータを取得
        inflate 'drink_id' => sub {
            my ($col_value, $row) = @_;
            return $row->handle->single('drink', { id => $col_value });
        };
    };
```
結果➡️エラー解消😊


## 試したこと3を更に詳しく`inflate`何ぞや？
Teng（PerlのORM）である`inflate`を使うことで、`occupation_id`や`drink_id`のIDを、その**関連するテーブルのデータ**に変換できるようにしている

#### **`inflate` の仕組み**
- `inflate` は **データベースのカラムの値を、より扱いやすいオブジェクトに変換するための関数**
- 例えば、`member` テーブルには `occupation_id` というカラムがありますが、通常このカラムには **数字（ID）** が入っている
- `inflate` を使うことで、`occupation_id` を **実際の occupation テーブルのデータ** に変換できる


#### **`inflate 'occupation_id' => sub { ... }` の意味**

```perl
inflate 'occupation_id' => sub {
    my ($col_value, $row) = @_;
    return $row->handle->single('occupation', { id => $col_value });
};
```

- `inflate` はカラムの変換ルールを定義するための関数。
- `sub { ... }` は **無名関数（関数に名前をつけずに定義するもの）**
- `my ($col_value, $row) = @_;`
    - `$col_value` は `occupation_id` の数値（例: `1` や `2`）
    - `$row` は `member` テーブルの1行のデータを扱うオブジェクト
- `return $row->handle->single('occupation', { id => $col_value });`
    - `$col_value` の ID に一致する `occupation` のレコードを取得して返す

→ **つまり、`member` の `occupation_id` を `occupation` テーブルのデータに変換する処理をしている。**

---

#### **`inflate 'drink_id' => sub { ... }` の意味**

```perl
inflate 'drink_id' => sub {
    my ($col_value, $row) = @_;
    return $row->handle->single('drink', { id => $col_value });
};
```

- これも `occupation_id` と同じ仕組み
- `drink_id` の **数値ID** を、`drink` テーブルの実際のデータに変換して返す


## さいごに
今回のエラーから`inflate`を活用することで、関連データを適切に取得できることを知ることができました。
どんな言語でもいえる事ですが、今まで学んできたモノはそのまま別の言語でも使える、という感覚でいると思わぬエラーに繋がることがあります💦
公式ドキュメントや他の実装例を確認しながら、適切な記述を心がけたい所です。