Most of the domain objects our company uses will have some common properties. These represent the user that created the object, the user that last updated the object, and the program they used to do it.
In the interest of DRYing out my domain classes, I want to find some way to add the same beforeInsert and beforeUpdate logic to all domain classes that have these columns without interfering with those that don't.
How I'd like to do it is using a Mixin with its own beforeInsert and beforeUpdate methods. I know you can use Mixins on domain classes.
package my.com
import my.com.DomainMixin
@Mixin(DomainMixin)
class MyClass {
String foo
String creator
String updater
static constraints = {
creator nullable:false
updater nullable:false
}
}
package my.com
class DomainMixin {
def beforeInsert() {
this.creator = 'foo'
this.updater = 'foo'
}
def beforeUpdate() {
this.updater = 'bar'
}
}
Unit tests would indicate that the beforeInsert method isn't actually getting fired when implemented this way.
Side note: I also know it's possible to add the methods in a BootStrap.groovy file using the metaClass, but my curiosity has gotten the better of me and I really want to see if the mixin works. Feel free to tell me that this is the better way to do it and I ought not muddle where man ought not.
FYI, use of groovy.lang.Mixin
is strongly discouraged (by the Groovy project leader, for one). If you have to use mixins you should use grails.util.Mixin
instead. One thing I don't like about your mixin approach is the implicit and unenforced assumption that the target of the mixin has creator
and updater
properties
Personally, I would probably just use plain-old inheritance for this, e.g.
abstract class Audit {
String creator
String updater
def beforeInsert() {
this.creator = 'foo'
this.updater = 'foo'
}
def beforeUpdate() {
this.updater = 'bar'
}
static constraints = {
creator nullable: false
updater nullable: false
}
}
any domain classes that need to be audited would simply extend Audit
. An alternative (and preferable) approach would be to use a trait rather than an abstract base class, but you'll need to be using a fairly recent Grails version in order to do this.