Search code examples
grailsgroovyuniquerelationshipsjoin

Grails addTo duplicates


I have the following association set up:

class Applicant {
    String appStepsComplete
    String name
    String eNumber
    String email
    String homePhone
    String cellPhone
    String address
    Integer age

    static hasMany = [scholarships:Scholarship]

    static mapping = {
        scholarships joinTable: [name:"APPLICANT_SCHOLARSHIPS"]
    }
}

class Scholarship {
    String fundCode
    String seqNo
    String name

    static belongsTo = Applicant
}

When I am calling this it is allowing for duplicates to be added to the database:

 applicant.scholarships << schol
 applicant.save()

I need it to prevent duplicates in the database. I tried setting a unique constraint on scholarships in applicant by doing the following, but it didn't work:

static constraints = {
    scholarships(unique:true)
}

Solution

  • Burt Beckwith's comment is right, you need to override hashCode and equals on Scholarship. Assuming the business key (the combination of fields that uniquely identify this entity, which you'd use as a composite natural key if the database wasn't using an artificial id) for this is a combination of fundCode and seqNo you could have something like:

    int hashCode() {
        (fundCode + seqNo).hashCode()
    }
    
    boolean equals(Object other) {
        other?.getClass() == this.class
        && other.fundCode == fundCode 
        && other.seqNo == seqNo
    }
    

    The hashCode implementation is probably not the best-performing thing ever, it is a lazy way to do it, leaning on String's hashCode. But it's enough to tell whether it fixes the dupe problem.

    A DRYer solution is to use an AST transformation with this annotation

    import groovy.transform.EqualsAndHashCode
    
    @EqualsAndHashCode(includes=['fundCode', 'seqNo'])
    class Scholarship {
        String fundCode
        String seqNo
        String name
    
        static belongsTo = Applicant
    }
    

    which will generate the equals and hashCode methods for you.

    The Set implementation relies on these methods to decide whether two object instances represent the same information. Not overriding means the only check is for whether the references are the same (so in the case where you have different object instances that have the same information, they would be treated as two different objects). Using the business information instead of the id to check equality means it will work regardless of whether the domain objects have an id assigned.