Jul 14, 2008

Grails Domain Diagram Including Subclasses

This proved to be a little more tricky than I'd thought. I first needed to understand that DefaultGrailsDomainClass is a wrapper for your actual domain classes. This class is very good about telling you about subclasses, but not about superclasses. That fact, coupled with associations of the parent class being echoed in the associations of the subclasses confused things a bit. Knowing the superclass you need to go back to the DefaultGrailsApplication to look it up.

So, here it is. A longer DotController that outputs an domain digram for your Grails domain objects:

class DotController {

def domain = {
render( generateDot() )
}

def domainDiagram = {
// NOTE dot must be on the path
Process p = Runtime.getRuntime().exec("dot -Tjpg")
p.outputStream.withStream { stream ->
stream << generateDot()
}
p.waitFor()

def imageBuffer = new ByteArrayOutputStream()
imageBuffer << p.inputStream
byte[] image = imageBuffer.toByteArray()

response.contentLength = image.length
response.contentType = 'image/jpeg'
response.outputStream << image
}

private def generateDot = {
def dotBuffer = new StringWriter()
def out = new PrintWriter(dotBuffer)

out.println """digraph { size="6,6"; node [shape=rectangle];"""
grailsApplication.domainClasses.each { domainClass ->
out.println """"$domainClass.name";"""

getAssociations(domainClass).each {
def property = it.key
def target = it.value.name
def isOneToMany = domainClass.isOneToMany(property)
def type = isOneToMany ? "crow" : "tee"
out.println """"$domainClass.name" -> "$target" [arrowhead = $type];"""
}

domainClass.subClasses.each { subClass ->
out.println """"$subClass.name" -> "$domainClass.name" [arrowhead = onormal];"""
}
}
out.println "}"

return dotBuffer.toString()
}

private getAssociations(domainClass) {
// super class associations are duplicated in for sub classes so we strip them here
def associations = domainClass.associationMap
if (!domainClass.isRoot()) {
def superClass = grailsApplication.getDomainClass(domainClass.clazz.superclass.name)
associations = associations.findAll { !superClass.associationMap.containsKey(it.key) }
}

return associations
}
}

A key source in getting this all to work (which I neglected to reference in the last post) was the documentation on the dynamic grailsApplication methods.

4 comments:

Andres Almiray said...

Sadly your blog formatting is not showing the whole code :-(
Nice way to demo DotBuilder though.

Neil said...

All the code is present in the source. Copying the lines of code is an easy way to extract it.

Curious Attempt Bunny said...

Ok, I take the hint. I've made the font size smaller (in a way that works fine in FireFox 3 at least).

Is it too small now?

Andres Almiray said...

Yes, I think it is too small now :-[ styling is not that easy