Your thinking is correct.
These are the Available hooks for updating:
// begin transaction
BeforeSave
BeforeUpdate
// save before associations
// update database
// save after associations
AfterUpdate
AfterSave
// commit or rollback transaction
And they work as expected. Check this example, I'll use SQLite
for simplicity:
package main
import (
"fmt"
"log"
"time"
"github.com/google/uuid"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
type Player struct {
ID *uuid.UUID `gorm:"type:uuid;primaryKey;" json:"ID"`
TeamID *uuid.UUID `gorm:"type:uuid" json:"TeamID"`
Name string `json:"Name"`
gorm.Model
}
type Team struct {
ID *uuid.UUID `gorm:"type:uuid;primaryKey;" json:"ID"`
Players []Player `gorm:"foreignKey:TeamID;references:ID" json:"Players"`
Name string `json:"Name"`
gorm.Model
}
func (player *Player) BeforeUpdate(tx *gorm.DB) (err error) {
if player.TeamID != nil && *player.TeamID != uuid.Nil {
tx.Model(&Team{}).Where("id = ?", *player.TeamID).Update("updated_at", time.Now())
}
return
}
func main() {
newLogger := logger.New(
log.New(log.Writer(), "\r\n", log.LstdFlags),
logger.Config{
SlowThreshold: time.Second,
LogLevel: logger.Info,
IgnoreRecordNotFoundError: true,
Colorful: true,
},
)
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
Logger: newLogger,
})
if err != nil {
log.Fatalf("failed to connect database: %v", err)
}
if err := db.AutoMigrate(&Team{}, &Player{}); err != nil {
log.Fatalf("failed to migrate database: %v", err)
}
teamID := uuid.New()
team := Team{
ID: &teamID,
Name: "Team A",
}
if err := db.Create(&team).Error; err != nil {
log.Fatalf("failed to create team: %v", err)
}
playerID := uuid.New()
player := Player{
ID: &playerID,
TeamID: &teamID,
Name: "Player 1",
}
if err := db.Create(&player).Error; err != nil {
log.Fatalf("failed to create player: %v", err)
}
fmt.Printf("Initial UpdatedAt - Team: %v, Player: %v\n", team.UpdatedAt, player.UpdatedAt)
time.Sleep(2 * time.Second)
if err := db.Model(&player).Update("Name", "Updated Player 1").Error; err != nil {
log.Fatalf("failed to update player: %v", err)
}
var updatedTeam Team
if err := db.First(&updatedTeam, "id = ?", teamID).Error; err != nil {
log.Fatalf("failed to retrieve team: %v", err)
}
var updatedPlayer Player
if err := db.First(&updatedPlayer, "id = ?", playerID).Error; err != nil {
log.Fatalf("failed to retrieve player: %v", err)
}
fmt.Printf("Updated UpdatedAt - Team: %v, Player: %v\n", updatedTeam.UpdatedAt, updatedPlayer.UpdatedAt)
}
The Logger
is enabled to show all raw SQL
sent to the database. When you run:
go run main.go
2025/01/21 12:05:17 /home/wilton/Workspace/gorm/main.go:54
[0.030ms] [rows:-] SELECT count(*) FROM sqlite_master WHERE type='table' AND name="teams"
2025/01/21 12:05:17 /home/wilton/Workspace/gorm/main.go:54
[0.061ms] [rows:2] SELECT sql FROM sqlite_master WHERE type IN ("table","index") AND tbl_name = "teams" AND sql IS NOT NULL order by type = "table" desc
2025/01/21 12:05:17 /home/wilton/Workspace/gorm/main.go:54
[0.018ms] [rows:-] SELECT * FROM `teams` LIMIT 1
2025/01/21 12:05:17 /home/wilton/Workspace/gorm/main.go:54
[0.017ms] [rows:-] SELECT count(*) FROM sqlite_master WHERE type = "index" AND tbl_name = "teams" AND name = "idx_teams_deleted_at"
2025/01/21 12:05:17 /home/wilton/Workspace/gorm/main.go:54
[0.014ms] [rows:-] SELECT count(*) FROM sqlite_master WHERE type='table' AND name="players"
2025/01/21 12:05:17 /home/wilton/Workspace/gorm/main.go:54
[0.045ms] [rows:2] SELECT sql FROM sqlite_master WHERE type IN ("table","index") AND tbl_name = "players" AND sql IS NOT NULL order by type = "table" desc
2025/01/21 12:05:17 /home/wilton/Workspace/gorm/main.go:54
[0.015ms] [rows:-] SELECT * FROM `players` LIMIT 1
2025/01/21 12:05:17 /home/wilton/Workspace/gorm/main.go:54
[0.027ms] [rows:-] SELECT count(*) FROM sqlite_master WHERE type = "table" AND tbl_name = "players" AND (sql LIKE "%CONSTRAINT ""fk_teams_players"" %" OR sql LIKE "%CONSTRAINT fk_teams_players %" OR sql LIKE "%CONSTRAINT `fk_teams_players`%" OR sql LIKE "%CONSTRAINT [fk_teams_players]%" OR sql LIKE "%CONSTRAINT fk_teams_players %")
2025/01/21 12:05:17 /home/wilton/Workspace/gorm/main.go:54
[0.016ms] [rows:-] SELECT count(*) FROM sqlite_master WHERE type = "index" AND tbl_name = "players" AND name = "idx_players_deleted_at"
2025/01/21 12:05:17 /home/wilton/Workspace/gorm/main.go:64
[2.639ms] [rows:1] INSERT INTO `teams` (`id`,`name`,`created_at`,`updated_at`,`deleted_at`) VALUES ("3b2fc6a1-54ee-417b-9959-8849d6a9e403","Team A","2025-01-21 12:05:17.013","2025-01-21 12:05:17.013",NULL) RETURNING `id`
2025/01/21 12:05:17 /home/wilton/Workspace/gorm/main.go:75
[2.272ms] [rows:1] INSERT INTO `players` (`id`,`team_id`,`name`,`created_at`,`updated_at`,`deleted_at`) VALUES ("fbb3a88c-d6fa-4181-bfd7-8803ceb9ba69","3b2fc6a1-54ee-417b-9959-8849d6a9e403","Player 1","2025-01-21 12:05:17.016","2025-01-21 12:05:17.016",NULL) RETURNING `id`
Initial UpdatedAt - Team: 2025-01-21 12:05:17.013606731 -0300 -03, Player: 2025-01-21 12:05:17.016384285 -0300 -03
2025/01/21 12:05:19 /home/wilton/Workspace/gorm/main.go:30
[0.352ms] [rows:1] UPDATE `teams` SET `updated_at`="2025-01-21 12:05:19.019" WHERE id = "3b2fc6a1-54ee-417b-9959-8849d6a9e403" AND `teams`.`deleted_at` IS NULL
2025/01/21 12:05:19 /home/wilton/Workspace/gorm/main.go:83
[2.770ms] [rows:1] UPDATE `players` SET `name`="Updated Player 1",`updated_at`="2025-01-21 12:05:19.02" WHERE `players`.`deleted_at` IS NULL AND `id` = "fbb3a88c-d6fa-4181-bfd7-8803ceb9ba69"
2025/01/21 12:05:19 /home/wilton/Workspace/gorm/main.go:88
[0.258ms] [rows:1] SELECT * FROM `teams` WHERE id = "3b2fc6a1-54ee-417b-9959-8849d6a9e403" AND `teams`.`deleted_at` IS NULL ORDER BY `teams`.`id` LIMIT 1
2025/01/21 12:05:19 /home/wilton/Workspace/gorm/main.go:93
[0.159ms] [rows:1] SELECT * FROM `players` WHERE id = "fbb3a88c-d6fa-4181-bfd7-8803ceb9ba69" AND `players`.`deleted_at` IS NULL ORDER BY `players`.`id` LIMIT 1
Updated UpdatedAt - Team: 2025-01-21 12:05:19.019831936 -0300 -0300, Player: 2025-01-21 12:05:19.020259014 -0300 -0300
Note the UPDATE
statements after waiting for 2
seconds.
It was corrected sent before the update since you are using the BeforeUpdate
hook.
2025/01/21 12:05:19 /home/wilton/Workspace/gorm/main.go:30
[0.352ms] [rows:1] UPDATE `teams` SET `updated_at`="2025-01-21 12:05:19.019" WHERE id = "3b2fc6a1-54ee-417b-9959-8849d6a9e403" AND `teams`.`deleted_at` IS NULL
2025/01/21 12:05:19 /home/wilton/Workspace/gorm/main.go:83
[2.770ms] [rows:1] UPDATE `players` SET `name`="Updated Player 1",`updated_at`="2025-01-21 12:05:19.02" WHERE `players`.`deleted_at` IS NULL AND `id` = "fbb3a88c-d6fa-4181-bfd7-8803ceb9ba69"
Could you test this in your environment and check if you can reproduce the results?