Go言語のGORMライブラリの規則でハマった（テーブル名編）

# はじめに

Go言語で開発されたORM （Object-Relational Mapping）ライブラリであるGORMで用いられるテーブル名に少しハマったのでメモしておきます。

### GORMとは
Go言語で開発されたORM （Object-Relational Mapping）ライブラリです。

https://gorm.io/ja_JP/docs/index.html

# 検証環境

こちらの記事で構築した環境を利用しました。

https://qiita.com/sedori/items/840e39a0cbf9d5bff006

# 問題点（ハマったポイント

GORMライブラリのドキュメントには次のような記載がありました。

:::note warn
テーブル名: デフォルトでは、GORMは構造体名を スネークケース に変換し、テーブル名を複数形にします。 例えば、 User 構造体は、データベースの users テーブルになります。
:::

https://gorm.io/ja_JP/docs/models.html


これ、少し気になりますよね。

というのも、名詞の複数形にはいろいろな形があります。

| 単数名詞 | 複数名詞 | 意味 | 備考 |
| ---- | ---- | ---- | ---- |
| product | products | 製品 | - |
| country | countries | 国 | 子音 + -y |
| tomato | tomatoes | トマト | 子音 + -o |
| shelf | shelves | 棚 | -f, -fe |
| bus | buses | バス | -s |
| tooth | teeth | 歯 | 不規則変化 |
| leaf | leaves | 葉 | 不規則変化 |
| fungus  | fungi | 菌類 | 不規則変化 |
| man | men | 男 | 不規則変化 |

単純な変化だけでなく、不規則変化する名詞もこの規則どおりの挙動になるのでしょうか。
今回はそれを確認していきます。

## 検証方法
今回は検証を行うだけなので、単数名詞の構造体を作成し、DBにテーブルは用意せずにDBアクセスして、SQLからテーブル名を確認してみました。

```golang:main.go
package main

import (
    "github.com/gin-gonic/gin"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)


type Product struct { // 書き換えポイント１
  Id int
  Name string
}

func main() {
    engine:= gin.Default()
    engine.GET("/", func(c *gin.Context) {
        //DB接続
        dsn := "root@tcp(mysql:3306)/first?charset=utf8mb4&parseTime=True&loc=Local"
        db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
        if err != nil {
            panic("failed to connect database")
        }
        // レコードを1行取得
        db.First(&Product{}) // 書き換えポイント２
    })
    engine.Run(":8080")
}
```

## 検証結果

| 構造体名 | SQL | 考察 |
| ---- | ---- | ---- |
| Product | ``SELECT * FROM `products` ORDER BY `products`.`id` LIMIT 1`` | productsなので期待通り |
| Country | ``SELECT * FROM `countries` ORDER BY `countries`.`id` LIMIT 1`` | countriesなので期待通り |
| Tomato | ``SELECT * FROM `tomatoes` ORDER BY `tomatoes`.`id` LIMIT 1`` | tomatoesなので期待通り |
| Shelf | ``SELECT * FROM `shelves` ORDER BY `shelves`.`id` LIMIT 1`` | shelvesなので期待通り |
| Bus | ``SELECT * FROM `buses` ORDER BY `buses`.`id` LIMIT 1`` | busesなので期待通り |

ここまでは通常の規則変化なので、期待値通りなのも頷けますね。

問題は不規則変化の名詞を用いた場合です。

| 構造体名 | SQL | 考察 |
| ---- | ---- | ---- |
| Tooth | ``SELECT * FROM `tooths` ORDER BY `tooths`.`id` LIMIT 1`` | teethにならずtoothsになってしまう |
| Leaf | ``SELECT * FROM `leafs` ORDER BY `leafs`.`id` LIMIT 1`` | leavesにならずleafsになってしまう |
| Fungus  | ``SELECT * FROM `fungus` ORDER BY `fungus`.`id` LIMIT 1`` | fungiにならずfungusのまま |
| Man | ``SELECT * FROM `men` ORDER BY `men`.`id` LIMIT 1`` | 期待通りなのだが、上記の例があるのでこれが不規則変化するのは納得しづらい |

# まとめ

たとえGORMライブラリの規約に従ってDB設計を行ったとしても、不規則変化する名詞を用いる場合にはその限りではないようなので注意が必要というお話でした。

一応この規則の影響を受けない実装方法がいくつかあるので、それに利用するかどうかも含めてケースバイケースということですね。

# Tips

## テーブル名を直接指定する方法

```golang
db.Table("fungi").First(&Fungus{}) //  "SELECT * FROM `fungi` ORDER BY `fungi`.`id` LIMIT 1"になる
```

小規模なプログラムであればこれでも良いかもしれませんが、メンテナンス性は悪そうですね。

## 構造体とテーブル名を紐づける方法

```golang
// 構造体
type Fungus struct {
  Id int
  Name string
}

// 構造体と紐づくテーブル名を定義する
func (l *Fungus) TableName() string {
    return "fungi"
}

...

db.First(&Fungus{}) //  "SELECT * FROM `fungi` ORDER BY `fungi`.`id` LIMIT 1"になる
```

この使い方であればいちいち書き換える必要はありませんが、直感的でないところが問題ですね。