one-to-many relationships in Grails forms
Here’s a scenario we see fairly often in our Grails applications.
- Parent object has a collection of Child objects
- We want the Parent’s create and edit GSPs to allow us to add/remove/update associated Child objects
- The controller should correctly persist changes to the collection of Child objects, including maintaining Child object ids so any other objects referencing them don’t get screwed up
I found a really nice solution that avoids adding a lot of code to the controller to sift out added/changed/deleted collection members. The original page seems to have disappeared, so here are copies from archive.org (easier to read) and Google cache (PDF).
I was disappointed that the original page is gone, and I found some small errors in the sample code, so I thought it would be nice to document here.
Here’s a sample project I created to go through this. Source code: one-many.tar.gz
The original example used Quest objects that can hold many Task objects. I’ll follow the Grails docs and use Author objects that can hold many Book objects.
First, create the Author class.
import org.apache.commons.collections.list.LazyList;
import org.apache.commons.collections.FactoryUtils;
class Author {
static constraints = {
}
String name
List books = new ArrayList()
static hasMany = [ books:Book ]
static mapping = {
books cascade:"all,delete-orphan"
}
def getExpandableBookList() {
return LazyList.decorate(books,FactoryUtils.instantiateFactory(Book.class))
}
}
(Here’s a minor correction I had to make to the original document’s code. They declared getExpandableBookList as returning a List, but that gave unknown property errors. Using a plain def fixed that.)
This adds a bunch of useful behaviour right away. The mapping block declares that books will be deleted when they’re removed from the Author.books collection, so we don’t need to clean up anything manually. By initializing books to an empty ArrayList when an Author object is created, and by using the getExpandableBookList() method, we can easily add and remove Book objects to the Author.books collection.
Next, the Book class is pretty simple.
class Book {
static constraints = {
}
String title
boolean _deleted
static transients = [ '_deleted' ]
static belongsTo = [ author:Author ]
def String toString() {
return title
}
}
Nothing too fancy here, but pay attention to the _deleted property. That’s what we’ll be using to filter out Book objects that need to be removed from the Author.book collection on updates.
For the views, I like to combine the guts of the create and edit GSPs into a template that they can both render.
<div class="dialog">
<table>
<tbody>
<tr class="prop">
<td valign="top" class="name"><label for="name">Name:</label></td>
<td valign="top" class="value ${hasErrors(bean:authorInstance,field:'name','errors')}">
<input type="text" id="name" name="name" value="${fieldValue(bean:authorInstance,field:'name')}"/>
</td>
</tr>
<tr class="prop">
<td valign="top" class="name"><label for="books">Books:</label></td>
<td valign="top" class="value ${hasErrors(bean:authorInstance,field:'books','errors')}">
<g:render template="books" model="['authorInstance':authorInstance]" />
</td>
</tr>
</tbody>
</table>
</div>
That uses _books.gsp to render the editable list of books.
<script type="text/javascript">
var childCount = ${authorInstance?.books.size()} + 0;
function addChild() {
var htmlId = "book" + childCount;
var deleteIcon = "${resource(dir:'images/skin', file:'database_delete.png')}";
var templateHtml = "<div id='" + htmlId + "' name='" + htmlId + "'>\n";
templateHtml += "<input type='text' id='expandableBookList[" + childCount + "].title' name='expandableBookList[" + childCount + "].title' />\n";
templateHtml += "<span onClick='$(\"#" + htmlId + "\").remove();'><img src='" + deleteIcon + "' /></span>\n";
templateHtml += "</div>\n";
$("#childList").append(templateHtml);
childCount++;
}
</script>
<div id="childList">
<g:each var="book" in="${authorInstance.books}" status="i">
<g:render template='book' model="['book':book,'i':i]"/>
</g:each>
</div>
<input type="button" value="Add Book" onclick="addChild();" />
And that uses _book.gsp to render the individual records. It’s a bit overkill to call out to another template for only a few lines of HTML, but that’s how the original example did it and I’ll do the same for consistency.
<div id="book${i}">
<g:hiddenField name='expandableBookList[${i}].id' value='${book.id}'/>
<g:textField name='expandableBookList[${i}].title' value='${book.title}'/>
<input type="hidden" name='expandableBookList[${i}]._deleted' id='expandableBookList[${i}]._deleted' value='false'/>
<span onClick="$('#expandableBookList\\[${i}\\]\\._deleted').val('true'); $('#book${i}').hide()"><img src="${resource(dir:'images/skin', file:'database_delete.png')}" /></span>
</div>
Here’s where I changed a bit more from the original example. I used jQuery because the selectors make things easy. Basically we render the books from the already-persisted author object, and keep track (using the _deleted field) of any that the user wants to remove. We also keep track of new objects to add.
One of the reasons I really liked this technique was how little impact there is on the controller. We just need to add this to the update method in AuthorController.
def update = {
def authorInstance = Author.get( params.id )
if(authorInstance) {
if(params.version) {
// ... version locking stuff
}
authorInstance.properties = params
def _toBeDeleted = authorInstance.books.findAll {it._deleted}
if (_toBeDeleted) {
authorInstance.books.removeAll(_toBeDeleted)
}
// ... etc.
The original example added similar code to the save method, but I don’t think it’s required for new objects (since they don’t have any already-persisted books to delete, only new books to create) so I only put it in the update method. I also changed it from find{} to findAll{} to guarantee that we get a list, and checked that we have objects to remove before calling the removeAll().
And it works great! Let’s look at some screenshots of the application in action.
First, we can create a new author and add some books right here instead of creating them separately and then matching them up.

Hit “Create” and it creates the Author and Book objects.

Edit the author we just created and see how we get a form that looks the same.

However, it’s worth noting that the books displayed here are the already-persisted ones, so the form is keeping track of their ids and whether we should keep them or delete them on update. Let’s delete the first one and add two more new books.

Now when we hit “Update” the controller has to be smart enough to remove that first book from the Author.books collection, then create two new Book objects and add them to the collection. And naturally, it is.

In addition to creating and destroying Book objects, we can update them. For example, let’s change the title of that first book to be the long version.

No problem!

So that’s one-to-many relationships in Grails forms. I hope it’s useful.

October 5th, 2009 at 22:07:57
Your posting is great! Exactly what i’m looking for since there is not complete example like this in grails manual.
Thanks!
October 8th, 2009 at 11:38:30
Thanks. I also wanted the Grails manual to cover this. It seems like it would come up pretty often.
October 17th, 2009 at 15:36:33
I was glad to find this site, there isn’t many about this subject online yet!
I also entered a small fix for your demo project.
The _toBeDeleted list needs to be checked for null’s. Null’s appear in AuthorInstance.books when you create more books and delete one(not the last) before pressing the update or save button. For this reason you need the _toBeDeleted check in the save action of the controller also.
Improve the check with Elvis checking for the null iterator in the findAll of the update action and the save action.
def _toBeDeleted = authorInstance.books.findAll {it==null?:it._deleted}
October 29th, 2009 at 21:52:55
Great start!
How about adding a unique constraint on Book.title?
November 2nd, 2009 at 13:19:49
Thanks, Eddy and Brad! Those are both good suggestions, although I could see cases where multiple books could have the same title. Grails automatically adds an “id” property to each domain class to guarantee a unique identifier, so you don’t have to worry about conflicts in the title. I’ll leave the project files as-is, but note here that I think both of those changes are worth considering for anyone else who reads this.
Also, you could probably simplify the _toBeDeleted null checking to this.
def _toBeDeleted = authorInstance.books.findAll {it?._deleted}
February 8th, 2010 at 03:31:34
Just a little note: I think “all,delete-orphan” must be “all-delete-orphan”. The grails doc was incorrect until 1.2. I think.
http://grails.org/doc/latest/guide/5.%20Object%20Relational%20Mapping%20%28GORM%29.html#5.5.2.9%20Custom%20Cascade%20Behaviour
May 3rd, 2010 at 21:57:13
Thanks for the article. I have got most of it working except for “adding a book”. My javascript error console gives out message that “$” is undefined in create functionality when I am trying to add the books to the childList. I am using Grails1.2 and jquery1.3.2. Appreciate any pointers.
May 3rd, 2010 at 22:51:36
Nevermind …. the problem was the syntax was changed for 1.2 for adding jquery plugin.
Instead of (in main.gsp under layouts) –
It should be -
Thanks.
May 25th, 2010 at 11:05:54
will this “pattern” work if the Book has many “Pages” (dummy example)?
June 12th, 2010 at 19:58:50
LA, I’m not sure I follow your example, but at first glance it sounds like it should be fine. You’d just do the same thing for the book class so it can edit the pages inline.
June 15th, 2010 at 10:30:57
Tim, thanks for the great post. Really helpful – exactly what I was looking for.
A couple of issues I found when I tried the example (might be because I used Grails 1.3.1):
(1) After saving an author with books, I couldn’t delete books from the list (they’d remain in the database). After debugging I found that the “authorInstance.properties = params” was not working correctly (the “_deleted” request parameter would be set to true, but the corresponding “authorInstance._deleted” property was always set to false). I changed “_deleted” to “deleted” (i.e. removed the underscore in the domain class & corresponding GSPs) then the code worked fine. I’m not sure why the underscore was causing an issue but it could be something weird relating to Grails 1.3.1.
(2) After I got (1) working, I ran into another issue. If I created, say, 3 NEW books (BookA, BookB and BookC in that order), then immediately remove BookB BEFORE hitting “save/update”, Grails actually persists BookA with an index field of 0 and BookC with an index field of 2 (instead of shifting BookC up to position 1 which would make more sense since B never really existed). I found the index “gap” was then causing issues when going to re-edit that particular author since Grails starts trying to retrieve book[2] from a collection of just two items.
There are a couple of ways round the 2nd issue – what I did was treat new books the same as existing ones (i.e. instead of calling “.remove()” in the GSP like you’d done, I kept a hidden “deleted” field for both existing AND new books, then called “hide()” in both cases when a book needed to be marked for deletion). Only thing is this means you DO have to ensure you include the same “authorInstance.books.removeAll(_toBeDeleted)” logic in your controller “save()” method as well (otherwise you can potentially end up with index “gaps” again).
June 15th, 2010 at 10:34:40
Sorry, just to clarify, I meant “authorInstance.books[i]._deleted” in point (1)
June 24th, 2010 at 22:00:34
Worked in Grails 1.3.2 on first try. Awesome! Thanks so much.
July 24th, 2010 at 03:25:14
Hello! I’m using your solution to make the a little complicated system, but to this later. When I was working on my version of yours _book.gsp the Netbeans shows me a little syntax error on line ” var deleteIcon = ${resource(dir:’images/skin’, file:’database_delete.png’)};” – it says:
“illegal string body character after dollar sign”.
I was thinking it was some artifact from previous changes to files, but it’s persistent and won’t leave after reopenings the file. I use Netbeans 6.9 and Grails 1.3.3.
I have also next question – I need to make adding two, connected object with as one List element added to “parent” object. I have main class ( Author counterpart ) Wniosek that has many Osobowosc ( Identity ) and TypOsobowosci ( Type of Identity ), both as sperate classes, but both needed, as one “record” in List. How to make the similar solution to yours for two models object as one “record”? Make two rendering files I guess, but what inside the class itself?
End result should be like this:
Create Wniosek:
Typ:
Osobowosci: –
–
–
etc.
August 12th, 2010 at 11:41:02
Nice tutorial. Very helpful. Thanks
August 19th, 2010 at 02:15:43
Tim, thanks a lot for this great tutorial. You just saved my life, can’t wait to go back home and try this. I’ve been looking all over for that sort of explanation. I really think this should be included somewhere in the grails docs.
August 20th, 2010 at 16:43:56
[...] to find a good way to implement one-to-many dynamic forms in Grails, I have finally come across this post which does a very good job at explaining the [...]