Search code examples
gogo-gorm

Is there a way to execute BeforeCreate and BeforeUpdate hooks for every struct inside a struct?


I have the following code:

type InnerStructA struct {
 A string
}

type InnerStructB struct {
 B string
}

func (a *A) BeforeCreate(scope *gorm.Scope) error {
    return scope.SetColumn("A", uuid.New().String())
}

func (b *B) BeforeCreate(scope *gorm.Scope) error {
    return scope.SetColumn("B", uuid.New().String())
}

type OuterStruct struct {
    InnerStructA
    InnerStructB
    ID      string `gorm:"type:varchar(40);not null"`
}

When I create records:

outerStructObject := OuterStruct{
        ID:          "sample_ID",
    }
err := db.Create(&outerStructObject).Error

only one of the BeforeCreate() hooks gets called. Other BeforeCreate gets skipped.

Gorm version is jinzhu/gorm v1.9.16

Is there a way which makes a call to both hooks when a create statement is called?

Expecting BeforeCreate hooks to be called for both inner structs - InnerStructA and InnerStructB.


Solution

  • Peter's comment is accurate, I'll share my tests and comments here but using the official repo at go-gorm/gorm and latest version v1.25.0 which has very similar results and some changes in the API.

    When creating the Outer registry, inner objects can be set like this:

    func (o *OuterStruct) BeforeCreate(tx *gorm.DB) error {
        fmt.Println("Hook triggered: BeforeCreate for OuterStruct")
        o.InnerStructA.A = uuid.New().String()
        o.InnerStructB.B = uuid.New().String()
        return nil
    }
    

    To trigger inner objects hooks, only by creating them directly:

    innerA := InnerStructA{
        A: "123",
    }
    
    errCreateInner := db.Create(&innerA).Error
    

    Here is a complete isolated example:

    package main
    
    import (
        "fmt"
        "log"
    
        "github.com/google/uuid"
        "gorm.io/driver/sqlite"
        "gorm.io/gorm"
    )
    
    func main() {
    
        db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
        if err != nil {
            panic(err)
        }
    
        db.AutoMigrate(&InnerStructA{}, &InnerStructB{}, &OuterStruct{})
    
        outerStructObject := OuterStruct{
            ID: "sample_ID",
        }
    
        errCreate := db.Create(&outerStructObject).Error
        if err != nil {
            log.Fatal(errCreate)
        }
    
        var outer OuterStruct
        db.Last(&outer)
        fmt.Println("Last register: ", outer)
    
        // innerA := InnerStructA{
        //  A: "123",
        // }
    
        // errCreateInner := db.Create(&innerA).Error
        // if err != nil {
        //  log.Fatal(errCreateInner)
        // }
    }
    
    type InnerStructA struct {
        A string
    }
    
    type InnerStructB struct {
        B string
    }
    
    // This is not being called
    func (a *InnerStructA) BeforeCreate(tx *gorm.DB) error {
        fmt.Println("Hook triggered: BeforeCreate for InnerStructA")
        // ...
        return nil
    }
    
    // This is not being called
    func (b *InnerStructB) BeforeCreate(tx *gorm.DB) error {
        fmt.Println("Hook triggered: BeforeCreate for InnerStructB")
        // ...
        return nil
    }
    
    // This works!
    func (o *OuterStruct) BeforeCreate(tx *gorm.DB) error {
        fmt.Println("Hook triggered: BeforeCreate for OuterStruct")
        o.InnerStructA.A = uuid.New().String()
        o.InnerStructB.B = uuid.New().String()
        return nil
    }
    
    type OuterStruct struct {
        InnerStructA
        InnerStructB
        ID string `gorm:"type:varchar(40);not null"`
    }