6
votes

I am trying to implement two different types of relationships between two domain classes in Grails.

Consider the following; I have two domain classes, an Author and Book class with an Author having many Books.

class Author{           
   String name 
}

class Book{
   String title
   static belongsTo = [author:Author]

}

The above depicts a very basic one to many relationship between an Author and a Book. But I also want an Author to have a concept of a list of favourite books. This would ideally be represented as a separate one to many relationship describing the same Book object as a list and persisted as such.

class Author{          
   String name
   static hasMany = [favouriteBooks: Book]

   static mapping = {
        favouriteBooks joinTable: [name: 'favourite_books',
                key: 'author_id']
   }
}

class Book{
   String title
   static belongsTo = [client:Client]

}

I have tried to describe this as above (among many other methods) and ultimately the database table (favourite_books) is not created. I do not get any errors. This is the only way I can think of doing this without using any extra objects which I would like to avoid to keep the model simple. I feel I'm on the right track but maybe missing some vital piece of the puzzle.

Any help would be much appreciated.

3
have you tried to clean, exit, grails again, then run-app. Sometimes grails does not pick up your domain changes without a cleanikumen
In the first example above you're missing the hasMany property on the Author side and in the second example there's no reference to the favouriteBooks relatiionship on the Book side.Dónal

3 Answers

5
votes

First of all, let's make sure I understand your problem correctly. You have Book and Author domain classes, but there are two relationships between these classes:

  1. An author writes books, so there's a one-to-many relationship between Author and Book. Of course in real life a book may be written by many authors, but it seems in this case we can ignore this.
  2. An author has favourite books, so there's a second many-to-many relationship between Author and Book. This relationship is many-to-many because a particular book could be a favourite of many authors.

So assuming I've understood the problem correctly, let's try to find a solution. First of all, let's add the many-to-many relationship (favourite books):

class Author {
    String name
    static hasMany = [favourites: Book]
}

class Book {
    String title
    static hasMany = [favouritedBy: Author]
    static belongsTo = Author
}

Whenever a many-to-many relationship is defined we have to choose one side as being the owner of the relationship. In this case I've specified

static belongsTo = Author

in the Book class, so Book is the owned side of the relationship and Author is the owner. Because of this we should add favourite books to authors rather than vice versa, see here for further details.

The one-to-many relationship can then be added with:

class Author {
    String name
    static hasMany = [favourites: Book, booksWritten: Book]
}

class Book {
    String title
    static hasMany = [favouritedBy: Author]
    static belongsTo = Author

    Book writtenBy
}

By the way, in your domain model you included the following in the Author class

static mapping = {
    favouriteBooks joinTable: [name: 'favourite_books', key: 'author_id']
}

This will cause the join table to be named instead favourite_books, whereas in my model the join table will default to author_favourites. If for some reason you particularly want the join table to be named like this (e.g. you're trying to map the classes to existing tables), then feel free to include the above.

Finally, if you find yourself struggling with defining domain class mappings, and are more comfortable with creating the tables, then generating the domain classes from them, check out this plugin

3
votes

Finally figured this out. Thanks to Don for pointing me in the direction of the db-reverse-engineer plugin which helped expose the key property that allows for this mapping strategy. Basically it all came down to using GORM's mappedBy association setting to explicitly tell Grails how the multiple hasMany references should be mapped. The class files that worked are as follows:

class Author {

    String name
    static hasMany = [books: Book, favourites: Book]

    // need to disambiguate the multiple hasMany references with the 
    // 'mappedBy' property:
    static mappedBy =   [books: "author",
                        favourites: "authors"]
}

class Book {

    String title
    Author author

    static hasMany = [authors: Author]
    static belongsTo = [Author]
}

Thanks again for the help

1
votes

Modify Book domain class. Remove author mapping.

class Book{
   String title
   static belongsTo = [Author]
}

Look for the new table FAVORITE_BOOKS once clean and run-app.