added action arg to the before Dao hook to allow skipping the default persist behavior

This commit is contained in:
Gani Georgiev 2023-07-29 19:52:36 +03:00
parent 6da94aef8d
commit cdeb9a94ed
5 changed files with 232 additions and 182 deletions

View File

@ -119,6 +119,7 @@
There is a system migration that will convert the existing view `relation` fields to `json` (multiple) and `text` (single) fields. There is a system migration that will convert the existing view `relation` fields to `json` (multiple) and `text` (single) fields.
This could be a breaking change if you have `relation` to view and use `expand` or some of the `relation` view fields as part of a collection rule. This could be a breaking change if you have `relation` to view and use `expand` or some of the `relation` view fields as part of a collection rule.
- **!** (@todo docs) Added action argument to the Dao hooks to allow skipping the default persist behavior.
## v0.16.10 ## v0.16.10

View File

@ -1049,58 +1049,58 @@ func (app *BaseApp) initDataDB() error {
func (app *BaseApp) createDaoWithHooks(concurrentDB, nonconcurrentDB dbx.Builder) *daos.Dao { func (app *BaseApp) createDaoWithHooks(concurrentDB, nonconcurrentDB dbx.Builder) *daos.Dao {
dao := daos.NewMultiDB(concurrentDB, nonconcurrentDB) dao := daos.NewMultiDB(concurrentDB, nonconcurrentDB)
dao.BeforeCreateFunc = func(eventDao *daos.Dao, m models.Model) error { dao.BeforeCreateFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
e := new(ModelEvent) e := new(ModelEvent)
e.Dao = eventDao e.Dao = eventDao
e.Model = m e.Model = m
return app.OnModelBeforeCreate().Trigger(e) return app.OnModelBeforeCreate().Trigger(e, func(e *ModelEvent) error {
return action()
})
} }
dao.AfterCreateFunc = func(eventDao *daos.Dao, m models.Model) { dao.AfterCreateFunc = func(eventDao *daos.Dao, m models.Model) error {
e := new(ModelEvent) e := new(ModelEvent)
e.Dao = eventDao e.Dao = eventDao
e.Model = m e.Model = m
if err := app.OnModelAfterCreate().Trigger(e); err != nil && app.isDebug { return app.OnModelAfterCreate().Trigger(e)
log.Println(err)
}
} }
dao.BeforeUpdateFunc = func(eventDao *daos.Dao, m models.Model) error { dao.BeforeUpdateFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
e := new(ModelEvent) e := new(ModelEvent)
e.Dao = eventDao e.Dao = eventDao
e.Model = m e.Model = m
return app.OnModelBeforeUpdate().Trigger(e) return app.OnModelBeforeUpdate().Trigger(e, func(e *ModelEvent) error {
return action()
})
} }
dao.AfterUpdateFunc = func(eventDao *daos.Dao, m models.Model) { dao.AfterUpdateFunc = func(eventDao *daos.Dao, m models.Model) error {
e := new(ModelEvent) e := new(ModelEvent)
e.Dao = eventDao e.Dao = eventDao
e.Model = m e.Model = m
if err := app.OnModelAfterUpdate().Trigger(e); err != nil && app.isDebug { return app.OnModelAfterUpdate().Trigger(e)
log.Println(err)
}
} }
dao.BeforeDeleteFunc = func(eventDao *daos.Dao, m models.Model) error { dao.BeforeDeleteFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
e := new(ModelEvent) e := new(ModelEvent)
e.Dao = eventDao e.Dao = eventDao
e.Model = m e.Model = m
return app.OnModelBeforeDelete().Trigger(e) return app.OnModelBeforeDelete().Trigger(e, func(e *ModelEvent) error {
return action()
})
} }
dao.AfterDeleteFunc = func(eventDao *daos.Dao, m models.Model) { dao.AfterDeleteFunc = func(eventDao *daos.Dao, m models.Model) error {
e := new(ModelEvent) e := new(ModelEvent)
e.Dao = eventDao e.Dao = eventDao
e.Model = m e.Model = m
if err := app.OnModelAfterDelete().Trigger(e); err != nil && app.isDebug { return app.OnModelAfterDelete().Trigger(e)
log.Println(err)
}
} }
return dao return dao

View File

@ -5,6 +5,8 @@ package daos
import ( import (
"errors" "errors"
"fmt"
"strings"
"time" "time"
"github.com/pocketbase/dbx" "github.com/pocketbase/dbx"
@ -45,12 +47,12 @@ type Dao struct {
ModelQueryTimeout time.Duration ModelQueryTimeout time.Duration
// write hooks // write hooks
BeforeCreateFunc func(eventDao *Dao, m models.Model) error BeforeCreateFunc func(eventDao *Dao, m models.Model, action func() error) error
AfterCreateFunc func(eventDao *Dao, m models.Model) AfterCreateFunc func(eventDao *Dao, m models.Model) error
BeforeUpdateFunc func(eventDao *Dao, m models.Model) error BeforeUpdateFunc func(eventDao *Dao, m models.Model, action func() error) error
AfterUpdateFunc func(eventDao *Dao, m models.Model) AfterUpdateFunc func(eventDao *Dao, m models.Model) error
BeforeDeleteFunc func(eventDao *Dao, m models.Model) error BeforeDeleteFunc func(eventDao *Dao, m models.Model, action func() error) error
AfterDeleteFunc func(eventDao *Dao, m models.Model) AfterDeleteFunc func(eventDao *Dao, m models.Model) error
} }
// DB returns the default dao db builder (*dbx.DB or *dbx.TX). // DB returns the default dao db builder (*dbx.DB or *dbx.TX).
@ -151,56 +153,75 @@ func (dao *Dao) RunInTransaction(fn func(txDao *Dao) error) error {
txDao := New(tx) txDao := New(tx)
if dao.BeforeCreateFunc != nil { if dao.BeforeCreateFunc != nil {
txDao.BeforeCreateFunc = func(eventDao *Dao, m models.Model) error { txDao.BeforeCreateFunc = func(eventDao *Dao, m models.Model, action func() error) error {
return dao.BeforeCreateFunc(eventDao, m) return dao.BeforeCreateFunc(eventDao, m, action)
} }
} }
if dao.BeforeUpdateFunc != nil { if dao.BeforeUpdateFunc != nil {
txDao.BeforeUpdateFunc = func(eventDao *Dao, m models.Model) error { txDao.BeforeUpdateFunc = func(eventDao *Dao, m models.Model, action func() error) error {
return dao.BeforeUpdateFunc(eventDao, m) return dao.BeforeUpdateFunc(eventDao, m, action)
} }
} }
if dao.BeforeDeleteFunc != nil { if dao.BeforeDeleteFunc != nil {
txDao.BeforeDeleteFunc = func(eventDao *Dao, m models.Model) error { txDao.BeforeDeleteFunc = func(eventDao *Dao, m models.Model, action func() error) error {
return dao.BeforeDeleteFunc(eventDao, m) return dao.BeforeDeleteFunc(eventDao, m, action)
} }
} }
if dao.AfterCreateFunc != nil { if dao.AfterCreateFunc != nil {
txDao.AfterCreateFunc = func(eventDao *Dao, m models.Model) { txDao.AfterCreateFunc = func(eventDao *Dao, m models.Model) error {
afterCalls = append(afterCalls, afterCallGroup{"create", eventDao, m}) afterCalls = append(afterCalls, afterCallGroup{"create", eventDao, m})
return nil
} }
} }
if dao.AfterUpdateFunc != nil { if dao.AfterUpdateFunc != nil {
txDao.AfterUpdateFunc = func(eventDao *Dao, m models.Model) { txDao.AfterUpdateFunc = func(eventDao *Dao, m models.Model) error {
afterCalls = append(afterCalls, afterCallGroup{"update", eventDao, m}) afterCalls = append(afterCalls, afterCallGroup{"update", eventDao, m})
return nil
} }
} }
if dao.AfterDeleteFunc != nil { if dao.AfterDeleteFunc != nil {
txDao.AfterDeleteFunc = func(eventDao *Dao, m models.Model) { txDao.AfterDeleteFunc = func(eventDao *Dao, m models.Model) error {
afterCalls = append(afterCalls, afterCallGroup{"delete", eventDao, m}) afterCalls = append(afterCalls, afterCallGroup{"delete", eventDao, m})
return nil
} }
} }
return fn(txDao) return fn(txDao)
}) })
if txError != nil {
return txError
}
if txError == nil {
// execute after event calls on successful transaction // execute after event calls on successful transaction
// (note: using the non-transaction dao to allow following queries in the after hooks) // (note: using the non-transaction dao to allow following queries in the after hooks)
var errs []error
for _, call := range afterCalls { for _, call := range afterCalls {
var err error
switch call.Action { switch call.Action {
case "create": case "create":
dao.AfterCreateFunc(dao, call.Model) err = dao.AfterCreateFunc(dao, call.Model)
case "update": case "update":
dao.AfterUpdateFunc(dao, call.Model) err = dao.AfterUpdateFunc(dao, call.Model)
case "delete": case "delete":
dao.AfterDeleteFunc(dao, call.Model) err = dao.AfterDeleteFunc(dao, call.Model)
}
}
} }
return txError if err != nil {
errs = append(errs, err)
}
}
if len(errs) > 0 {
// @todo after go 1.20+ upgrade consider replacing with errors.Join()
var errsMsg strings.Builder
for _, err := range errs {
errsMsg.WriteString(err.Error())
errsMsg.WriteString("; ")
}
return fmt.Errorf("after transaction errors: %s", errsMsg.String())
}
return nil
} }
return errors.New("failed to start transaction (unknown dao.NonconcurrentDB() instance)") return errors.New("failed to start transaction (unknown dao.NonconcurrentDB() instance)")
@ -213,12 +234,7 @@ func (dao *Dao) Delete(m models.Model) error {
} }
return dao.lockRetry(func(retryDao *Dao) error { return dao.lockRetry(func(retryDao *Dao) error {
if retryDao.BeforeDeleteFunc != nil { action := func() error {
if err := retryDao.BeforeDeleteFunc(retryDao, m); err != nil {
return err
}
}
if err := retryDao.NonconcurrentDB().Model(m).Delete(); err != nil { if err := retryDao.NonconcurrentDB().Model(m).Delete(); err != nil {
return err return err
} }
@ -228,6 +244,13 @@ func (dao *Dao) Delete(m models.Model) error {
} }
return nil return nil
}
if retryDao.BeforeDeleteFunc != nil {
return retryDao.BeforeDeleteFunc(retryDao, m, action)
}
return action()
}) })
} }
@ -258,12 +281,7 @@ func (dao *Dao) update(m models.Model) error {
m.RefreshUpdated() m.RefreshUpdated()
if dao.BeforeUpdateFunc != nil { action := func() error {
if err := dao.BeforeUpdateFunc(dao, m); err != nil {
return err
}
}
if v, ok := any(m).(models.ColumnValueMapper); ok { if v, ok := any(m).(models.ColumnValueMapper); ok {
dataMap := v.ColumnValueMap() dataMap := v.ColumnValueMap()
@ -276,19 +294,24 @@ func (dao *Dao) update(m models.Model) error {
if err != nil { if err != nil {
return err return err
} }
} else { } else if err := dao.NonconcurrentDB().Model(m).Update(); err != nil {
if err := dao.NonconcurrentDB().Model(m).Update(); err != nil {
return err return err
} }
}
if dao.AfterUpdateFunc != nil { if dao.AfterUpdateFunc != nil {
dao.AfterUpdateFunc(dao, m) return dao.AfterUpdateFunc(dao, m)
} }
return nil return nil
} }
if dao.BeforeUpdateFunc != nil {
return dao.BeforeUpdateFunc(dao, m, action)
}
return action()
}
func (dao *Dao) create(m models.Model) error { func (dao *Dao) create(m models.Model) error {
if !m.HasId() { if !m.HasId() {
// auto generate id // auto generate id
@ -306,12 +329,7 @@ func (dao *Dao) create(m models.Model) error {
m.RefreshUpdated() m.RefreshUpdated()
} }
if dao.BeforeCreateFunc != nil { action := func() error {
if err := dao.BeforeCreateFunc(dao, m); err != nil {
return err
}
}
if v, ok := any(m).(models.ColumnValueMapper); ok { if v, ok := any(m).(models.ColumnValueMapper); ok {
dataMap := v.ColumnValueMap() dataMap := v.ColumnValueMap()
if _, ok := dataMap["id"]; !ok { if _, ok := dataMap["id"]; !ok {
@ -322,22 +340,27 @@ func (dao *Dao) create(m models.Model) error {
if err != nil { if err != nil {
return err return err
} }
} else { } else if err := dao.NonconcurrentDB().Model(m).Insert(); err != nil {
if err := dao.NonconcurrentDB().Model(m).Insert(); err != nil {
return err return err
} }
}
// clears the "new" model flag // clears the "new" model flag
m.MarkAsNotNew() m.MarkAsNotNew()
if dao.AfterCreateFunc != nil { if dao.AfterCreateFunc != nil {
dao.AfterCreateFunc(dao, m) return dao.AfterCreateFunc(dao, m)
} }
return nil return nil
} }
if dao.BeforeCreateFunc != nil {
return dao.BeforeCreateFunc(dao, m, action)
}
return action()
}
func (dao *Dao) lockRetry(op func(retryDao *Dao) error) error { func (dao *Dao) lockRetry(op func(retryDao *Dao) error) error {
retryDao := dao retryDao := dao

View File

@ -49,33 +49,37 @@ func TestDaoClone(t *testing.T) {
dao := daos.NewMultiDB(testApp.Dao().ConcurrentDB(), testApp.Dao().NonconcurrentDB()) dao := daos.NewMultiDB(testApp.Dao().ConcurrentDB(), testApp.Dao().NonconcurrentDB())
dao.MaxLockRetries = 1 dao.MaxLockRetries = 1
dao.ModelQueryTimeout = 2 dao.ModelQueryTimeout = 2
dao.BeforeDeleteFunc = func(eventDao *daos.Dao, m models.Model) error { dao.BeforeDeleteFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
hookCalls["BeforeDeleteFunc"]++ hookCalls["BeforeDeleteFunc"]++
return nil return action()
} }
dao.BeforeUpdateFunc = func(eventDao *daos.Dao, m models.Model) error { dao.BeforeUpdateFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
hookCalls["BeforeUpdateFunc"]++ hookCalls["BeforeUpdateFunc"]++
return nil return action()
} }
dao.BeforeCreateFunc = func(eventDao *daos.Dao, m models.Model) error { dao.BeforeCreateFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
hookCalls["BeforeCreateFunc"]++ hookCalls["BeforeCreateFunc"]++
return action()
}
dao.AfterDeleteFunc = func(eventDao *daos.Dao, m models.Model) error {
hookCalls["AfterDeleteFunc"]++
return nil return nil
} }
dao.AfterDeleteFunc = func(eventDao *daos.Dao, m models.Model) { dao.AfterUpdateFunc = func(eventDao *daos.Dao, m models.Model) error {
hookCalls["AfterDeleteFunc"]++
}
dao.AfterUpdateFunc = func(eventDao *daos.Dao, m models.Model) {
hookCalls["AfterUpdateFunc"]++ hookCalls["AfterUpdateFunc"]++
return nil
} }
dao.AfterCreateFunc = func(eventDao *daos.Dao, m models.Model) { dao.AfterCreateFunc = func(eventDao *daos.Dao, m models.Model) error {
hookCalls["AfterCreateFunc"]++ hookCalls["AfterCreateFunc"]++
return nil
} }
clone := dao.Clone() clone := dao.Clone()
clone.MaxLockRetries = 3 clone.MaxLockRetries = 3
clone.ModelQueryTimeout = 4 clone.ModelQueryTimeout = 4
clone.AfterCreateFunc = func(eventDao *daos.Dao, m models.Model) { clone.AfterCreateFunc = func(eventDao *daos.Dao, m models.Model) error {
hookCalls["NewAfterCreateFunc"]++ hookCalls["NewAfterCreateFunc"]++
return nil
} }
if dao.MaxLockRetries == clone.MaxLockRetries { if dao.MaxLockRetries == clone.MaxLockRetries {
@ -86,16 +90,18 @@ func TestDaoClone(t *testing.T) {
t.Fatal("Expected different ModelQueryTimeout") t.Fatal("Expected different ModelQueryTimeout")
} }
emptyAction := func() error { return nil }
// trigger hooks // trigger hooks
dao.BeforeDeleteFunc(nil, nil) dao.BeforeDeleteFunc(nil, nil, emptyAction)
dao.BeforeUpdateFunc(nil, nil) dao.BeforeUpdateFunc(nil, nil, emptyAction)
dao.BeforeCreateFunc(nil, nil) dao.BeforeCreateFunc(nil, nil, emptyAction)
dao.AfterDeleteFunc(nil, nil) dao.AfterDeleteFunc(nil, nil)
dao.AfterUpdateFunc(nil, nil) dao.AfterUpdateFunc(nil, nil)
dao.AfterCreateFunc(nil, nil) dao.AfterCreateFunc(nil, nil)
clone.BeforeDeleteFunc(nil, nil) clone.BeforeDeleteFunc(nil, nil, emptyAction)
clone.BeforeUpdateFunc(nil, nil) clone.BeforeUpdateFunc(nil, nil, emptyAction)
clone.BeforeCreateFunc(nil, nil) clone.BeforeCreateFunc(nil, nil, emptyAction)
clone.AfterDeleteFunc(nil, nil) clone.AfterDeleteFunc(nil, nil)
clone.AfterUpdateFunc(nil, nil) clone.AfterUpdateFunc(nil, nil)
clone.AfterCreateFunc(nil, nil) clone.AfterCreateFunc(nil, nil)
@ -129,26 +135,29 @@ func TestDaoWithoutHooks(t *testing.T) {
dao := daos.NewMultiDB(testApp.Dao().ConcurrentDB(), testApp.Dao().NonconcurrentDB()) dao := daos.NewMultiDB(testApp.Dao().ConcurrentDB(), testApp.Dao().NonconcurrentDB())
dao.MaxLockRetries = 1 dao.MaxLockRetries = 1
dao.ModelQueryTimeout = 2 dao.ModelQueryTimeout = 2
dao.BeforeDeleteFunc = func(eventDao *daos.Dao, m models.Model) error { dao.BeforeDeleteFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
hookCalls["BeforeDeleteFunc"]++ hookCalls["BeforeDeleteFunc"]++
return nil return action()
} }
dao.BeforeUpdateFunc = func(eventDao *daos.Dao, m models.Model) error { dao.BeforeUpdateFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
hookCalls["BeforeUpdateFunc"]++ hookCalls["BeforeUpdateFunc"]++
return nil return action()
} }
dao.BeforeCreateFunc = func(eventDao *daos.Dao, m models.Model) error { dao.BeforeCreateFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
hookCalls["BeforeCreateFunc"]++ hookCalls["BeforeCreateFunc"]++
return action()
}
dao.AfterDeleteFunc = func(eventDao *daos.Dao, m models.Model) error {
hookCalls["AfterDeleteFunc"]++
return nil return nil
} }
dao.AfterDeleteFunc = func(eventDao *daos.Dao, m models.Model) { dao.AfterUpdateFunc = func(eventDao *daos.Dao, m models.Model) error {
hookCalls["AfterDeleteFunc"]++
}
dao.AfterUpdateFunc = func(eventDao *daos.Dao, m models.Model) {
hookCalls["AfterUpdateFunc"]++ hookCalls["AfterUpdateFunc"]++
return nil
} }
dao.AfterCreateFunc = func(eventDao *daos.Dao, m models.Model) { dao.AfterCreateFunc = func(eventDao *daos.Dao, m models.Model) error {
hookCalls["AfterCreateFunc"]++ hookCalls["AfterCreateFunc"]++
return nil
} }
new := dao.WithoutHooks() new := dao.WithoutHooks()
@ -481,12 +490,13 @@ func TestDaoRetryCreate(t *testing.T) {
retryBeforeCreateHookCalls := 0 retryBeforeCreateHookCalls := 0
retryAfterCreateHookCalls := 0 retryAfterCreateHookCalls := 0
retryDao := daos.New(testApp.DB()) retryDao := daos.New(testApp.DB())
retryDao.BeforeCreateFunc = func(eventDao *daos.Dao, m models.Model) error { retryDao.BeforeCreateFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
retryBeforeCreateHookCalls++ retryBeforeCreateHookCalls++
return errors.New("database is locked") return errors.New("database is locked")
} }
retryDao.AfterCreateFunc = func(eventDao *daos.Dao, m models.Model) { retryDao.AfterCreateFunc = func(eventDao *daos.Dao, m models.Model) error {
retryAfterCreateHookCalls++ retryAfterCreateHookCalls++
return nil
} }
model := &models.Admin{Email: "new@example.com"} model := &models.Admin{Email: "new@example.com"}
@ -507,7 +517,7 @@ func TestDaoRetryCreate(t *testing.T) {
// with non-locking error // with non-locking error
retryBeforeCreateHookCalls = 0 retryBeforeCreateHookCalls = 0
retryAfterCreateHookCalls = 0 retryAfterCreateHookCalls = 0
retryDao.BeforeCreateFunc = func(eventDao *daos.Dao, m models.Model) error { retryDao.BeforeCreateFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
retryBeforeCreateHookCalls++ retryBeforeCreateHookCalls++
return errors.New("non-locking error") return errors.New("non-locking error")
} }
@ -539,12 +549,13 @@ func TestDaoRetryUpdate(t *testing.T) {
retryBeforeUpdateHookCalls := 0 retryBeforeUpdateHookCalls := 0
retryAfterUpdateHookCalls := 0 retryAfterUpdateHookCalls := 0
retryDao := daos.New(testApp.DB()) retryDao := daos.New(testApp.DB())
retryDao.BeforeUpdateFunc = func(eventDao *daos.Dao, m models.Model) error { retryDao.BeforeUpdateFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
retryBeforeUpdateHookCalls++ retryBeforeUpdateHookCalls++
return errors.New("database is locked") return errors.New("database is locked")
} }
retryDao.AfterUpdateFunc = func(eventDao *daos.Dao, m models.Model) { retryDao.AfterUpdateFunc = func(eventDao *daos.Dao, m models.Model) error {
retryAfterUpdateHookCalls++ retryAfterUpdateHookCalls++
return nil
} }
if err := retryDao.Save(model); err != nil { if err := retryDao.Save(model); err != nil {
@ -564,7 +575,7 @@ func TestDaoRetryUpdate(t *testing.T) {
// with non-locking error // with non-locking error
retryBeforeUpdateHookCalls = 0 retryBeforeUpdateHookCalls = 0
retryAfterUpdateHookCalls = 0 retryAfterUpdateHookCalls = 0
retryDao.BeforeUpdateFunc = func(eventDao *daos.Dao, m models.Model) error { retryDao.BeforeUpdateFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
retryBeforeUpdateHookCalls++ retryBeforeUpdateHookCalls++
return errors.New("non-locking error") return errors.New("non-locking error")
} }
@ -590,12 +601,13 @@ func TestDaoRetryDelete(t *testing.T) {
retryBeforeDeleteHookCalls := 0 retryBeforeDeleteHookCalls := 0
retryAfterDeleteHookCalls := 0 retryAfterDeleteHookCalls := 0
retryDao := daos.New(testApp.DB()) retryDao := daos.New(testApp.DB())
retryDao.BeforeDeleteFunc = func(eventDao *daos.Dao, m models.Model) error { retryDao.BeforeDeleteFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
retryBeforeDeleteHookCalls++ retryBeforeDeleteHookCalls++
return errors.New("database is locked") return errors.New("database is locked")
} }
retryDao.AfterDeleteFunc = func(eventDao *daos.Dao, m models.Model) { retryDao.AfterDeleteFunc = func(eventDao *daos.Dao, m models.Model) error {
retryAfterDeleteHookCalls++ retryAfterDeleteHookCalls++
return nil
} }
model, _ := retryDao.FindAdminByEmail("test@example.com") model, _ := retryDao.FindAdminByEmail("test@example.com")
@ -616,7 +628,7 @@ func TestDaoRetryDelete(t *testing.T) {
// with non-locking error // with non-locking error
retryBeforeDeleteHookCalls = 0 retryBeforeDeleteHookCalls = 0
retryAfterDeleteHookCalls = 0 retryAfterDeleteHookCalls = 0
retryDao.BeforeDeleteFunc = func(eventDao *daos.Dao, m models.Model) error { retryDao.BeforeDeleteFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
retryBeforeDeleteHookCalls++ retryBeforeDeleteHookCalls++
return errors.New("non-locking error") return errors.New("non-locking error")
} }
@ -643,13 +655,13 @@ func TestDaoBeforeHooksError(t *testing.T) {
baseDao := testApp.Dao() baseDao := testApp.Dao()
baseDao.BeforeCreateFunc = func(eventDao *daos.Dao, m models.Model) error { baseDao.BeforeCreateFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
return errors.New("before_create") return errors.New("before_create")
} }
baseDao.BeforeUpdateFunc = func(eventDao *daos.Dao, m models.Model) error { baseDao.BeforeUpdateFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
return errors.New("before_update") return errors.New("before_update")
} }
baseDao.BeforeDeleteFunc = func(eventDao *daos.Dao, m models.Model) error { baseDao.BeforeDeleteFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
return errors.New("before_delete") return errors.New("before_delete")
} }
@ -688,27 +700,30 @@ func TestDaoTransactionHooksCallsOnFailure(t *testing.T) {
baseDao := testApp.Dao() baseDao := testApp.Dao()
baseDao.BeforeCreateFunc = func(eventDao *daos.Dao, m models.Model) error { baseDao.BeforeCreateFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
beforeCreateFuncCalls++ beforeCreateFuncCalls++
return nil return action()
} }
baseDao.BeforeUpdateFunc = func(eventDao *daos.Dao, m models.Model) error { baseDao.BeforeUpdateFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
beforeUpdateFuncCalls++ beforeUpdateFuncCalls++
return nil return action()
} }
baseDao.BeforeDeleteFunc = func(eventDao *daos.Dao, m models.Model) error { baseDao.BeforeDeleteFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
beforeDeleteFuncCalls++ beforeDeleteFuncCalls++
return nil return action()
} }
baseDao.AfterCreateFunc = func(eventDao *daos.Dao, m models.Model) { baseDao.AfterCreateFunc = func(eventDao *daos.Dao, m models.Model) error {
afterCreateFuncCalls++ afterCreateFuncCalls++
return nil
} }
baseDao.AfterUpdateFunc = func(eventDao *daos.Dao, m models.Model) { baseDao.AfterUpdateFunc = func(eventDao *daos.Dao, m models.Model) error {
afterUpdateFuncCalls++ afterUpdateFuncCalls++
return nil
} }
baseDao.AfterDeleteFunc = func(eventDao *daos.Dao, m models.Model) { baseDao.AfterDeleteFunc = func(eventDao *daos.Dao, m models.Model) error {
afterDeleteFuncCalls++ afterDeleteFuncCalls++
return nil
} }
existingModel, _ := testApp.Dao().FindAdminByEmail("test@example.com") existingModel, _ := testApp.Dao().FindAdminByEmail("test@example.com")
@ -776,27 +791,30 @@ func TestDaoTransactionHooksCallsOnSuccess(t *testing.T) {
baseDao := testApp.Dao() baseDao := testApp.Dao()
baseDao.BeforeCreateFunc = func(eventDao *daos.Dao, m models.Model) error { baseDao.BeforeCreateFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
beforeCreateFuncCalls++ beforeCreateFuncCalls++
return nil return action()
} }
baseDao.BeforeUpdateFunc = func(eventDao *daos.Dao, m models.Model) error { baseDao.BeforeUpdateFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
beforeUpdateFuncCalls++ beforeUpdateFuncCalls++
return nil return action()
} }
baseDao.BeforeDeleteFunc = func(eventDao *daos.Dao, m models.Model) error { baseDao.BeforeDeleteFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
beforeDeleteFuncCalls++ beforeDeleteFuncCalls++
return nil return action()
} }
baseDao.AfterCreateFunc = func(eventDao *daos.Dao, m models.Model) { baseDao.AfterCreateFunc = func(eventDao *daos.Dao, m models.Model) error {
afterCreateFuncCalls++ afterCreateFuncCalls++
return nil
} }
baseDao.AfterUpdateFunc = func(eventDao *daos.Dao, m models.Model) { baseDao.AfterUpdateFunc = func(eventDao *daos.Dao, m models.Model) error {
afterUpdateFuncCalls++ afterUpdateFuncCalls++
return nil
} }
baseDao.AfterDeleteFunc = func(eventDao *daos.Dao, m models.Model) { baseDao.AfterDeleteFunc = func(eventDao *daos.Dao, m models.Model) error {
afterDeleteFuncCalls++ afterDeleteFuncCalls++
return nil
} }
existingModel, _ := testApp.Dao().FindAdminByEmail("test@example.com") existingModel, _ := testApp.Dao().FindAdminByEmail("test@example.com")

View File

@ -744,36 +744,44 @@ func (form *RecordUpsert) Submit(interceptors ...InterceptorFunc[*models.Record]
// upload new files (if any) // upload new files (if any)
// //
// note: executed after the default BeforeCreateFunc and BeforeUpdateFunc hooks // note: executed after the default BeforeCreateFunc and BeforeUpdateFunc hook actions
// to allow uploading AFTER the before app model hooks (eg. in case of an id change) // to allow uploading AFTER the before app model hooks (eg. in case of an id change)
// but BEFORE the actual record db persistence // but BEFORE the actual record db persistence
// --- // ---
dao.BeforeCreateFunc = func(eventDao *daos.Dao, m models.Model) error { dao.BeforeCreateFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
newAction := func() error {
if m.TableName() == form.record.TableName() && m.GetId() == form.record.GetId() {
if err := form.processFilesToUpload(); err != nil {
return err
}
}
return action()
}
if form.dao.BeforeCreateFunc != nil { if form.dao.BeforeCreateFunc != nil {
if err := form.dao.BeforeCreateFunc(eventDao, m); err != nil { return form.dao.BeforeCreateFunc(eventDao, m, newAction)
}
return newAction()
}
dao.BeforeUpdateFunc = func(eventDao *daos.Dao, m models.Model, action func() error) error {
newAction := func() error {
if m.TableName() == form.record.TableName() && m.GetId() == form.record.GetId() {
if err := form.processFilesToUpload(); err != nil {
return err return err
} }
} }
if m.TableName() == form.record.TableName() && m.GetId() == form.record.GetId() { return action()
return form.processFilesToUpload()
} }
return nil
}
dao.BeforeUpdateFunc = func(eventDao *daos.Dao, m models.Model) error {
if form.dao.BeforeUpdateFunc != nil { if form.dao.BeforeUpdateFunc != nil {
if err := form.dao.BeforeUpdateFunc(eventDao, m); err != nil { return form.dao.BeforeUpdateFunc(eventDao, m, newAction)
return err
}
} }
if m.TableName() == form.record.TableName() && m.GetId() == form.record.GetId() { return newAction()
return form.processFilesToUpload()
}
return nil
} }
// --- // ---