🙆♀️
GORMで使う構造体のフィールドにユーザー定義型を使っていいパターン・使えないパターン
検証環境
Go: 1.18.3
GORM: 1.23.6
基本的にはユーザー定義型は使用できない
当たり前っちゃ当たり前ですが、独自定義型をGORMは解釈できません。
以下は、usersテーブルに、レコードを追加する例です。
main.go
// Userモデル
// Ageに後述のUserAge型を使用
type User struct {
gorm.Model
Name string
Email string
Age UserAge
}
// Ageモデル
type UserAge struct {
Num string
}
func main() {
...(DBに接続するとこは割愛)
age := UserAge{Num: "20"}
user := User{
Name: "Gorm Taro",
Email: "gorm@example.com",
Age: age,
}
result := DB.Create(&user)
}
次のようなエラーが返ります。
[error] invalid field found for struct main.User's field Age: define a valid foreign key for relations or implement the Valuer/Scanner interface
英語が読めなのでよくわかりませんが、なんか言ってます。
プリミティブ型をラップしただけのものは使用できる
僕が使えるって言ったのはこういうやつね。
main.go
// Userモデル
// Ageに後述のAge型を使用
type User struct {
gorm.Model
Name string
Email string
Age UserAge
}
// Ageモデル
type UserAge string // stringをラップしただけの型
公式ドキュメントでこのことに明言している箇所を見つけられなかったが、当たり前だからかな?(書いてるけど僕が見つけれてないだけが濃厚)
Valuer/Scannerを定義してあげると使えるようになる
GORMさんがユーザー定義型を解釈できないというのなら、教えてあげればいいじゃない!
ということで、教えるためのインターフェースがあります。
それが、Valuer/Scanner
になります。
詳しくは公式ドキュメントを見ていただければと思うので、ここでは簡単に。
Valuer => DB書き込み時に変換する
Scanner => 読み込み時に変換する
変換の仕方を書いてあげましょう。
main.go
type User struct {
gorm.Model
Name string
Email string
Age UserAge
}
// こいつに対してValueとScanを定義してあげる
type UserAge struct {
Num string `json:"num"`
}
// 書き込み時にstringに変換する
func (u UserAge) Value() (driver.Value, error) {
bytes, err := json.Marshal(u)
if err != nil {
return nil, err
}
return string(bytes), nil
}
// 読み出し時に変換する
func (u *UserAge) Scan(input interface{}) error {
switch v := input.(type) {
case string:
return json.Unmarshal([]byte(v), u)
case []byte:
return json.Unmarshal(v, u)
default:
return fmt.Errorf("unsupported type: %T", input)
}
}
これさえ設定してあげれば、ユーザー定義型を普通に使えます。
ちなみにDBにはこんなふうに入ります。
+----+----------------------------------------------------+
| id | ... | name | email | age |
+----+----------------------------------------------------+
| 1 | ... | Gorm Taro | gorm@example.com | {"num":"2020"}|
+----+----------------------------------------------------+
せっかくなんで、読み込みの方もチェックしましょう。
main.go
user := User{}
result := DB.First(&user)
if result.Error != nil {
panic(result.Error)
}
fmt.Printf("user: %+v\n", user1)
fmt.Printf("age: %T\n", user1.Age)
user: ... Name:Gorm Taro Email:gorm@example.com Age:{Num:2020}}
name: string
age: main.UserAge // 読み込んだだけで、目的の型に変換された!
正直こんなん書くのだるいと思う。
まとめ
ActiveRecordが恋しい
Discussion