Association で任意の外部キー名を使う
初詣行ってきました
大吉引いて幸先良いです。
あと、巫女さんが可愛かったです。
本題
GORM の Association 関連で、任意の外部キー名を使用しようとしてハマったのでメモ。
標準の外部キー名を使う方法
その前に、まずは Convention に従った外部キー名を使う方法。
これはGORMのマニュアルにも書かれている通り。今回は Belongs-to の関連で試してみる。
今回は上記の通り、ユーザ(users)と、それに紐づくアイコンイメージ(images)をデータとして保持できるようにする。アイコンイメージには任意のイメージ名と、そのURLを持っている。
※以下の例では association という database を使用する。もともと同名の database があって、かつ users, images というテーブルがあると一旦削除されてしまうので注意。
package main import ( "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jinzhu/gorm" ) const ( dsn = "root@/association?parseTime=true&loc=Asia%2fTokyo" ) type User struct { gorm.Model Name string Image Image ImageID uint } type Image struct { gorm.Model Name string Url string } func main() { db := prepare() defer db.Close() for _, id := range []uint{1, 2, 3} { var user User fmt.Println("-------------------------------") db.First(&user, id).Related(&user.Image) fmt.Printf("名前 : %s\n", user.Name) fmt.Printf("アイコン:\n") fmt.Printf(" 名称: %s\n", user.Image.Name) fmt.Printf(" URL : %s\n", user.Image.Url) } fmt.Println("-------------------------------") } func prepare() *gorm.DB { db, err := gorm.Open("mysql", dsn) if err != nil { panic("Failed to connect database") } db.DropTableIfExists(&User{}, &Image{}) db.AutoMigrate(&Image{}) db.AutoMigrate(&User{}) var members = []map[string]string{ {"Name": "ミク", "ImageFile": "miku.jpg"}, {"Name": "マホ", "ImageFile": "maho.jpg"}, {"Name": "コヒメ", "ImageFile": "kohime.jpg"}, } for _, member := range members { image := Image{ Name: member["Name"] + "アイコン", Url: "https://image.example.com/" + member["ImageFile"], } db.Create(&image) user := User{ Name: member["Name"], ImageID: image.ID, } db.Create(&user) } return db }
実行結果
------------------------------- 名前 : ミク アイコン: 名称: ミクアイコン URL : https://image.example.com/miku.jpg ------------------------------- 名前 : マホ アイコン: 名称: マホアイコン URL : https://image.example.com/maho.jpg ------------------------------- 名前 : コヒメ アイコン: 名称: コヒメアイコン URL : https://image.example.com/kohime.jpg -------------------------------
Userのモデルは以下のように定義されている。
type User struct { gorm.Model Name string Image Image ImageID uint }
ここでは、フィールド Image が Image型であることが宣言されているが、その取得元レコードへの外部キーが ImageID であることは特に明示していない。また、問い合わせで
db.First(&user, id).Related(&user.Image)
としているが、Related()で関連レコードを取得する際にも、外部キーが ImageID であることを明示していない。
それでも Related() が問題なく外部キーを特定し関連レコードを取得できるのは、特に指示のない限り、
- 関連モデルの名称 + 'Id'
- 自身のモデルの名称 + 'Id'
を外部キーとみなすようになっているから。
ここでは Image が Image型であるため、
- ImageID
- UserID
の順に外部キーを探していき、同名のキーが見つかった時点でそれを使用する。
任意の外部キーを使う方法
ところが、外部キーの名称を (モデル名) + 'Id' 「以外」にしたいケースというのも多々ある。例えば今回はアイコンイメージのIDを ImageID というキー名で参照しているが、これはあまり良い名称とは言えない。アイコンイメージであるなら分かりやすく IconImageID としたほうがいいし、また背景画像など、同じ Image モデルを参照する別のフィールドが追加されたときは、少なくともどちらかは ImageID 以外の外部キー名を使わなければならない。
そこで User モデルの ImageID を IconImageID に変更してみる。
type User struct { gorm.Model Name string IconImage Image IconImageID uint }
名称変更自体はこれで良いのだが、IconImageID というキー名はこのままでは外部キーとして認識されない。これを解決する方法はいくつかあるが、GORM ドキュメントの Association の項で触れられている範囲内では2つのやり方がある。
Related() で明示的に外部キーを指定する方法
Related() の第2引数で外部キー名を明示的に指定することができる。
db.First(&user, id).Related(&user.IconImage, "IconImageID")
お手軽。
Association Mode を使う
モデルを宣言する際、モデル間の関連についてタグで指定することができる。
type User struct { gorm.Model Name string IconImage Image `gorm:"ForeignKey:IconImageID"` IconImageID uint }
上記のようにすると、「IconImage でレコードを取得する際は、外部キーとして IconImageID を使用する」と明示的に指示できる。
そして関連レコードを取得する際は、
db.First(&user, id).Association("IconImage").Find(&user.IconImage)
のように、Association で取得対象のフィールド名を指定すれば、タグで指定したものが外部キーとして使用される。
※修正済みのソースはこちら