Search code examples
grailsgrails-ormrelationshiphas-manybelongs-to

Grails: creating a many-to-many-to-many relationship?


So I'm currently building a Grails application, and I am trying to allow users to create posts, and those posts have comments as well.

The association I am trying to create is something like:

A User can have 0 to many Posts, and Posts can have 0 to many Comments

I tried to set this up with my domain classes as the following:

class User implements Serializable{
    static hasMany = [created_posts: Post]
    String username
    String password
}

class Post {
    static hasMany = [comments: Comment]
    String description
}

class Comment {
    String comment_body
    static belongsTo = [post: Post]
}

I tried playing with the mappedBy property as well, like the following:

class User implements Serializable{
    static hasMany = [created_posts: Post]
    static mappedBy = [created_posts: 'creator']
    String username
    String password
}

class Post {
    static hasMany = [comments: Comment]
    static belongsTo = [creator: User]
    String description
}

class Comment {
    String comment_body
    static belongsTo = [post: Post]
}

But it would still not work. The code I am using to create this is the following:

Post new_post = new Post(description: "testing").save()
User user = User.get(springSecurityService.principal.id)
user.addToCreated_posts(new_post).save()

Unfortunately, Grails cannot seem to execute the last line, and sometimes it would not compile at all, giving a :bootRun error.

I am unsure of how to create the proper associations between the Users, Posts, and Comments. Any help is welcome and greatly appreciated!


EDIT: my current code: (compiles, but does not save to user's set)

class User implements Serializable{
    static hasMany = [createdPosts: Post]
    static mappedBy = [createdPosts: 'creator']
    String username
    String password
}

class Post {
    static hasMany = [comments: Comment]
    static belongsTo = [creator: User]
    String description
    static constraints = { creator nullable: true }
}

class Comment {
    String comment_body
    static belongsTo = [post: Post]
}

The code I am using in my controller:

User user = User.get(springSecurityService.principal.id)
Post new_post = new Post(description: "testing description", creator: user).save()
user.save()
System.out.println("post saved to user? " + user.getCreatedPosts())

Output is:

post saved to user? []

Solution

  • The issue is more than likely the fact that the save() function does not return the object that was saved.

    Initially, I changed your code to:

    Post new_post = new Post(description: "testing")
    new_post.save()
    User user = User.get(springSecurityService.principal.id)
    user.addToCreated_posts(new_post).save()
    

    Which did compile. But, there is one issue remaining - instead of running user.addToCreated_posts, you'll have to set the property creator of new_post, since you did not explicitly specify that it can be null when you created your Post domain class. Therefore, the call to save() will silently fail.

    If you change the code to:

    User user = User.get(springSecurityService.principal.id)
    Post new_post = new Post(description: "testing", creator: user)
    new_post.save()
    

    Your code should work.

    It is also probably a good idea to replace all of your calls to save() with save(failOnError: true), so that when a save fails, an exception is thrown. You can catch this exception, and extract error messages from the exception. Usually, the error messages are related to failed validations for the domain object.

    This was tested on Grails 3.2.7, using H2 (the default, in-memory database packaged with Grails).

    EDIT Here's an updated snippet based on the update that was made to the question.

    You are running:

    User user = User.get(springSecurityService.principal.id)
    Post new_post = new Post(description: "testing description", creator: user).save()
    user.save()
    System.out.println("post saved to user? " + user.getCreatedPosts())
    

    I would change this to:

    User user = User.get(springSecurityService.principal.id)
    Post new_post = new Post(description: "testing description", creator: user)
    new_post.save(failOnError: true)
    System.out.println("post saved to user? " + user.getCreatedPosts())
    

    The reason I would do this is because you aren't saving the user - you are saving the post. When you get created posts for a user, Grails searches the database for posts where the creator_id column is set to the id of the user you called the method from.

    Also, I would make sure that you did not remove the following line from User.groovy:

    static hasMany = [createdPosts: Post]
    static mappedBy = [createdPosts: "creator"]
    

    You will absolutely need the following in Post.groovy:

    static belongsTo = [creator: User]
    

    This way, Grails knows how it is supposed to look up Posts for a user.

    Once you make these changes, make sure you start with a fresh DB - for H2 in memory, this should be done automatically, so long as you are using the development environment - and restart your server.