gormのmany2manyはこうすると上手く行く


Go言語のgormというORマッパーを使った時のハマったところとその解決についてです。ほぼ自分用メモです。
尚、2021/06/23現在の最新バージョン gorm.io/gorm v1.21.11 を使用しています。

駄目だった実装

https://gorm.io/ja_JP/docs/many_to_many.html
gormのドキュメントを見ながら書いた最初のコードはこんな感じでした。

type Person struct {
    PersonId       string    `json:"person_id" gorm:"primaryKey;autoIncrement:false"`
    Languages []Language `gorm:"many2many:person_languages;"`
}

type Language struct {
    LanguageCode   string `json:"language_code" gorm:"primaryKey;autoIncrement:false"`
    Name   string `json:"name"`
}

type PersonLanguages struct {
    PersonId string `json:"person_id" gorm:"primaryKey;autoIncrement:false"`
    LanguageCode  string `json:"language_code" gorm:"primaryKey;autoIncrement:false"`
}

ここから、personを取得した時に紐づくlanguagesを一気に取得したい、というのがもともとの動機です。

しかし、

result := DB.First(&person)

とした時、personに紐付いている中間テーブルを介して取得できるlanguagesがあるはずなのに、 len(person.Languages) はゼロ。

gormのドキュメントを見ただけでは、なぜうまく行かないのかわからず。

上手く行った実装

  1. Preloadする
  2. キーとなるカラム名からテーブル名prefixを取る

この2点が必要でした。

1. Preloadする

DB.Preload("Languages").First(&person)

参照
https://note.com/note_fumi/n/n29a8d64ce46d

2. キーとなるカラム名からテーブル名prefixを取る

PersonのPersonIdをIdに、

LanguageのLanguageCodeをCodeにするべきでした。

type Person struct {
    Id       string    `json:"id" gorm:"primaryKey;autoIncrement:false"`
    Languages []Language `gorm:"many2many:person_languages;"`
}

type Language struct {
    Code   string `json:"code" gorm:"primaryKey;autoIncrement:false"`
    Name   string `json:"name"`
}

こういうことです。当然ですが、マイグレーションからやり直す必要があります。

こうすると、

DB.Preload("Languages").First(&person)

person.Languages でpersonに紐づくLanguagesがちゃんと取得できます。

ドキュメントにこのような記載が無かったので少し混乱しましたが、自分の仮説が当たっていたようでした。(すごく熟読したわけじゃないので、実は書いてあるかも?)

調査していないですが、gormの挙動から想像するに、has manyでも同じことが起きそうです。

※元のコードから脚色を加えたものを掲載しているため、このままでは上手く動かない可能性があります。

記事を書き慣れてないので、読みづらかったらすみません。