Skip to content

Commit

Permalink
feat: BeforeUpdate hook supports update using struct
Browse files Browse the repository at this point in the history
  • Loading branch information
demoManito committed Aug 26, 2024
1 parent 4a50b36 commit 671e7db
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 0 deletions.
23 changes: 23 additions & 0 deletions callbacks/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ func SetupUpdateReflectValue(db *gorm.DB) {
func BeforeUpdate(db *gorm.DB) {
if db.Error == nil && db.Statement.Schema != nil && !db.Statement.SkipHooks && (db.Statement.Schema.BeforeSave || db.Statement.Schema.BeforeUpdate) {
callMethod(db, func(value interface{}, tx *gorm.DB) (called bool) {
rv := reflect.ValueOf(value)
for rv.Kind() == reflect.Ptr {
rv = rv.Elem()
}
// save a copy before executing the hook so that can find out which fields were modified after the hook is executed.
rvClone := reflect.New(rv.Type()).Elem()
rvClone.Set(rv)

if db.Statement.Schema.BeforeSave {
if i, ok := value.(BeforeSaveInterface); ok {
called = true
Expand All @@ -47,6 +55,21 @@ func BeforeUpdate(db *gorm.DB) {
}
}

if called {
for _, field := range db.Statement.Schema.Fields {
if field.PrimaryKey {
continue
}
dbFieldName, ok := field.TagSettings["COLUMN"]
if !ok {
continue
}
// compare with the copy value and update the field if there is a difference
if !reflect.DeepEqual(rv.FieldByName(field.Name).Interface(), rvClone.FieldByName(field.Name).Interface()) {
db.Statement.SetColumn(dbFieldName, rv.FieldByName(field.Name).Interface())
}
}
}
return called
})
}
Expand Down
36 changes: 36 additions & 0 deletions tests/hooks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -609,3 +609,39 @@ func TestPropagateUnscoped(t *testing.T) {
t.Fatalf("unscoped did not propagate")
}
}

type StructUpdate struct {
ID uint `gorm:"column:id;primary_key"`
Version int `gorm:"column:version"`
Name string `gorm:"column:name"`
}

func (StructUpdate) TableName() string {
return "struct_updates"
}

func (su *StructUpdate) BeforeUpdate(*gorm.DB) error {
su.Version++
return nil
}

func TestBeforeUpdateWithStructColumn(t *testing.T) {
DB.Migrator().DropTable(&StructUpdate{})
DB.AutoMigrate(&StructUpdate{})

su := StructUpdate{
ID: 1,
Version: 1,
}
err := DB.Create(&su).Error
if err != nil {
t.Fatalf("create struct failed: %v", err)
}

sql := DB.ToSQL(func(tx *gorm.DB) *gorm.DB {
return tx.Model(&su).Update("name", "demoManito")
})
if sql != "UPDATE `struct_updates` SET `user_updates`.`name` = `demoManito`, `user_updates`.`version` = 2 WHERE `user_updates`.`id` = 1" {
t.Fatalf("invalid sql: %v", sql)
}
}

0 comments on commit 671e7db

Please sign in to comment.