Pg: Document how to use hooks

Created on 5 Jul 2019  路  7Comments  路  Source: go-pg/pg

I have not found a basic how-to example on using hooks.

For my purpose, I've created a db, created tables, added rows, and I just want to add a BeforeUpdate hook. However, the documentation at this time assumes a level of existing expertise with hooks. Even after reading code in the repository and lots of googling, the closest I've gotten to working is this error message,
"panic: Product.BeforeUpdate must be updated - https://github.com/go-pg/pg/wiki/Model-Hooks",
which doesn't really tell me anything.

Example function definition

func (product *Product) BeforeUpdate(db orm.DB) error {
product.UpdatedOn = time.Now()
return nil
}

Do I need to register the hook in some manner?

I think this type of documentation would help out people who are new to the project. Of course, with a working hook.

package main

import (
"github.com/go-pg/pg"
"github.com/go-pg/pg/orm"
"log"
"time"

)

type Product struct {
Id int
Name string sql:",unique,notnull"
Price float32
CreatedOn time.Time sql:"created_on,notnull,default:now()"
UpdatedOn time.Time sql:"updated_on,notnull,default:now()"
}

//func (product *Product) BeforeUpdate(db orm.DB) error {
// product.UpdatedOn = time.Now()
// return nil
//}

func SelectProductByName(db pg.DB, product *Product, name string) {
filter := func(q *orm.Query) (
orm.Query, error) {
q = q.Where("name = ?", name)
return q, nil
}

err := db.Model(product).
    Apply(filter).
    Select()
if err != nil {
    log.Panicf("SelectProductByName: %v", err)
}

}

func main() {
db := pg.Connect(&pg.Options{
Network: "tcp",
Addr: "0.0.0.0:5432",
User: "test",
Password: "test",
Database: "dbTest",
})
defer db.Close()

if err := db.CreateTable(&Product{}, &orm.CreateTableOptions{
    IfNotExists: true,
}); err != nil {
    log.Panicf("createProductTable: %v", err)
}

product := Product{
    Name: "Product 1",
    Price: 9.99,
}
if err := db.Insert(&product); err != nil {
    log.Panicf("AddProduct: %v", err)
}

productReturn := Product{}
SelectProductByName(db, &productReturn, "Product 1")

product.Price = 4.49
if err := db.Update(&product); err != nil {
    log.Panicf("UpdateProduct: %v", err)
}

productReturn2 := Product{}
SelectProductByName(db, &productReturn2, "Product 1")

log.Println("Original product return: %v", productReturn)
log.Println("Updated product return: %v", productReturn2)
// NOTE: Price and UpdatedOn should not match

}

Most helpful comment

You already have struct and db.Update call. All you need to add is

model.UpdatedAt = time.Now()

All 7 comments

What version are you on? The latest API requires context object to be passed in and out https://github.com/go-pg/pg/wiki/Model-Hooks. There has been some API changes surrounding this so I suggest to update to the latest go-pg

I just started working in this world recently. I'm running version "[email protected]+incompatible".

If I change to passing context, the pre-update change is not made or, if I attempt to register the BeforeUpdateHook, I get an error.

package main

import (
    "context"
    "github.com/go-pg/pg"
    "github.com/go-pg/pg/orm"
    "log"
    "time"


)

type Product struct {
    Id int
    Name         string `sql:",unique,notnull"`
    Price        float32
    CreatedOn    time.Time `sql:"created_on,notnull,default:now()"`
    UpdatedOn    time.Time `sql:"updated_on,notnull,default:now()"`
}

var _ orm.BeforeUpdateHook = (*Product)(nil)

func (product *Product) BeforeUpdate(ctx context.Context) (context.Context, error) {
    return ctx, updateProductUpdateTime(product)
}

func updateProductUpdateTime(product *Product) error {
    product.UpdatedOn = time.Now()
    return nil
}

func SelectProductByName(db *pg.DB, product *Product, name string) {
    filter := func(q *orm.Query) (*orm.Query, error) {
        q = q.Where("name = ?", name)
        return q, nil
    }

    err := db.Model(product).
        Apply(filter).
        Select()
    if err != nil {
        log.Panicf("SelectProductByName: %v", err)
    }

}


func main() {
    db := pg.Connect(&pg.Options{
        Network:  "tcp",
        Addr:     "0.0.0.0:5432",
        User:     "test",
        Password: "test",
        Database: "dbTest",
    })
    defer db.Close()


    if err := db.CreateTable(&Product{}, &orm.CreateTableOptions{
        IfNotExists: true,
    }); err != nil {
        log.Panicf("createProductTable: %v", err)
    }

    product := Product{
        Name: "Product 1",
        Price: 9.99,
    }
    if err := db.Insert(&product); err != nil {
        log.Panicf("AddProduct: %v", err)
    }

    productReturn := Product{}
    SelectProductByName(db, &productReturn, "Product 1")

    product.Price = 4.49
    if err := db.Update(&product); err != nil {
        log.Panicf("UpdateProduct: %v", err)
    }

    productReturn2 := Product{}
    SelectProductByName(db, &productReturn2, "Product 1")

    log.Println("Original product return: %v", productReturn)
    log.Println("Updated product return: %v", productReturn2)
    // NOTE: Price and UpdatedOn should not match

}


| => go run testmain.go
# command-line-arguments
./testmain.go:21:7: undefined: orm.BeforeUpdateHook

So, where is the disconnect between the documentation and the implementation.

First of all, it would really help to format things properly using triple forward quotes + file extension: ```go YOUR CODE

Followed by closing triple forward quotes: ```

Example:

func (t *HookTest) BeforeUpdate(c context.Context) (context.Context, error) {
    t.beforeUpdate++
    return c, nil
}

I haven't tried using latest PG myself, but the https://github.com/go-pg/pg/blob/master/hook_test.go should be instructive. Maybe you need to use specific versioned imports which seems to be this repo's direction at the moment...

@vmihailenco I don't know why you closed this. The lacking of documentation is one of the biggest killers of open source projects.
While I greatly appreciate the input from ernsheong, he didn't really know the answer to my question. The code I supplied is self-documenting, works, can be used as a starting point for people considering adopting pg, and only requires someone with a knowledge of hooks to make a couple of edits to also illustrate how hooks work.
The hook documentation hasn't changed. Therefore, the problem with how do hooks work in practice is still unanswered.

It should be not be surprising that you don't get an answer when you don't format your code and at the same time require better docs. And writing good examples how to use hooks is not as easy as it may look - people want to do all the smart/stupid things with hooks you can imagine. In your case - why do you need a hook to set UpdatedAt field? I am sure you have a good reason (everyone has) but I think you should not use hooks to do it.

undefined: orm.BeforeUpdateHook suggests that you need to update import path to github.com/go-pg/pg/v9 and use Go modules.

@vmihailenco What derives from your reply is using hooks maybe is not a decent way for dealing with something like setting UpdateAt field so what's your approach?
Maybe a simple repository method?
I'm willing to know how you handle it.

Thanks.

You already have struct and db.Update call. All you need to add is

model.UpdatedAt = time.Now()
Was this page helpful?
0 / 5 - 0 ratings

Related issues

kieusonlam picture kieusonlam  路  6Comments

sas1024 picture sas1024  路  5Comments

noggan picture noggan  路  5Comments

Alireza-Ta picture Alireza-Ta  路  3Comments

owentran picture owentran  路  6Comments