5
votes

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?

2
I'm wondering what it would mean for an abstract class to be persisted to the DB, since there has to be something to persist (i.e., an instance).Ben Dunlap
Well, I would venture that an abstract class is the preliminary schema outline, especially if it as at the root of an inheritance relationship. So if I have a User->RegularEmployee relationship, the table name should be User and a column should be added for "class", which is used to store the type a la the standard table-per-hierarchy model.Visionary Software Solutions

2 Answers

3
votes

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.

2
votes

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.