Search code examples
sqlgogo-gorm

How to delete related models of a relation in Gorm?


So basically I have 3 models: User, Profile, and Post.

They are related like so: User has one Profile. Profile has many Post

They look like this:

type User struct {
    Base             // holds this object's uuid, createdAt, updatedAt
    Role     string  `json:"role"`
    Username string  `json:"username" gorm:"unique"`
    Password string  `json:"password"`
    Profile  Profile `gorm:"constraint:OnDelete:CASCADE;"`
}

type Profile struct {
    Base          // holds this object's uuid, createdAt, updatedAt
    UserId string `json:"user_id"`
    Name   string `json:"name"`
    Bio    string `json:"bio"`
    Age    uint8  `json:"age"`
    Posts  []Post `gorm:"constraint:OnDelete:CASCADE;"`
}

type Post struct {
    Base             // holds this object's uuid, createdAt, updatedAt
    ProfileId string `json:"profile_id"`
    Caption   string `json:"caption"`
    Likes     uint32 `json:"num_likes" gorm:"default:0"`
}

What I want to happen is when I delete the user, I want the profile to be deleted and all the posts that are related to it. My only other experience with relational databases are Django where this is automatic.

What actually happens is when I delete the user, the profile gets deleted but the posts remain in the database.

This is how I am deleting the user:

...
base := models.Base{Id: id}
if err := configs.Database.Select(clause.Associations).Delete(&models.User{Base: base}).Error; err != nil {
    return c.Status(400).JSON(err.Error())
}
...

I've already looked at this but its not very helpful. How could I accomplish this?

Thank you!


Solution

  • Based on the issue link you posted, and other related issues, it might not be possible to use clause.Associations for relations nested deeper than one level. In your case, the Profile related to your User is deleted, but not the Posts related to the Profile.

    One way to delete all wanted associations is to use a delete hook. Either BeforeDelete or AfterDelete, depending on your setup and how strong your relationships are. For example:

    func (u *User) BeforeDelete(tx *gorm.DB) (err error) {
      if err := tx.Joins("profiles p ON p.id = posts.profile_id").Joins("users u ON u.id = p.user_id").Where("u.id = ?", u.Base.Id).Delete(&Post{}).Error; err != nil {
        return err
      } 
      return  tx.Joins("users u ON u.id = profiles.user_id").Where("u.id = ?", u.Base.Id).Delete(&Profile{}).Error
    }
    

    This way, when you execute configs.Database.Delete(&models.User{Base: base}), it will execute the hook first, then this query.

    Another way would be to execute all the queries from the hook function separately:

    base := models.Base{Id: id}
    if err := configs.Database.Joins("profiles p ON p.id = posts.profile_id").Joins("users u ON u.id = p.user_id").Where("u.id = ?", base.Id).Delete(&Post{}).Error; err != nil {
        return c.Status(400).JSON(err.Error())
    }
    
    if err := configs.Database.Joins("users u ON u.id = profiles.user_id").Where("u.id = ?", base.Id).Delete(&Profile{}).Error; err != nil {
        return c.Status(400).JSON(err.Error())
    }
    
    if err := configs.Database.Delete(&models.User{Base: base}).Error; err != nil {
        return c.Status(400).JSON(err.Error())
    }