Multiple hasMany relationships to same domain class in Grails

NickForrer picture NickForrer · Sep 24, 2011 · Viewed 15.9k times · Source

I'm using Grails, and I have a domain model with multiple hasMany attributes to the same domain class, which looks like this:

static hasMany = [ posts : Post, likes : Post, dislikes : Post ]

The problem that I'm running into is that when I add something to the posts list, it also somehow makes it into the likes and dislikes lists. At least, that's how it looks when I iterate through each of those lists.

I think that the issue is that I also have the following relationship in my Post domain:

static belongsTo = [ contributer : Contributer ]

What is the best way of going about configuring these relationships to make my model work? Any suggestions?


@Wayne,

I tried using your test as well, and it passed successfully. So, the only thing that I can think of is that there is something wrong with my save method in my PostController. I have pasted the relavent code below (I am using the Spring Security Core plugin, and my Contributer class extends the User class that is created with that plugin):

@Secured(['IS_AUTHENTICATED_FULLY'])
def save = {
def props = [title:params.title, post:params.post,   category:Category.get(params.category.id)]

def user = Contributer.get(springSecurityService.principal.id)
def postInstance = new Post(props)

postInstance.contributer = user
if (postInstance.save(flush: true)) {
  flash.message = "${message(code: 'default.created.message', args: [message(code: 'post.label', default: 'Post'), postInstance.id])}"
  redirect(action: "show", id: postInstance.id)
}
else {
  render(view: "create", model: [postInstance: postInstance])
}
}

Is there anything that stands out here?

Answer

proflux picture proflux · Sep 26, 2011

The problem is that you have a one to many between Post and Contributor (post has an author, author has many posts) as well as two many to many relationships between Post and Contributor (post has many likers, likers like many posts) (post has many dislikers, dislikers dislike many posts). The belongsTo in Post does explain the behavior, but removing it will not fix the problem, just create different ones. The end result is that GORM conventions are going to fall short so you have to tell GORM how to behave or model things differntly.

There are several options, but the one that jumps to mind is to model Vote separately from Post and make it so that a Contributor hasMany likeVotes and hasMany dislikeVotes

class Vote {

   // for illustration here, you need to think about the 
   // cascading behavior that makes sense and model it if you decide 
   // to go this route. 
  belongsTo = [post, contributor] 

}

class LikeVote extends Vote {
}

class DislikeVote extends Vote {
}

GORM will model this as one vote table with a discriminator column to separate likes and dislikes; this will let you eliminate the conflicts between likes, dislikes, and authored posts.

Then in Contributor

 hasMany = [likes:LikeVote, dislikes:DislikeVote, posts:Post]

The relationships are cleared up now:

  1. Post has many likeVotes
  2. Post has many dislikeVotes
  3. Contributor has many likeVotes
  4. Contributor has many dislikeVotes
  5. Post has one contributor
  6. Contributor has many posts

GORM can understand these relationships and will behave appropriately.

If you don't like this option, the next step would be to specify custom mappings for your database structure and then use mappedBy to differentiate the various relationships. This is the approach to take if you absolutely want to have a Contributor relate directly to Post in three different ways.