Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Grails (GORM) Many-To-One Cascade Delete Behaviour

I've been struggling to produce the right configurations to produce cascade-delete behaviour in a relatively simple Grails project.

Say I have the following simple domain classes:

class Author {
    String name
    static constraints = {
    }
}

and

class Book {
    String title
    Author author
    static constraints = {
    }
}

If I create an author, and then create a book written by that author, I am not able to delete the Author without first manually deleting the book. I get an "Integrity constraint violation". This isn't suprising as MySQL (my underlying database) is created by Grails with a "foreign key constraint" on the "author" column of the "book" table as "Restrict" (and this behaviour is consistent with expectations from the Grails documentation as I understand it).

Now, if I were to manually change the underlying database constraint on the "author" column of the book table from "Restrict" to "Cascade", I get the behaviour I want. Namely, that if you delete the Author, all their books are also deleted.

So, what I'd like to do is change my Grails "Book" class in a way that creates the "book" table with "on delete cascade" on the author column. I've been reading plenty of information about doing this sort of thing and GORM defaults using "belongsTo" and explicit "mappings".

One way to do this, based on the documentation for "belongsTo", seemed to be to change the line in the Book class from:

Author author

to

static belongsTo = [author:  Author]

Thereby making it explicit that the Author is the "owning side" of the relationship. The documentation seems to suggest that this should generate the cascade-delete behaviour I'm after. However, it doesn't work unless I add an explicit "hasMany = [books:Book]" into the Author class. I don't wish to do this. (This wish makes more sense in my actual business domain, but even just as an exercise in understanding, I don't yet get why I have to have the Author domain class know about books explicitly).

I'd just like a grails setting to change the Book class to produce the "cascade delete" setting in the database, without having to change the Author class. I tried using an explicit mapping like:

static mapping = {
        author cascade: 'all'
}

and combinations of this with other explicit mapping or "belongsTo" options. This didn't work.

Noting that a simple change to the "constraints" in the underlying SQL database provides the behaviour I want, is there a way to get this through Grails? If not, have I misunderstood something fundamental here or am I trying to do something stupid?

like image 900
Glennn Avatar asked Nov 04 '22 00:11

Glennn


1 Answers

I think you are missing the required field of type Book in Author.

Here's the sample code, which is as per the documentation (tested and works)

class Author {

    String name
    Book book //you are probably missing this field

    static constraints = {
    }
}

class Book {

    String name

    static belongsTo = [author: Author]

    static constraints = {
    }
}

Test case:

@TestFor(Author)
@Mock([Book])
class AuthorTests {

    @Test
    void testAuthorBookCascades() {

        Author a = new Author(name: "Douglas Adams")
        Book b = new Book(name: "So Long, and Thanks for all the Fish")
        a.book = b
        a.save()

        assert Author.count() == 1
        assert Book.count() == 1

        a.delete()

        assert Author.count() == 0
        assert Book.count() == 0
    }
}

As you can see, you need the Book argument in Author. No need for the hasMany or hasOne clause.

like image 160
Sagar V Avatar answered Nov 15 '22 06:11

Sagar V