Tuesday, 24 March 2009

Why Grails Is Good

Obviously I can't say that Grails is the best thing since sliced bread and then not present some evidence to support my claim, so here's a specific example.

In my application I have a Network domain class and some other domain classes that extend it - TwitterNetwork, FacebookNetwork, etc. I wanted to handle the CRUD functionality for these classes in a single controller, but keep the forms separate. Having auto-generated the controller + views and then deleted the stuff I didn't want, here's the structure I ended up with:
- grails-app
- domain
- Network.groovy
- FacebookNetwork.groovy
- TwitterNetwork.groovy
- controllers
- NetworkController.groovy
- views
- network
- list.gsp
- facebookNetwork
- create.gsp
- edit.gsp
- twitterNetwork
- create.gsp
- edit.gsp
Out of the box, the default list action just works:
def list = {
if (!params.max) params.max = 10
[ networkInstanceList: Network.list(params) ]
}

As do the delete, save and update actions.

All I have to do now is get the edit and create actions to use the correct view, depending on the network type. To do this I use the class name to determine which view to use. For example, here's my edit action:
def edit = {
def networkInstance = Network.get(params.id)
renderEditForm(networkInstance)
}

def renderEditForm(network) {
def map = [ networkInstance : network ]
def viewPath = getViewPath(network)
render(view:"/${viewPath}/edit", model:map)
}

// derive the view path from the given network instance
def getViewPath(network) {
def networkName = getName(network)
networkName[0].toLowerCase() + networkName[1..-1]
}

The great thing is, I know that will work because by convention Grails will put the views for TwitterNetwork in the twitterNetwork folder and the views for FacebookNetwork in the facebookNetwork folder. It then becomes a simple case of taking the class name and making the first character lower case.

I use the same principle for my create action:
static networks = [ "twitter" : TwitterNetwork,
"facebook" : FacebookNetwork ]

def create = {
def networkInstance = createNetwork(params.type)
networkInstance.properties = params
renderCreateForm(networkInstance, params.type)
}

// create a new network instance of the given type
def createNetwork(networkType) {
if (networkType in networks.keySet()) {
def clazz = networks[networkType]
return clazz.newInstance()
}
}

... etc

And that's it! Full CRUD functionality for a set of polymorphic domain classes in a few lines of code - most of which were auto-generated by the framework. :-)

No comments: