Assigning ID for domain objects in Grails via constructor

Update for Grails 2.2+

As of Grails 2.2-RC1 it is possible to simply add a bindable:true to the constraints section of the domain class to allow assignment in the constructor / findOrCreateWhere:

1
2
3
4
5
6
class MyDomain {
static constraints = {
// allow binding of "id" attribute (e.g. in constructor or url parameters)
id bindable: true
}
}

The Problem

I’m currently writing a Grails application with many domain objects that use a id generator of assigned:

1
2
3
4
5
6
7
class MyDomain {
String name

static mapping = {
id generator: 'assigned'
}
}

For domain objects of this kind, the id property must be set manually before any save() is possible. Unfortunately Grails doesn’t allow to set the id property via a map to the constructor, although this works for any other property:

1
2
3
4
5
// doesn't work for the "id" property!
domain = new MyDomain(id: 123, name: "Test")

// doesn't work either (for the "id" property)
domain = MyDomain.findOrCreateWhere(id: 123, name: "Test")

A manual assignment of the id outside of the constructor actually works:

1
domain.id = 123

I don’t know why this is the case, however there are bug reports that seem to be caused by this: GRAILS-1984 and GRAILS-8422. As a result any scaffold’ed Controller won’t be able to create new domain objects, as it uses the constructor internally to assign all fields. As this doesn’t work for the id, the object is not savable and creation fails.

A (temporary) solution

Until the described issues are fixed, it is possible to override the constructor of all domain classes to accept the id property. I don’t know the internals of Grails and this might introduce some security holes to your application. However, I’m not aware of any AND this behavior is really annoying, so I’m willing to take the risk (“kids, don’t try this at home!”).

By adding the following code to your BootStrap class, all domain classes are modified with a “fixed” constructor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class BootStrap {

def grailsApplication

def init = { servletContext ->

grailsApplication.domainClasses.each { clazz ->
def oldConstructor = clazz.metaClass.retrieveConstructor(Map)
clazz.metaClass.constructor = { Map data ->
def instance = oldConstructor.newInstance(data)

def idName = clazz.identifier.name
if (data.containsKey(idName)) {
def unparsedValue = data."$idName"
def value
if (unparsedValue == null || unparsedValue == "")
value = null
else
value = clazz.identifier.type.valueOf(unparsedValue)

instance."$idName" = value
}
return instance
}

}
}
}

It overrides the Map constructor of each domain class and calls the original one first. After this, the identity property is set if specified.

After applying this change, the scaffolded Controllers are working and findOrSaveWhere() / findOrCreateWhere() are functional, too.

Building a Groovy project with Maven

Intro - Why I chose Maven

Recently I wrote a small program in Groovy. As many of my Groovy projects it all started with a single script. After some coding, I ended up with a script containing 3-4 classes with more to come. So I decided to go for a “real project”: Add a build script and separate every class in its own file.

But… what technology should I use to build this? I knew Maven and Ant and just heard of Gant and Gradle. As I have written some Ant scripts before and have seen different ones from other developers, I quickly decided to skip Ant as an option: The build scripts are way to cluttered and long for “modern times” in my opinion. Furthermore I do not want to spent as much time for writing my build script as I need to write the actual software.

Unfortunately I had not time to research for Gradle or Gant. I know both tools are basically Groovy driven build tools, which makes them a quite natural choice for building a Groovy project. However, my company uses Maven and Ant for nearly all of its builds. So using another build tool would add a technology, developers in my company (including me) need to learn first. So here we are… Maven it should be. (I hope I will be able to use Gradle some time in the future - at least in my hobby projects.)

Let’s get started - building the environment

Project structure

I started with the typical structure of Maven projects:

Project structure

All Groovy files are in src/main/groovy and all tests are in src/test/groovy. Note: If you have some Java code, just put it in the src/main/java directory and Maven will take care of it.

The POM file

step 1: A basic build file

I started with a basic POM that actually builds only java projects:

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">


<modelVersion>4.0.0</modelVersion>
<groupId>de.redcube.example</groupId>
<artifactId>groovybuild</artifactId>
<version>1.0-SNAPSHOT</version>

</project>

Step 2: Adding the GMaven plugin

To actually compile the Groovy code, I found the really good plugin GMaven. However the documentation of this plugin is quite scattered over the internet and most examples haven’t worked for me (especially those at the GMaven wiki). Thus the existence of this article. ;)

The GMaven plugin needs to be added to the <build> section of the pom file. I decided to use the currently most recent version 1.4 of the plugin (and runtime) and Groovy 1.8.6. GMaven uses a quite clever method to decide how to build the code: different Groovy flavors are selected by choosing another runtime package as dependency. If you want to compile e.g. Groovy 1.7.x code, just use gmaven-runtime-1.7 instead. Note: There are only packages for each minor version number of Groovy - runtime 1.8 will actually compile any Groovy 1.8.x code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<build>
<plugins>
<plugin>
<groupId>org.codehaus.gmaven</groupId>
<artifactId>gmaven-plugin</artifactId>
<version>1.4</version>
<dependencies>
<dependency>
<groupId>org.codehaus.gmaven.runtime</groupId>
<artifactId>gmaven-runtime-1.8</artifactId>
<version>1.4</version>
</dependency>
</dependencies>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

Step 3: Adding a dependency to Groovy

The runtime of GMaven will come with a preselected Groovy package. However, I wanted to explicitly select Groovy 1.8.6 for my code. So I added a dependency in the dependencies section of my POM file.

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>1.8.6</version>
</dependency>
</dependencies>

Step 4: Running the build

After those tweaks the build was runnable by calling

1
mvn clean package

from a command prompt. Maven will run the build and all existing tests after this invocation.

Step 5: The final POM file

After all that I ended up with this (quite simple) build file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>de.redcube.example</groupId>
<artifactId>groovybuild</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>1.8.6</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.codehaus.gmaven</groupId>
<artifactId>gmaven-plugin</artifactId>
<version>1.4</version>
<dependencies>
<dependency>
<groupId>org.codehaus.gmaven.runtime</groupId>
<artifactId>gmaven-runtime-1.8</artifactId>
<version>1.4</version>
</dependency>
</dependencies>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>