WoJ
WoJ

Reputation: 30045

How to cascade a DELETE in GORM?

I have tried everything I could think about to obtain a cascaded delete in GROM (deleting an object also deletes the tree of objects below). The code below is an attempt to do that and I tried with a HasMany, BelongsTo and HasMany+BelongsTo variant (by changing the types).

The current version is Hasmany+BelongsTo - the result is always the same: only the top object is soft-deleted (person) but nothing below

package main

import (
    "github.com/glebarez/sqlite"
    "github.com/rs/zerolog/log"
    "gorm.io/gorm"
)

type Person struct {
    gorm.Model
    Name  string
    Pills []Pill `gorm:"constraint:OnDelete:CASCADE"`
}

type Pill struct {
    gorm.Model
    Name       string
    PersonID   uint
    Person     Person
    Posologies []Posology `gorm:"constraint:OnDelete:CASCADE"`
}

type Posology struct {
    gorm.Model
    Name   string
    PillID uint
    Pill   Pill
}

func main() {
    var err error
    // setup database
    db, err := gorm.Open(sqlite.Open("test-gorm.db"), &gorm.Config{})
    if err != nil {
        log.Fatal().Msgf("failed to open follow-your-pills.db: %v", err)
    }
    // migrate database
    err = db.AutoMigrate(&Person{}, &Pill{}, &Posology{})
    if err != nil {
        log.Fatal().Msgf("failed to migrate database: %v", err)
    }
    log.Info().Msg("database initialized")

    db.Save(&Person{Name: "John"})
    db.Save(&Pill{Name: "Paracetamol", PersonID: 1})
    db.Save(&Pill{Name: "Aspirin", PersonID: 1})
    db.Save(&Posology{Name: "1x/day", PillID: 1})

    var person Person
    db.Find(&person)
    db.Delete(&person)
    log.Info().Msgf("person: %v", person)
}

Before giving up (something I really would like to avoid), or using a solution for a previous question (where OP ended up manually deleting all the objects) I would like to understand if I am completely missing the point of GORM (and its mechanisms) or if this is how it is and if I want to have cascaded deletes I will have to do them myself (which, as I think it now, may be the right solution after all because I see in the DB structure that DELETE CASCADE is part of the schema

CREATE TABLE `pills` (`id` integer,`created_at` datetime,`updated_at` datetime,`deleted_at` datetime,`name` text,`person_id` integer,PRIMARY KEY (`id`),CONSTRAINT `fk_people_pills` FOREIGN KEY (`person_id`) REFERENCES `people`(`id`) ON DELETE CASCADE)

EDIT Following up on the last idea, I tried a

delete from people where id=1

and it correctly deleted the whole tree

Upvotes: 5

Views: 8286

Answers (2)

Canberk Sinangil
Canberk Sinangil

Reputation: 1052

I had a very similar case.

My parent table had a soft deletion, but all the associated tables had a hard deletion.

This was the solution for my case: Delete Associations

    if err := tx.Select(clause.Associations).Delete(&YOUR_MODEL).Error; err != nil {
        return err
    }

So, the parent table is soft deleted, and all the associations are hard deleted.

Upvotes: 0

Shahriar Ahmed
Shahriar Ahmed

Reputation: 625

gorm:"constraint:OnDelete:CASCADE" is used for hard delete. But gorm doesn't do hard delete by default if you don't enforce by clause.Unscoped(). It does soft delete by updating the deleted_at field. You can achieve soft delete cascade by after delete hook. Related github issue.

type ModelA struct {
    ID         uint
    ModelBList []ModelB
}

type ModelB struct {
    ID         uint
    ModelAID   uint
    ModelCList []ModelC
}

type ModelC struct {
    ID       uint
    ModelBID uint
}

func (m *ModelA) AfterDelete(tx *gorm.DB) (err error) {
    tx.Clauses(clause.Returning{}).Where("model_a_id = ?", m.ID).Delete(&ModelB{})
    return
}

func (m *ModelB) AfterDelete(tx *gorm.DB) (err error) {
    tx.Clauses(clause.Returning{}).Where("model_b_id = ?", m.ID).Delete(&ModelC{})
    return
}

Upvotes: 4

Related Questions