go-gorm / gorm.io

GORM official site
https://gorm.io
253 stars 354 forks source link

Gorm Delete Associations with Select triggers hook with an empty object receiver #599

Open duke-alibubu opened 1 year ago

duke-alibubu commented 1 year ago

When deleting associations with Select, the association's hook will be triggered, but with an empty object receiver.

For example, if we have the following structs:

type Party struct {
    ID   uint64
    Name string
    Dogs []Dog
}

type Dog struct {
    ID      uint64
    Breed   string
    Age     uint64
    PartyID uint64
}

Dog struct has a BeforeDelete hook like this:

func (d *Dog) BeforeDelete(tx *gorm.DB) error {
    fmt.Println(d)
    return nil
}

When the progream deletes the associations with tx.Select("Dogs").Delete(p) - BeforeDelete hook will be fired, with an empty receiver. In the above example, the value printed out is: &{0 0 0}.

The full example is given here: https://github.com/duke-alibubu/gorm_select_delete_issue/blob/master/main.go

My own speculation is probably under the hood, Delete Associations will fire db.Where("associate_id = ?", aid).Delete(&Model{}) thus triggering hooks on an empty object. However, this behaviour is not good and should be improved - like firing the hook with the proper object receiver, or just skip the hooks when deleting associations.

StevenOng97 commented 1 year ago

is there any solution?

muse254 commented 2 months ago

Think of it as a call chain.

type ObjectA struct {
    gorm.Model
    Name    string
    ObjectB []ObjectB `gorm:"foreignKey:ObjectAID"`
}

type ObjectB struct {
    gorm.Model
    ObjectAID uint32 `gorm:"column:object_a_id"`
    Name      string
}

func deletionCall(db *gorm.DB) {
    _ = db.Clauses(clause.Returning{Columns: []clause.Column{{Name: "id"}}}).
        Where("id", 1).
        Delete(&ObjectA{}).Error
}

func (a *ObjectA) AfterDelete(tx *gorm.DB) (err error) {
    tx.
        // if we have a trigger on ObjectA we can chain again as commented
        // Clauses(clause.Returning{Columns: []clause.Column{{Name: "id"}}}).
        Where("object_a_id = ?", a.ID).Delete(&ReturnablesCustomerLedger{})
    return
}