mysqlgogo-gormrevel

How to access Gorm in Revel Controller?


let me start by saying these are my first couple days of toying around in Go.

I'm trying to use the Revel framework with Gorm like this:

app/controllers/gorm.go

package controllers

import (
    "fmt"
    "go-testapp/app/models"

    _ "github.com/go-sql-driver/mysql"
    "github.com/jinzhu/gorm"
    "github.com/revel/revel"
)

var DB gorm.DB

func InitDB() {
    var err error
    DB, err = gorm.Open("mysql", "root:@/go-testapp?charset=utf8&parseTime=True")
    if err != nil {
        panic(err)
    }
    DB.LogMode(true)
    DB.AutoMigrate(models.User{})
}

type GormController struct {
    *revel.Controller
    DB *gorm.DB
}

app/controller/app.go

package controllers

import (
    "fmt"
    "go-bingo/app/models"

    _ "github.com/go-sql-driver/mysql"
    "github.com/revel/revel"
)

type App struct {
    GormController
}

func (c App) Index() revel.Result {
    user := models.User{Name: "Jinzhu", Age: 18}

    fmt.Println(c.DB)
    c.DB.NewRecord(user)

    c.DB.Create(&user)

    return c.RenderJson(user)
}

After running it results in:

runtime error: invalid memory address or nil pointer dereference on line 19 c.DB.NewRecord(user)

It successfully creates the datatables with automigrate, but I have no idea how I should use Gorm in my controller.

Any hints in the right direction?


Solution

  • Important note

    it's just replacement for GORP of original example of the Revel. And it comes with some pitfalls of the origin. This answer can be used as drop-in replacement for the original one. But it doesn't solve the pitfalls.

    Please, take a look comments of this unswer and @MaxGabriel's answer that solves the pitfalls.

    I'd recommend to use @MaxGabriel's solution to protect you application against some kinds of slow-* DDoS attacks. And to reduce (in some cases) DB pressure.

    Original answer

    @rauyran rights, you have to invoke InitDB inside init function (into controllers package).

    Full example here (too much):

    Tree

    /app
        /controllers
          app.go
          gorm.go
          init.go
        /models
          user.go
    [...]
    

    user.go

    // models/user.go
    package models
    
    import  "time" // if you need/want
    
    type User struct {          // example user fields
        Id                    int64
        Name                  string
        EncryptedPassword     []byte
        Password              string      `sql:"-"`
        CreatedAt             time.Time
        UpdatedAt             time.Time
        DeletedAt             time.Time     // for soft delete
    }
    

    gorm.go

    //controllers/gorm.go
    package controllers
    
    import (
        "github.com/jinzhu/gorm"
        _ "github.com/lib/pq" // my example for postgres
        // short name for revel
        r "github.com/revel/revel"
        // YOUR APP NAME
        "yourappname/app/models"
        "database/sql"
    )
    
    // type: revel controller with `*gorm.DB`
    // c.Txn will keep `Gdb *gorm.DB`
    type GormController struct {
        *r.Controller
        Txn *gorm.DB
    }
    
    // it can be used for jobs
    var Gdb *gorm.DB
    
    // init db
    func InitDB() {
        var err error
        // open db
        Gdb, err = gorm.Open("postgres", "user=uname dbname=udbname sslmode=disable password=supersecret")
        if err != nil {
            r.ERROR.Println("FATAL", err)
            panic( err )
        }
        Gdb.AutoMigrate(&models.User{})
        // unique index if need
        //Gdb.Model(&models.User{}).AddUniqueIndex("idx_user_name", "name")
    }
    
    
    // transactions
    
    // This method fills the c.Txn before each transaction
    func (c *GormController) Begin() r.Result {
        txn := Gdb.Begin()
        if txn.Error != nil {
            panic(txn.Error)
        }
        c.Txn = txn
        return nil
    }
    
    // This method clears the c.Txn after each transaction
    func (c *GormController) Commit() r.Result {
        if c.Txn == nil {
            return nil
        }
        c.Txn.Commit()
        if err := c.Txn.Error; err != nil && err != sql.ErrTxDone {
            panic(err)
        }
        c.Txn = nil
        return nil
    }
    
    // This method clears the c.Txn after each transaction, too
    func (c *GormController) Rollback() r.Result {
        if c.Txn == nil {
            return nil
        }
        c.Txn.Rollback()
        if err := c.Txn.Error; err != nil && err != sql.ErrTxDone {
            panic(err)
        }
        c.Txn = nil
        return nil
    }
    

    app.go

    package controllers
    
    import(
        "github.com/revel/revel"
        "yourappname/app/models"
    )
    
    type App struct {
        GormController
    }
    
    func (c App) Index() revel.Result {
        user := models.User{Name: "Jinzhup"}
        c.Txn.NewRecord(user)
        c.Txn.Create(&user)
        return c.RenderJSON(user)
    }
    

    init.go

    package controllers
    import "github.com/revel/revel"
    
    func init() {
        revel.OnAppStart(InitDB) // invoke InitDB function before
        revel.InterceptMethod((*GormController).Begin, revel.BEFORE)
        revel.InterceptMethod((*GormController).Commit, revel.AFTER)
        revel.InterceptMethod((*GormController).Rollback, revel.FINALLY)
    }
    

    As you can see, it's like Revel's Booking modified for GORM.

    Works fine for me. Result:

    {
      "Id": 5,
      "Name": "Jinzhup",
      "EncryptedPassword": null,
      "Password": "",
      "CreatedAt": "2014-09-22T17:55:14.828661062+04:00",
      "UpdatedAt": "2014-09-22T17:55:14.828661062+04:00",
      "DeletedAt": "0001-01-01T00:00:00Z"
    }