MrPlow
MrPlow

Reputation: 1467

Grails 2.5.0 Second level Abstract domain class creates it's own table even though it shouldn't

If I create an abstract domain class such as this:

abstract class DomainBase {

    LocalDateTime created = LocalDateTime.now()
    LocalDateTime updated

    static constraints = {
        created nullable: false
        updated nullable: true
    }

    static mapping = {
        tablePerConcreteClass true
        created column: 'CREATED'
        updated column: 'UPDATED'
    }

    def beforeUpdate() {
        this.updated = LocalDateTime.now()
    }
}

Then i can extend other domain classes with this and they will inherit everything (the properties, constraints, mappings and interceptors), and the generated database contains no concrete DOMAIN_BASE table. With that everything works as expected.

However, if I create another abstract class which extends DomainBase, for example:

abstract class EntityBase extends DomainBase {
    User createdBy
    Boolean active = true

    static constraints = {
        createdBy nullable: false
        active nullable: true
    }

    static mapping = {
        tablePerConcreteClass true
        createdBy column: 'CREATED_BY_ID'
        active column: 'ACTIVE'
    }
}

Then the generated database will have a concrete ENTITY_BASE table. And that is the problem.

Proposed solution from what I could gather was to have the base classes be ordinary POGO's and not domain objects, however then the mapping is not inherited and I'm much too lazy to copy paste the mapping into every single domain class I create.

Also if I try to make these domain objects to be traits I can't even run the app due to it breaking with an NPE during compilation.

Is there a simple and elegant way to solve this?

Upvotes: 2

Views: 467

Answers (2)

MrPlow
MrPlow

Reputation: 1467

My current solution with which I'm somewhat satisfied with is an adaptation of This SO answer only wrapped into an "Utility" closure and looks like this:

/**
 * Domain related utilities
 */
class DomainUtil {
    private DomainUtil() {}

    /**
     * Utility for inheriting mapping definitions from non-domain classes (classes defined in src/groovy)<br/>
     * Adapted from <a href="https://stackoverflow.com/a/18339655/1182835">https://stackoverflow.com/a/18339655/1182835</a>
     * @param source - source class from which to inherit the mappings
     * @param destination - closure to which the mappings will be added
     */
    static def inheritDomainMappingFrom = { source, destination ->
        def copyMapping = source.mapping.clone()
        copyMapping.delegate = destination
        copyMapping.call()
    }
}

And It is used like this:

Given some abstract class Foo which has a mapping closure and is stored inside src/groovy so it's not considered a domain class:

abstract class Foo {
    LocalDate prop1
    Boolean prop2

    static mapping = {
        prop1 column: "PROP1_DATE", type: PersistentLocalDate
        prop2 column: "IS_PROP2"
    }
}

And a domain class Bar which inherits Foo:

class Bar extends Foo {
    Integer prop3
    String prop4
    String prop5

    static mapping = {
        DomainUtil.inheritDomainMappingFrom(Foo, delegate)
        prop3 column: 'PROP3'
        prop4 column: 'PROP4'
        prop5 column: 'PROP5'
    }
}

Using DomainUtil.inheritDomainMappingFrom(Foo, delegate) my Bar domain class will inherit all the mapping definitions from Foo abstract base class. This, for me, is an adequate compromise since it's easier to write and for someone joining or maintaining the project to understand.

Extending Scaffolding Templates as suggested by Yannic-AT is also a possible solution however it contains a few drawbacks which put me off:

  1. It's not refactor friendly - if certain properties/constraints/mappings/interceptors need to be added after substantial development has already been done, then all the changes need to be retroactively added to all existing classes
  2. Doesn't follow the DRY principle - by modifying the template, all we're doing is instructing the grails domain builder to copy and paste our newly added code into every new domain class we create. So if we have a lot of domain classes we will have a lot of duplicate code which will, in turn, make refactoring a nightmare (see 1.)
  3. It only works if we wish to add certain properties to ALL our domain classes, otherwise we have to delete the generated code which, in my opinion, defeats the purpose of the template

However it is quite useful for stuff adding the @ToString annotation to all my domain classes.

Upvotes: 0

YAT
YAT

Reputation: 466

As far as i know you can't inherit the mapping in Grails 2.4.x, it think this in 2.5.0 the same...

As a workaround you can use and change the Scaffolding Template for the domain classes.

Upvotes: 1

Related Questions