diff --git a/CHANGELOG.md b/CHANGELOG.md index d9685d88..50facb9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -119,6 +119,7 @@ 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. +- **!** (@todo docs) Added action argument to the Dao hooks to allow skipping the default persist behavior. ## v0.16.10 diff --git a/core/base.go b/core/base.go index d3da0366..a2055f05 100644 --- a/core/base.go +++ b/core/base.go @@ -1049,58 +1049,58 @@ func (app *BaseApp) initDataDB() error { func (app *BaseApp) createDaoWithHooks(concurrentDB, nonconcurrentDB dbx.Builder) *daos.Dao { 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.Dao = eventDao 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.Dao = eventDao e.Model = m - if err := app.OnModelAfterCreate().Trigger(e); err != nil && app.isDebug { - log.Println(err) - } + return app.OnModelAfterCreate().Trigger(e) } - 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.Dao = eventDao 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.Dao = eventDao e.Model = m - if err := app.OnModelAfterUpdate().Trigger(e); err != nil && app.isDebug { - log.Println(err) - } + return app.OnModelAfterUpdate().Trigger(e) } - 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.Dao = eventDao 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.Dao = eventDao e.Model = m - if err := app.OnModelAfterDelete().Trigger(e); err != nil && app.isDebug { - log.Println(err) - } + return app.OnModelAfterDelete().Trigger(e) } return dao diff --git a/daos/base.go b/daos/base.go index 0a6d0e30..48303f23 100644 --- a/daos/base.go +++ b/daos/base.go @@ -5,6 +5,8 @@ package daos import ( "errors" + "fmt" + "strings" "time" "github.com/pocketbase/dbx" @@ -45,12 +47,12 @@ type Dao struct { ModelQueryTimeout time.Duration // write hooks - BeforeCreateFunc func(eventDao *Dao, m models.Model) error - AfterCreateFunc func(eventDao *Dao, m models.Model) - BeforeUpdateFunc func(eventDao *Dao, m models.Model) error - AfterUpdateFunc func(eventDao *Dao, m models.Model) - BeforeDeleteFunc func(eventDao *Dao, m models.Model) error - AfterDeleteFunc func(eventDao *Dao, m models.Model) + BeforeCreateFunc func(eventDao *Dao, m models.Model, action func() error) error + AfterCreateFunc 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) error + BeforeDeleteFunc func(eventDao *Dao, m models.Model, action func() error) error + AfterDeleteFunc func(eventDao *Dao, m models.Model) error } // 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) if dao.BeforeCreateFunc != nil { - txDao.BeforeCreateFunc = func(eventDao *Dao, m models.Model) error { - return dao.BeforeCreateFunc(eventDao, m) + txDao.BeforeCreateFunc = func(eventDao *Dao, m models.Model, action func() error) error { + return dao.BeforeCreateFunc(eventDao, m, action) } } if dao.BeforeUpdateFunc != nil { - txDao.BeforeUpdateFunc = func(eventDao *Dao, m models.Model) error { - return dao.BeforeUpdateFunc(eventDao, m) + txDao.BeforeUpdateFunc = func(eventDao *Dao, m models.Model, action func() error) error { + return dao.BeforeUpdateFunc(eventDao, m, action) } } if dao.BeforeDeleteFunc != nil { - txDao.BeforeDeleteFunc = func(eventDao *Dao, m models.Model) error { - return dao.BeforeDeleteFunc(eventDao, m) + txDao.BeforeDeleteFunc = func(eventDao *Dao, m models.Model, action func() error) error { + return dao.BeforeDeleteFunc(eventDao, m, action) } } 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}) + return 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}) + return 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}) + return nil } } return fn(txDao) }) - - if txError == nil { - // execute after event calls on successful transaction - // (note: using the non-transaction dao to allow following queries in the after hooks) - for _, call := range afterCalls { - switch call.Action { - case "create": - dao.AfterCreateFunc(dao, call.Model) - case "update": - dao.AfterUpdateFunc(dao, call.Model) - case "delete": - dao.AfterDeleteFunc(dao, call.Model) - } - } + if txError != nil { + return txError } - return txError + // execute after event calls on successful transaction + // (note: using the non-transaction dao to allow following queries in the after hooks) + var errs []error + for _, call := range afterCalls { + var err error + switch call.Action { + case "create": + err = dao.AfterCreateFunc(dao, call.Model) + case "update": + err = dao.AfterUpdateFunc(dao, call.Model) + case "delete": + err = dao.AfterDeleteFunc(dao, call.Model) + } + + 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)") @@ -213,21 +234,23 @@ func (dao *Dao) Delete(m models.Model) error { } return dao.lockRetry(func(retryDao *Dao) error { - if retryDao.BeforeDeleteFunc != nil { - if err := retryDao.BeforeDeleteFunc(retryDao, m); err != nil { + action := func() error { + if err := retryDao.NonconcurrentDB().Model(m).Delete(); err != nil { return err } + + if retryDao.AfterDeleteFunc != nil { + retryDao.AfterDeleteFunc(retryDao, m) + } + + return nil } - if err := retryDao.NonconcurrentDB().Model(m).Delete(); err != nil { - return err + if retryDao.BeforeDeleteFunc != nil { + return retryDao.BeforeDeleteFunc(retryDao, m, action) } - if retryDao.AfterDeleteFunc != nil { - retryDao.AfterDeleteFunc(retryDao, m) - } - - return nil + return action() }) } @@ -258,35 +281,35 @@ func (dao *Dao) update(m models.Model) error { m.RefreshUpdated() + action := func() error { + if v, ok := any(m).(models.ColumnValueMapper); ok { + dataMap := v.ColumnValueMap() + + _, err := dao.NonconcurrentDB().Update( + m.TableName(), + dataMap, + dbx.HashExp{"id": m.GetId()}, + ).Execute() + + if err != nil { + return err + } + } else if err := dao.NonconcurrentDB().Model(m).Update(); err != nil { + return err + } + + if dao.AfterUpdateFunc != nil { + return dao.AfterUpdateFunc(dao, m) + } + + return nil + } + if dao.BeforeUpdateFunc != nil { - if err := dao.BeforeUpdateFunc(dao, m); err != nil { - return err - } + return dao.BeforeUpdateFunc(dao, m, action) } - if v, ok := any(m).(models.ColumnValueMapper); ok { - dataMap := v.ColumnValueMap() - - _, err := dao.NonconcurrentDB().Update( - m.TableName(), - dataMap, - dbx.HashExp{"id": m.GetId()}, - ).Execute() - - if err != nil { - return err - } - } else { - if err := dao.NonconcurrentDB().Model(m).Update(); err != nil { - return err - } - } - - if dao.AfterUpdateFunc != nil { - dao.AfterUpdateFunc(dao, m) - } - - return nil + return action() } func (dao *Dao) create(m models.Model) error { @@ -306,36 +329,36 @@ func (dao *Dao) create(m models.Model) error { m.RefreshUpdated() } + action := func() error { + if v, ok := any(m).(models.ColumnValueMapper); ok { + dataMap := v.ColumnValueMap() + if _, ok := dataMap["id"]; !ok { + dataMap["id"] = m.GetId() + } + + _, err := dao.NonconcurrentDB().Insert(m.TableName(), dataMap).Execute() + if err != nil { + return err + } + } else if err := dao.NonconcurrentDB().Model(m).Insert(); err != nil { + return err + } + + // clears the "new" model flag + m.MarkAsNotNew() + + if dao.AfterCreateFunc != nil { + return dao.AfterCreateFunc(dao, m) + } + + return nil + } + if dao.BeforeCreateFunc != nil { - if err := dao.BeforeCreateFunc(dao, m); err != nil { - return err - } + return dao.BeforeCreateFunc(dao, m, action) } - if v, ok := any(m).(models.ColumnValueMapper); ok { - dataMap := v.ColumnValueMap() - if _, ok := dataMap["id"]; !ok { - dataMap["id"] = m.GetId() - } - - _, err := dao.NonconcurrentDB().Insert(m.TableName(), dataMap).Execute() - if err != nil { - return err - } - } else { - if err := dao.NonconcurrentDB().Model(m).Insert(); err != nil { - return err - } - } - - // clears the "new" model flag - m.MarkAsNotNew() - - if dao.AfterCreateFunc != nil { - dao.AfterCreateFunc(dao, m) - } - - return nil + return action() } func (dao *Dao) lockRetry(op func(retryDao *Dao) error) error { diff --git a/daos/base_test.go b/daos/base_test.go index 4606c32e..9de1ba9d 100644 --- a/daos/base_test.go +++ b/daos/base_test.go @@ -49,33 +49,37 @@ func TestDaoClone(t *testing.T) { dao := daos.NewMultiDB(testApp.Dao().ConcurrentDB(), testApp.Dao().NonconcurrentDB()) dao.MaxLockRetries = 1 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"]++ - 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"]++ - 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"]++ + return action() + } + dao.AfterDeleteFunc = func(eventDao *daos.Dao, m models.Model) error { + hookCalls["AfterDeleteFunc"]++ return nil } - dao.AfterDeleteFunc = func(eventDao *daos.Dao, m models.Model) { - hookCalls["AfterDeleteFunc"]++ - } - dao.AfterUpdateFunc = func(eventDao *daos.Dao, m models.Model) { + dao.AfterUpdateFunc = func(eventDao *daos.Dao, m models.Model) error { 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"]++ + return nil } clone := dao.Clone() clone.MaxLockRetries = 3 clone.ModelQueryTimeout = 4 - clone.AfterCreateFunc = func(eventDao *daos.Dao, m models.Model) { + clone.AfterCreateFunc = func(eventDao *daos.Dao, m models.Model) error { hookCalls["NewAfterCreateFunc"]++ + return nil } if dao.MaxLockRetries == clone.MaxLockRetries { @@ -86,16 +90,18 @@ func TestDaoClone(t *testing.T) { t.Fatal("Expected different ModelQueryTimeout") } + emptyAction := func() error { return nil } + // trigger hooks - dao.BeforeDeleteFunc(nil, nil) - dao.BeforeUpdateFunc(nil, nil) - dao.BeforeCreateFunc(nil, nil) + dao.BeforeDeleteFunc(nil, nil, emptyAction) + dao.BeforeUpdateFunc(nil, nil, emptyAction) + dao.BeforeCreateFunc(nil, nil, emptyAction) dao.AfterDeleteFunc(nil, nil) dao.AfterUpdateFunc(nil, nil) dao.AfterCreateFunc(nil, nil) - clone.BeforeDeleteFunc(nil, nil) - clone.BeforeUpdateFunc(nil, nil) - clone.BeforeCreateFunc(nil, nil) + clone.BeforeDeleteFunc(nil, nil, emptyAction) + clone.BeforeUpdateFunc(nil, nil, emptyAction) + clone.BeforeCreateFunc(nil, nil, emptyAction) clone.AfterDeleteFunc(nil, nil) clone.AfterUpdateFunc(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.MaxLockRetries = 1 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"]++ - 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"]++ - 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"]++ + return action() + } + dao.AfterDeleteFunc = func(eventDao *daos.Dao, m models.Model) error { + hookCalls["AfterDeleteFunc"]++ return nil } - dao.AfterDeleteFunc = func(eventDao *daos.Dao, m models.Model) { - hookCalls["AfterDeleteFunc"]++ - } - dao.AfterUpdateFunc = func(eventDao *daos.Dao, m models.Model) { + dao.AfterUpdateFunc = func(eventDao *daos.Dao, m models.Model) error { 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"]++ + return nil } new := dao.WithoutHooks() @@ -481,12 +490,13 @@ func TestDaoRetryCreate(t *testing.T) { retryBeforeCreateHookCalls := 0 retryAfterCreateHookCalls := 0 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++ 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++ + return nil } model := &models.Admin{Email: "new@example.com"} @@ -507,7 +517,7 @@ func TestDaoRetryCreate(t *testing.T) { // with non-locking error retryBeforeCreateHookCalls = 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++ return errors.New("non-locking error") } @@ -539,12 +549,13 @@ func TestDaoRetryUpdate(t *testing.T) { retryBeforeUpdateHookCalls := 0 retryAfterUpdateHookCalls := 0 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++ 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++ + return nil } if err := retryDao.Save(model); err != nil { @@ -564,7 +575,7 @@ func TestDaoRetryUpdate(t *testing.T) { // with non-locking error retryBeforeUpdateHookCalls = 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++ return errors.New("non-locking error") } @@ -590,12 +601,13 @@ func TestDaoRetryDelete(t *testing.T) { retryBeforeDeleteHookCalls := 0 retryAfterDeleteHookCalls := 0 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++ 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++ + return nil } model, _ := retryDao.FindAdminByEmail("test@example.com") @@ -616,7 +628,7 @@ func TestDaoRetryDelete(t *testing.T) { // with non-locking error retryBeforeDeleteHookCalls = 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++ return errors.New("non-locking error") } @@ -643,13 +655,13 @@ func TestDaoBeforeHooksError(t *testing.T) { 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") } - 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") } - 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") } @@ -688,27 +700,30 @@ func TestDaoTransactionHooksCallsOnFailure(t *testing.T) { 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++ - 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++ - 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++ - return nil + return action() } - baseDao.AfterCreateFunc = func(eventDao *daos.Dao, m models.Model) { + baseDao.AfterCreateFunc = func(eventDao *daos.Dao, m models.Model) error { afterCreateFuncCalls++ + return nil } - baseDao.AfterUpdateFunc = func(eventDao *daos.Dao, m models.Model) { + baseDao.AfterUpdateFunc = func(eventDao *daos.Dao, m models.Model) error { afterUpdateFuncCalls++ + return nil } - baseDao.AfterDeleteFunc = func(eventDao *daos.Dao, m models.Model) { + baseDao.AfterDeleteFunc = func(eventDao *daos.Dao, m models.Model) error { afterDeleteFuncCalls++ + return nil } existingModel, _ := testApp.Dao().FindAdminByEmail("test@example.com") @@ -776,27 +791,30 @@ func TestDaoTransactionHooksCallsOnSuccess(t *testing.T) { 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++ - 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++ - 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++ - return nil + return action() } - baseDao.AfterCreateFunc = func(eventDao *daos.Dao, m models.Model) { + baseDao.AfterCreateFunc = func(eventDao *daos.Dao, m models.Model) error { afterCreateFuncCalls++ + return nil } - baseDao.AfterUpdateFunc = func(eventDao *daos.Dao, m models.Model) { + baseDao.AfterUpdateFunc = func(eventDao *daos.Dao, m models.Model) error { afterUpdateFuncCalls++ + return nil } - baseDao.AfterDeleteFunc = func(eventDao *daos.Dao, m models.Model) { + baseDao.AfterDeleteFunc = func(eventDao *daos.Dao, m models.Model) error { afterDeleteFuncCalls++ + return nil } existingModel, _ := testApp.Dao().FindAdminByEmail("test@example.com") diff --git a/forms/record_upsert.go b/forms/record_upsert.go index 4ddb24c5..43d660c9 100644 --- a/forms/record_upsert.go +++ b/forms/record_upsert.go @@ -744,36 +744,44 @@ func (form *RecordUpsert) Submit(interceptors ...InterceptorFunc[*models.Record] // 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) // but BEFORE the actual record db persistence // --- - dao.BeforeCreateFunc = func(eventDao *daos.Dao, m models.Model) error { - if form.dao.BeforeCreateFunc != nil { - if err := form.dao.BeforeCreateFunc(eventDao, m); err != nil { - return err + 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 m.TableName() == form.record.TableName() && m.GetId() == form.record.GetId() { - return form.processFilesToUpload() + if form.dao.BeforeCreateFunc != nil { + return form.dao.BeforeCreateFunc(eventDao, m, newAction) } - return nil + return newAction() } - dao.BeforeUpdateFunc = func(eventDao *daos.Dao, m models.Model) error { - if form.dao.BeforeUpdateFunc != nil { - if err := form.dao.BeforeUpdateFunc(eventDao, m); err != nil { - return err + 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 action() } - if m.TableName() == form.record.TableName() && m.GetId() == form.record.GetId() { - return form.processFilesToUpload() + if form.dao.BeforeUpdateFunc != nil { + return form.dao.BeforeUpdateFunc(eventDao, m, newAction) } - return nil + return newAction() } // ---