Grails/GORM: creating one-to-one optional relationship
Asked Answered
I

1

6

I'm designing a system in which posts/discussions between users can be upgraded to become tickets. At one particular place I'm trying to create a one-to-one optional relationship but am running into certain issues. A condensed version of the entities in the spotlight is given below.

Rules:

  1. A Post can become a Ticket if required. (optional)
  2. A Ticket must have a Post. (mandatory)

Post.groovy

class Post {

        String title
        String description
        String postedBy

        Ticket ticket

        static hasMany = [comments: Comment]

    static constraints = {
        title(blank:false)
        description(blank:false)
        postedBy(blank:false)
        ticket (nullable:true,unique:true)
    }
}

Ticket.groovy

class Ticket {

        String title
        String description
        String postedBy

        Post post

        static hasMany = [responses: Response]

        static constraints = {
                title(blank:false)
                description(blank:false)
                postedBy(blank:false)
                post (nullable:false,unique:true)
        }

}

This works to some extent. I can:

  1. Create a Post leaving the ticket attribute null If and when the post is upgraded to become a ticket
  2. I can explicitly set the Post's ticket attribute to point to the parent ticket.

However, this mapping isn't enforced at the domain level. It leaves room for a situation where Ticket1 points to Post1, but Post1 points to Ticket2 instead.

I tried using a static hasOne = [post: Post] in the Ticket class but later learned that it mandates the presence of a static belongsTo = [ticket: Ticket] in the Post class and this becomes a mandatory 1-to-1 relationship which is not what I'm looking for.

Is there a way to achieve this 1-to-1 optional mapping in this scenario? Any pointers would be most helpful.

Ivett answered 7/10, 2011 at 8:8 Comment(2)
Please close question, if it's answered to your satisfaction. Thanks! :-)Burble
It doesn't work. I don't think 1-1 can be created. I probably should close it as unanswerable?Ivett
B
3

You could consider making a custom validator like

class Post {
  // Other fields

  Ticket ticket

  static constraints = {
    // Other constraints
    ticket (nullable:true,unique:true, validator: { val, obj ->
       if(val) {
         return val.post == obj
       }
    })
  }
}

Would this solve your problem?

Burble answered 7/10, 2011 at 9:2 Comment(4)
Hi, thanks for your solution! It works (with the small edit made) and is better than the earlier situation since now there is validation at at least one end. However, now there is still a possibility that I set the correct ticket argument in Post (since the validator enforces it) but then go back to Ticket and change the Post object that it points to. I was just wondering if there is any way to enforce it from both ends, but I guess there isn't? :(Ivett
How about another validation in the other end? It should be possible?Burble
How did you come along? Any chance you will be closing this question?Burble
Hi, sorry for the delay. I tried with a validator at the other end but it creates a conflict since one of the values will have to be set before the other; still trying, will let you know of the result soon.Ivett

© 2022 - 2024 — McMap. All rights reserved.