Creating One-To-Many Records In A Grails Service

Standard

A parent can have many children. This article explains how to write a service such that, if after adding a parent there is an error when adding a child, the entire transaction is rolled back. For example, add parent p1, successfully add child c1, then when adding child c2 an error occurs, both p1 and c1 should be rolled back.

Note that this requires Grails 1.2-M4.

Here are the domain objects:

1
2
3
4
5
6
7
8
9
10
class Parent {
 
static hasMany = [ children : Child ]
 
String  name
 
static constraints = {
name(blank:false,unique:true)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Child {
 
static belongsTo = Parent
 
String name
 
String pet
 
Parent parent
 
static constraints = {
name(blank:false,unique:true)
pet(blank:false)
}
}

Now if you do:

1
grails generate-all *

You’ll get separate forms that do work. But if we were talking about an invoice application, for example, you’d really want to have 1 data entry form. That’s what we do here:

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
45
46
47
48
49
50
51
52
53
54
55
56
 
<%@ page contentType="text/html;charset=UTF-8" %>
 
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="layout" content="main" />
    <title>Sample title</title>
  </head>
  <body>
    <h1>Add A Record</h1>
 
  <g:if test="${flash.message}">
    <div class="message">${flash.message}</div>
  </g:if>
  <g:if test="${flash.errors}">
    <div class="errors">
      <ul>
      <g:render template="renderflasherrors" collection="${flash.errors}"  as="list"/>
      </ul>
    </div>
  </g:if>
 
 
  <g:form action="add" name="doAdd">
    <table>
      <tr>
        <td>
          Parent Name
        </td>
        <td>
          Child Name
        </td>
        <td>
          Pet Name
        </td>
      </tr>
      <tr>
        <td>
      //<g:textField name="parentName" value="${flash.params?.parentName}" />
       <g:textField name="parentName" value="${params?.parentName}" />
      </td>
      <td>
      //<g:textField name="childName" value="${flash.params?.childName}" />
       <g:textField name="childName" value="${params?.childName}" />
      </td>
      <td>
      //<g:textField name="petName" value="${flash.params?.petName}" />
      <g:textField name="petName" value="${params?.petName}" />
      </td>
      </tr>
      <tr><td><g:submitButton name="update" value="Update" /></td></tr>
    </table>
  </g:form>
</body>
</html>

The tricky part in this was figuring out how to create both records in the controller and return any errors and the entered data back to client. More on that later, but for now just notice lines 13-16 where we render the errors. This is a little different than in the generated forms. There you can check if the object has errors and display them.

But in this example, all of the objects are created in a service, so the .gsp (and the controller) don’t know anything about the objects. So we end up passing the errors back in a collection flash.errors.

Now look at lines 37, 40 and 43 and compare those to similar lines in the generated forms. There you use a bean to get the value from. But here, we just redisplay the params which contains everything the user originally entered. Actually, the params are being stored in flash. For some reason, the form fields end up getting dropped from the params by the time we get back to action:show.

Here’s the controller:

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
class AddrecordController {
 
    def addRecordsService
 
    def index = {
        redirect action:"show", params:params
    }
 
    def add = {
        println "do add"
 
        try {
            addRecordsService.addAll(params)
        } catch (java.lang.RuntimeException re){
 
          def errorMessages = re.errors.allErrors.collect { g.message(error:it) }
            println  errorMessages
            flash.message = g.message(code:"addRecord.save.failed")
            flash.errors = errorMessages
           //flash.params = params
            //render action:"show",  params:params
            render view: "show", params:params
            return
        }
        redirect action:"show"
 
    }
 
    def show = {}
 
}

Make sure you render the view not the action and follow it with a return.

If there’s an error in the service a RunTimeError get’s thrown. As of Grails 1.2-M4, the exception thrown is a grails.validation.ValidationException, which has the list of errors, so at line 18 we pass each error to a message tag to get the readable messages [1].

Finally, here’s the service:

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
class AddRecordsService {
 
    static transactional = true
 
    def addAll(params) {
        println "add all"
        println params
        def Parent theParent =  addParent(params.parentName)
        def Child theChild   = addChild(params.childName,params.petName,theParent)
 
        println theParent
        println theChild
 
    }
 
    def addParent(pName) {
        println "add parent: ${pName}"
        def theParent = new Parent(name:pName)
        theParent.save(failOnError:true)
 
        return theParent
    }
 
    def addChild(cName,pName,Parent theParent) {
        println "add child: ${cName}"
        def theChild = new Child(name:cName,pet:pName,parent:theParent)
 
       theChild.save(failOnError:true)
 
        return theChild
    }
 
}

Grails 1.2-M4 added failOnError:true as an option to save(). So now the cause of an error is returned.

Thanks to everyone who answered my questions on this ([1],[2],[3],[4]).

[1]Re: How To Parse Runttime Exception Error (GRAILS-5146)

[2]How To Make Transactions Work In Grails

[3]How To Know The Cause Of A Validation Error

[4]Render Errors From A Service

Download transtest

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]