How do I access the Gorm in the Revel Controller?
Let me start by saying that these are my first couple of days playing Go.
I am trying to use 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 its result:
runtime error: invalid memory address or nil pointer dereference
on line 19 c.DB.NewRecord(user)
It creates data successfully with automation, but I have no idea how to use Gorm in the controller.
Any clues in the right direction?
source to share
Important note
it is just a replacement for the GORP original Revel example . And this is due to some of the pitfalls of origin. This answer can be used as a replacement for the original one. But that doesn't solve the pitfalls.
Please see the comments of this answer and @ MaxGabriel's answer which resolves the pitfalls.
I would recommend using @MaxGabriel's solution to protect your application from some kind of slow DDoS attacks. And reduce (in some cases) the OBD pressure.
Original Answer
@rauyran rights , you must call the function InitDB
inside init
(in the package controllers
).
Full example here (too many):
Wood
/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, this is similar to Revel Booking modified for GORM.
Works great 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"
}
source to share
Your error is caused by not initializing the c.DB database variable, it is still zero.
In your controller file /init.go make sure you call revel.OnAppStart (InitDB). It should look something like this:
package controllers
import "github.com/revel/revel"
func init() {
revel.OnAppStart(InitDB)
// maybe some other init things
}
source to share
This answer is derived from @ IvanBlack's answer, as suggested by him. Its version is a direct translation of the Revel example code, but we have identified some issues with the source code, which is fixed by this answer. Major changes:
-
The entire HTTP request is no longer completed with a database transaction. Wrapping the entire HTTP request takes much longer than necessary to open the transaction, which can cause a number of problems:
- Your transactions will store locks in the database, and many transactions containing locks can lead to locks.
- Additional database resources are being used
- These problems increase if your HTTP requests are mostly not restricted to SQL database access. For example. if your HTTP request is blocked for 30 seconds while making an external HTTP request or accessing another database, slowing down these services could affect your SQL database.
-
Since the transaction is no longer automatically checked for errors at the end of the HTTP request, errors are checked immediately after the database is inserted. This is also more correct: imagine that after inserting the User structure into the database, you then save it
User.Id
to another database, such as Redis. It would be nice if the database insert worked, but if it fails and you don't check for the error right away, you should insert the default int64 from 0 into Redis (before rolling back only the SQL transaction later).
Wood
/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"
)
// type: revel controller with `*gorm.DB`
type GormController struct {
*r.Controller
DB *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=USERNAME dbname=DBNAME sslmode=disable")
Gdb.LogMode(true) // Print SQL statements
if err != nil {
r.ERROR.Println("FATAL", err)
panic(err)
}
}
func (c *GormController) SetDB() r.Result {
c.DB = Gdb
return nil
}
init.go
package controllers
import "github.com/revel/revel"
func init() {
revel.OnAppStart(InitDB) // invoke InitDB function before
revel.InterceptMethod((*GormController).SetDB, revel.BEFORE)
}
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"} // Note: In practice you should initialize all struct fields
if err := c.DB.Create(&user).Error; err != nil {
panic(err)
}
return c.RenderJSON(user)
}
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"
}
source to share