views:

548

answers:

2

Grails GORM does not persist abstract domain classes to the database, causing a break in polymorphic relationships. For example:

abstract class User {
String email
String password
static constraints = {
    email(blank:false, nullable:false,email:true)
    password(blank:false, password:true)
}

static hasMany = [membership:GroupMembership]
}

class RegularEmployee extends User {}
class Manager extends User {
Workgroup managedGroup
}

class Document {
String name
String description
int fileSize
String fileExtension
User owner
Date creationTime
Date lastModifiedTime
DocumentData myData
boolean isCheckedOut
enum Sensitivity {LOW,MEDIUM,HIGH}
def documentImportance = Sensitivity.LOW

static constraints = {
    name(nullable:false, blank:false)
    description(nullable:false, blank:false)
    fileSize(nullable:false)
    fileExtension(nullable:false)
    owner(nullable:false)
    myData(nullable:false)
}

}

causes

Caused by: org.hibernate.MappingException: An association from the table document refers to an unmapped class: User ... 25 more 2009-11-11 23:52:58,933 [main] ERROR mortbay.log - Nested in org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'messageSource': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionManager': Cannot resolve reference to bean 'sessionFactory' while setting bean property 'sessionFactory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory': Invocation of init method failed; nested exception is org.hibernate.MappingException: An association from the table document refers to an unmapped class: User: org.hibernate.MappingException: An association from the table document refers to an unmapped class: User

But in this scenario, I want the polymorphic effects of allowing any user to own a document, while forcing every user of the system to fit into one of the defined roles. Hence, User should not be directly instantiated and is made abstract.

I don't want to use an enum for roles in a non-abstract User class, because I want to be able to add extra properties to the different roles, which may not make sense in certain contexts (I don't wanna have a single User with role set to RegularEmployee that somehow gets a not null managedGroup).

Is this a bug in Grails? Am I missing something?

+1  A: 

You might like to view the domain models for the Shiro, Nimble (uses Shiro), and/or Spring Security plugins. They create a concrete User domain and a concrete Role domain. Shiro in particular creates a UserRole domain for the many-to-many mapping.

Then on your Role domain, you can add whatever properties you want. If necessary, you can create a separate domain to allow for arbitrary properties like so:

class Role {
    //some properties
    static hasMany = [roleProperties:RoleProperty, ...]
}

class RoleProperty {
    String name
    String value
    static belongsTo = [role:Role]
}

I don't think you'll get what you're looking for in your current domain mapping though.

Matt Lachman
Great call on the Shiro, Nimble, and Spring Security (I assume this is Acegi?) plug-ins. I'm looking for a comparison between them but all I'm finding is old postings to mailing lists from 2K7, which probably have limited applicability to modern Grails. Do you happen to know of any resources that offer side-by-side comparisons?
Visionary Software Solutions
No, I've never found an extensive comparison on them all. I've used Shiro before (as JSecurity) so when Nimble was announced earlier this year it seemed like a natural move (built on Shiro with a much greater feature set). I've only ever seen posts about Spring Security (aka Acegi) but never read more about it.
Matt Lachman
A: 

We were testing out the grails inheritance heirarchy the other day at work to look at polymorphism. We found the following scenarios:

Abstract Superclass - Subclasses inherit the behavior of the parent but the parent cannot be used to reference a subclass you want to store in the database.

Superclass with tablePerHeirarchy false - Subclasses store the parent's fields in the parent's table, polymorphism works as expected.

Empty Superclass with tablePerHeirarchy false - Subclasses store all of their own data in their table, polymorphism works as expected.

So in your case, if you made were to remove the abstract keyword from the user class everything would work as expected. The only downside is all of the User fields are stored in the User table leaving the RegularEmployee table with only the id and version columns and the Manager table having only a reference to a Workgroup row.

Blacktiger
Interesting, but removing the abstract keyword would also allow for the instantiation of User objects. This means that there's a way to create a User that is not explicitly forced into one of the roles, which violates my business logic.
Visionary Software Solutions
That's a good point. I suppose you could get around that by creating an explicit constructor that throws an exception, but even then what we really want is an abstract class. For anyone else who reads this, be sure to vote for the issue on JIRA. http://jira.codehaus.org/browse/GRAILS-5356
Blacktiger