RevelでGORMを使ったMySQL Associationの実装


GoのORMの[GORM](http://jinzhu.me/gorm/)を使ってアソシエーションをしようとしたらはまったのでメモ。

## RevelでMySQLに接続
gormやmysqlのインストールがまだだったら、下記コマンドで。

```bash
$ go get github.com/jinzhu/gorm
$ go get github.com/go-sql-driver/mysql
```

ModelにGORMの接続設定を書く。
DBの接続情報は`conf/app.conf`の`[dev]`セクションに書く。

```go:app/models/grom.go
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`に追記する。

```go:app/init.go
func init() {
  revel.OnAppStart(models.InitDB)
}
```

これでMySQLに接続する準備は整いました。

## 構造体の定義
`user` belongsTo `team`、`team` hasMany `users`で実装を進めます。

### 基本構造体の定義
汎用的なカラムは基本構造体として定義しちゃいます。

```go
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"`は外部キーなどが変わった名前の場合に使います。今回はなくても動きます。

```go
type User struct {
  gorm.Model
  TeamID        int
  Name          string
  Email         string
  Password      []byte
  RememberToken string
  Team          Team `gorm:"ForeignKey:TeamID;AssociationForeignKey:ID"`
}
```

### Teamの構造体を定義
```go
type Team struct {
  gorm.Model
  Name  string
  Users []User
}
```


## Query
本当は`Where()`を使いたかったんだけど、`Where()`を使うと`Related()`が発動しないみたい・・・。結果、「こ、これでいいのか・・・？」というコードに。

### OKパターン
#### 多対一の場合
```go
func GetUserByEmail(email string) (user User) {
  (*DB).Limit(1).Find(&user, "email = ?", email).Related(&user.Team)
  return
}
```

IDで取り出す場合は`First()`でもOK

```go
func GetUserById(id int) (user User) {
  (*DB).First(&user, id).Related(&user.Team)
  return
}
```

#### 一対多の場合
取り出し方は多対一と同じです。

```go
func GetTeamByName(teamName string) (team Team) {
  (*DB).Limit(1).Find(&team, "name = ?", teamName).Related(&team.Users)
  return
}
```


### NGパターン
これだと`User`は取れるんだけど、`user.Team`が取れていない（なぜかはわかっていない）。

```go
func GetUserByEmail(email string) (user User) {
  (*DB).Where("email = ?", email).First(&user).Related(&user.Team)
  return
}
```


## 参考
- http://jinzhu.me/gorm/
- https://rclayton.silvrback.com/revel-gorp-and-mysql