Grails/GORM -- Using 'hasMany' to map an association to an entire class hierarchy

I have the following Class Diagram for a grails application called Projects:

Now I want to create the actual domain classes in grails. But I have an issue with that: I don't know how to map the associations in a "best practice" way. This is best explained by the example of a code implementation of the Project domain class:

package com.pm.projects

class Project {

    static hasMany = [contracts:Contract, customerContracts:CustomerContract, partnerContracts: PartnerContract]

    String property1
    Integer property2
    Date property3
         .
         .
         .
    TypeN propertyN

    static constraints = {
    }
}

I have the feeling that it is bad practice to include all contract subclasses in the hasMany map, because in an UML Class Diagram, subclasses inherit associations of their superclasses. Therefore, from a logical data model point of view, I feel like it would be leaner, clearer just to code:

static hasMany = [contracts:Contract]

Although this felt as the right approach in theory for myself, it turned out to make certain manipulation techniques of the data unavailable. For instance,

pro = Project.get(params.id)
ProPaconList = pro?.partnercontracts
ProCuconList = pro?.customercontracts

to create two lists of contracts, one for partnercontracts and another one for customercontracts.

Is it possible to achieve a similar result without including the subclasses in the hasMany map? And even if yes, the more general Question would be: Can hasMany maps be compared to associations of UML Class Diagramms in a way that it is bad practice to "connect" a class not only with another independent class but also with this other class' subclasses?

I failed to find any information on this topic online, which is a shame because I am very interested in accurate data modelling following best practice and common conventions, not only trial and error.

Answers


You can map just like you felt right:

static hasMany = [contracts:Contract]

Then, if you want to get just the contracts of certain type, you can just create a method on the Project domain class to do so:

class Project {
    static hasMany = [contracts:Contract]

    def findPartnerContracts(){
        return contracts.findAll { contract ->
            contract.instanceOf( PartnerContract )
        }
    }
}

def pro = Project.get(params.id)
def proPaconList = pro?.findPartnerContracts()

I am not sure if this is a best practice, but... it is one way to do it.

Be careful with hasMany associations. If your collection tends to get big overtime, you might have performance issues. Take a look at this presentation by Burt Beckwith.


In this case, I would say that a 'best practice' is very much dependent on how your application will work, and the context that it is in. You could take either approach - I think the choice depends on answers to a few questions:

  • How many contracts will there be? As Felipe says, you could have performance problems by searching a single association.
  • Do you often want to separate contracts into types? If so, your approach may be better - even storing them on different tables. Be aware that if you store sub-classes on the same table, GORM/Hibernate will add a discriminator column so it can discriminate the types, which adds a small complication to the retrieval.
  • You might also consider doing explicit queries to retrieve only want you want from the database directly, instead of relying on GORMs association queries. If you had a lot of data, and wanted some kind of ordering or a subset, it may be better to do explicit queries (encapsulated in methods on Project) to get what you want.

So these are just a few things you might want to think about to decide what your 'best practice' is.

BTW, a small comment on your class diagram: since both Contract sub-classes reference a Timesheet, you could move that association up to Contract, and let them both inherit it.


Need Your Help

$.getJSON .always() method won't execute on HTTP error

javascript jquery error-handling getjson

I'm trying to retrieve some google calendar information using getJson. In some cases the calendars I'm trying to retrieve may or may not be available and I'll get either GET HTTP error 403 or 404, ...

How long does opening the Process tab in Build Definition file take for you

tfs tfs2012 tfsbuild tfs2013

In my project, opening the Process tab in my Build Definition file in VisualStudio 2013 and 2012 takes 2-3 minutes.