Tech Note: Grails Custom One-To-Many Scaffolding

{ Dan Stieglitz // Groovy/Grails // April 10, 2008 }

One small issue we have with Grails is that out-of-the-box it doesn't scaffold a UI to deal with one-to-many (OTM) relationships for existing objects. You can add a new instance of an entity to a container object when editing it, but if you have pre-existing objects you can't add them to the container. When you're in the early phases of an application and are tweaking your domain model this can be a big help. Here I'm going to present a quick way of modifying the scaffolding templates to allow you to generate a UI that can allow this behavior. There's a lot of value in knowing how to modify the Grails default behavior and this short tutorial should give you a little introduction into how Grails works under the covers.

Create the application

grails create-app OneToManyScaffolding

Create the domain model

In this example we create a Container and an Component object, where Container contains many Components.
grails create-domain-class Container
grails create-domain-class Component
Next, flesh out the domain objects:
class Component {
	
	String name
	
        String toString() {
		return name
	}
}
We implement the toString() method so that when the scaffold displays the objects in the UI, we'll get a value that has some meaning. We also build our Container domain class having a hasMany reference to our newly-created component class.
class Container {
	
        String name
	static hasMany = [ components : Component ]

        String toString() {
                return name
        }
}
Let's run the app to see what we get out-of-the-box. First we'll create a container using the UI (ContainerController -> New Container). When we attempt to edit our new container we get an edit screen with an "Add Component" link; however, clicking this link takes us to the "Create Component" screen. If we'd had some pre-created Components we wanted to add to this container the out-of-the-box UI would not allows us to do this. We can implement this behavior for the default UI by modifying the templates that generate these pages.

Install the scaffolding templates

First we'll need to install the templates themselves, and Grails makes this quite easy. You'll only need to issue:
grails install-templates
When this operation completes there will be a src/templates/scaffolding directory added to the application. This directory contains all of the Groovy templates that are used to generate the scaffolded pages when you issue a "generate-all ..." command. There is also a GSP that generates the "editor" for domain objects called "renderEditor.template" that contains logic we'll modify to get this working.

Modify the scaffolding templates

Open the renderEditor.template file and find the "renderOneToMany" method. The last line before the method returns generates the link we see that allows us to add (currently only new) objects to our container object. Add the following line below:
pw.println "
Associate ${property.referencedDomainClass.shortName}";
This will create an "Associate ..." link below the "Add ..." link on the Container edit page. Clicking on the link will call the "list" action of the component class and pass in 3 paramters: component_id, source, and callback. To complete the flow for associating the objects we'll need to create a new method, "choose," in the template that generates the controllers. This template is in the same scaffolding directory and is called Controller.groovy. Add the method definition to the end of the class:
    def choose = {
        redirect(controller:params.source,action:params.callback,params:params)
    }
We'll need to regenerate the controllers now since we've changed the scaffolding templates, so issue a "grails generate-all Container" and "grails generate-all Component."

Now we have to crack open the list template and modify it to produce links that know when we're trying to list instances and when we're trying to select an association. Each instance link is generated in an each closure near the bottom on the template. We'll add some code to generate a different link when a "callback" parameter is passed in, indicating we've come from an "Associate ..." link and not an "Add ..." link.

        
            \${${propertyName}.${p.name}?.encodeAsHTML()}
        
        
            \${${propertyName}.${p.name}?.encodeAsHTML()}
        
We've modified the behavior to call the "choose" method we implemented above on the Container class, which will in turn call the method specified in the "callback" parameter we've been passing around in the URLs. The callback method will be called on the "source" object also passed around. In this specific case, Grails expects to see an addComponent method in the Container controller, so let's go ahead and add that method:
    def addComponent = {
    	def container = Container.get(params["container_id"])
    	def component = Component.get(params["id"])
    	container.addToComponents(component)
    	render(view:'edit',model:[container:container])
    }
That completes the code for the flow, so now you should be able to add components to your model, and then associate them with a controller in your view. Be careful not to overwrite your custom controller methods when you regenerate controllers, or better yet, tweak this code so those methods can live in a service.