GORM Playground Link

https://github.com/go-gorm/playground/pull/593

Description

When you save a model which BeforeSave is implemented in a transaction, BeforeSave will be executed twice.

package main_test

import (
    "testing"

    "github.com/stretchr/testify/assert"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

func Test_main(t *testing.T) {
    dsn := "root:pass@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn))
    assert.NoError(t, err)
    assert.NoError(t, db.AutoMigrate(&User{}, &Token{}))

    u := User{
        Name: "user",
        Token: Token{
            Content: "token",
        },
    }
    u1, err := saveUser(db, &u)
    assert.NoError(t, err)
    assert.Equal(t, "token_encrypted", u1.Token.Content)

    u = User{
        ID:   u.ID,
        Name: "user",
        Token: Token{
            Content: "token2",
        },
    }
    u2, err := saveUser(db, &u)
    assert.NoError(t, err)
    assert.Equal(t, "token2_encrypted", u2.Token.Content) // FAIL: actual is token2_encrypted_encrypted
}

func saveUser(db *gorm.DB, u *User) (*User, error) {
    var newUser User
    if err := db.Transaction(func(tx *gorm.DB) error {
        if err := tx.Debug().Session(&gorm.Session{FullSaveAssociations: true}).Save(u).Error; err != nil {
            return err
        }

        if err := tx.Preload("Token").First(&newUser, u.ID).Error; err != nil {
            return err
        }

        return nil
    }); err != nil {
        return nil, err
    }

    return &newUser, nil
}

// - Models

type User struct {
    ID    int    `gorm:"primary_key"`
    Name  string `gorm:"type:varchar(100)"`
    Token Token  `gorm:"foreignKey:UserID"`
}

type Token struct {
    UserID  int    `gorm:"primary_key"`
    Content string `gorm:"type:varchar(100)"`
}

// This method should be called only once
func (t *Token) BeforeSave(tx *gorm.DB) error {
    t.Content += "_encrypted"
    return nil
}
$ go test main_test.go

2023/05/01 11:00:35 /home/ras/misc/go/gorm/main_test.go:42
[2.085ms] [rows:1] INSERT INTO `tokens` (`content`,`user_id`) VALUES ('token_encrypted',7) ON DUPLICATE KEY UPDATE `content`=VALUES(`content`)

2023/05/01 11:00:35 /home/ras/misc/go/gorm/main_test.go:42
[5.084ms] [rows:1] INSERT INTO `users` (`name`) VALUES ('user')

2023/05/01 11:00:35 /home/ras/misc/go/gorm/main_test.go:42
[2.312ms] [rows:2] INSERT INTO `tokens` (`content`,`user_id`) VALUES ('token2_encrypted',7) ON DUPLICATE KEY UPDATE `content`=VALUES(`content`)

2023/05/01 11:00:35 /home/ras/misc/go/gorm/main_test.go:42
[4.827ms] [rows:0] UPDATE `users` SET `name`='user' WHERE `id` = 7

2023/05/01 11:00:35 /home/ras/misc/go/gorm/main_test.go:42
[2.382ms] [rows:2] INSERT INTO `tokens` (`content`,`user_id`) VALUES ('token2_encrypted_encrypted',7) ON DUPLICATE KEY UPDATE `content`=VALUES(`content`)

2023/05/01 11:00:35 /home/ras/misc/go/gorm/main_test.go:42
[5.107ms] [rows:0] INSERT INTO `users` (`name`,`id`) VALUES ('user',7) ON DUPLICATE KEY UPDATE `name`=VALUES(`name`)
--- FAIL: Test_main (0.12s)
    main_test.go:36: 
                Error Trace:    /home/ras/misc/go/gorm/main_test.go:36
                Error:          Not equal: 
                                expected: "token2_encrypted"
                                actual  : "token2_encrypted_encrypted"

                                Diff:
                                --- Expected
                                +++ Actual
                                @@ -1 +1 @@
                                -token2_encrypted
                                +token2_encrypted_encrypted
                Test:           Test_main
FAIL
FAIL    command-line-arguments  0.125s
FAIL

Comment From: github-actions[bot]

The issue has been automatically marked as stale as it missing playground pull request link, which is important to help others understand your issue effectively and make sure the issue hasn't been fixed on latest master, checkout https://github.com/go-gorm/playground for details. it will be closed in 30 days if no further activity occurs. if you are asking question, please use the Question template, most likely your question already answered https://github.com/go-gorm/gorm/issues or described in the document https://gorm.io ✨ Search Before Asking

Comment From: GuridMa

Have you solved this? I also encountered the same problem