GoのORMのGORMを使ってアソシエーションをしようとしたらはまったのでメモ。
RevelでMySQLに接続
gormやmysqlのインストールがまだだったら、下記コマンドで。
$ go get github.com/jinzhu/gorm
$ go get github.com/go-sql-driver/mysql
ModelにGORMの接続設定を書く。
DBの接続情報はconf/app.conf
の[dev]
セクションに書く。
package models
import (
"github.com/revel/revel"
"github.com/jinzhu/gorm"
"strings"
"time"
"fmt"
_"github.com/go-sql-driver/mysql"
)
var DB **gorm.DB
func InitDB() {
db, err := gorm.Open("mysql", getConnectionString())
if err != nil {
revel.ERROR.Println("FATAL", err)
panic(err)
}
db.DB()
DB = &db
}
type Model struct {
gorm.Model
ID uint `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
}
type Validator interface {
IsSatisfied(interface{}) bool
DefaultMessage() string
}
func getParamString(param string, defaultValue string) string {
p, found := revel.Config.String(param)
if !found {
if defaultValue == "" {
revel.ERROR.Fatal("Cound not find parameter: " + param)
} else {
return defaultValue
}
}
return p
}
func getConnectionString() string {
host := getParamString("db.host", "localhost")
port := getParamString("db.port", "3306")
user := getParamString("db.user", "username")
pass := getParamString("db.password", "password")
dbname := getParamString("db.name", "dbname")
protocol := getParamString("db.protocol", "tcp")
dbargs := getParamString("dbargs", " ")
timezone := getParamString("db.timezone", "parseTime=true&loc=Asia%2FTokyo")
if strings.Trim(dbargs, " ") != "" {
dbargs = "?" + dbargs
} else {
dbargs = ""
}
return fmt.Sprintf("%s:%s@%s([%s]:%s)/%s%s?%s", user, pass, protocol, host, port, dbname, dbargs, timezone)
}
これをアプリケーション起動時に実行されるようにapp/init.go
に追記する。
func init() {
revel.OnAppStart(models.InitDB)
}
これでMySQLに接続する準備は整いました。
構造体の定義
user
belongsTo team
、team
hasMany users
で実装を進めます。
基本構造体の定義
汎用的なカラムは基本構造体として定義しちゃいます。
type Model struct {
ID uint `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
}
Userの構造体を定義
gorm.Model
は基本構造体を使うよ!という宣言になります。宣言しない場合は、IDから自分で書きます。
gorm:"ForeignKey:TeamID;AssociationForeignKey:ID"
は外部キーなどが変わった名前の場合に使います。今回はなくても動きます。
type User struct {
gorm.Model
TeamID int
Name string
Email string
Password []byte
RememberToken string
Team Team `gorm:"ForeignKey:TeamID;AssociationForeignKey:ID"`
}
Teamの構造体を定義
type Team struct {
gorm.Model
Name string
Users []User
}
Query
本当はWhere()
を使いたかったんだけど、Where()
を使うとRelated()
が発動しないみたい・・・。結果、「こ、これでいいのか・・・?」というコードに。
OKパターン
多対一の場合
func GetUserByEmail(email string) (user User) {
(*DB).Limit(1).Find(&user, "email = ?", email).Related(&user.Team)
return
}
IDで取り出す場合はFirst()
でもOK
func GetUserById(id int) (user User) {
(*DB).First(&user, id).Related(&user.Team)
return
}
一対多の場合
取り出し方は多対一と同じです。
func GetTeamByName(teamName string) (team Team) {
(*DB).Limit(1).Find(&team, "name = ?", teamName).Related(&team.Users)
return
}
NGパターン
これだとUser
は取れるんだけど、user.Team
が取れていない(なぜかはわかっていない)。
func GetUserByEmail(email string) (user User) {
(*DB).Where("email = ?", email).First(&user).Related(&user.Team)
return
}