Go難しいです。gormは「なんでやねん」と思うことが多いです。
元Railsエンジニアには早すぎた技術なのかもしれません🥺
Many To Many周りの公式の記事が少しわかりにくくて、tipsを書いておきます
Many To Many (many2many)
gormではPreload()する際に、構造体のフィールドのタグ(reflect.StructTag)を仕込むことで、中間テーブルを簡単にORM側に理解させることができます。
普通の書き方
gorm内部的には、Fieldという構造体でTagSettings map[string]stringで値を持っていて、全部大文字に変換してるのでmap[MANY2MANY:many2many:user_languages]という感じで保存してあります
// usersテーブル
type User struct {
  ID        uint
  Languages []*Language `gorm:"many2many:user_languages;"`
}
// languagesテーブル
type Language struct {
  ID   uint
  Name string
}
// 中間テーブルの構造体を定義せずに、Eager Loadingが実行できる
DB.Model(&User{}).Preload("Languages")
テーブル名と構造体名が違う書き方
こういう書き方がGo的に良いか悪いかどうかは置いといて😅
Languageの構造体にフィールドを追加したくなくて、Languageと同じフィールドを持つLanguageWithOwnを作成した時の、many2manyの書き方で
foreignKey, joinForeignKey, References, joinReferencesの4つを設定する必要があります
前述の通り、大文字小文字は内部で吸収されているので関係ないです。
// usersテーブル
type User struct {
  ID        uint
  Languages []*OwnLanguage `gorm:"many2many:user_languages;foreignKey:id;joinForeignKey:user_id;References:id;joinReferences:language_id;"`
}
// foreignKey:id
// joinForeignKey:user_id
// References:id
// joinReferences:language_id
// languagesテーブル
type LanguageWithOwn struct {
  ID   uint
  Name string
  Own  bool // 母国語かどうか
}
// 構造体とテーブル名を一致させる
func (l *LanguageWithOwn) TableName() string {
	return "languages"
}
// Eager Loadingが実行され、User.Languagesに値が入る
DB.Model(&User{}).Preload("Languages")
あんまGoのライブラリのソース読まないけど、jsのライブラリとテイストは似てるなと思った(所感)