Following the GORM docs I tried to use the following domain class with Grails 2.2.1:
package grailscompositiontest
class ScafPerson {
String name
ScafAddress homeAddress
ScafAddress workAddress
static constraints = {
name(nullable: false, blank: false)
}
static embedded = ['homeAddress', 'workAddress']
}
class ScafAddress {
String number
String code
}
The controller just uses scaffolding:
package grailscompositiontest
class ScafPersonController {
static scaffold = true
}
Unfortunately this does not work, it triggers a server error as soon as I browse to the "create" view:
URI: /GrailsCompositionTest/scafPerson/create
Class: java.lang.NullPointerException
Message: Cannot get property 'id' on null object
Any idea what I am doing wrong?
I found that the grails documentation says to put embedded class definitions either the same source file as the domain class or put it into src/groovy. I put Address.groovy in src/groovy and generated the UI for my Family class, which has an embedded object. The generated create page now displays correctly with the address fields are included in the Family page. However, I still had to modify the name and the value fields of each form field in the generated _form.gsp.
For example, the following generated code...
<g:textField name="line1" required="" value="${addressInstance?.line1}"/>
was changed to...
<g:textField name="address.line1" required="" value="${familyRegistrationInstance.address?.line1}"/>
Where the primary domain object is a FamilyRegistration domain class (referred to in the generated _form.jsp as familyRegistrationInstance), which has an embedded Address object. I am using grails 2.3.11.
The save operation now works with the fields of the embedded object populated correctly. In my case, this means that FamilyRegistration.address is populated correctly.
Here's the related info from the Grails Reference document...
"The embedded component class is typically declared in the same source file as the owning class or in its own file under src/groovy. You can put the component class under grails-app/domain, but if you do so Grails will automatically create a dedicated table for it. Putting the class under src/groovy is usually the best option because you can then share the component across multiple domain classes."
http://grails.org/doc/2.2.4/ref/Domain%20Classes/embedded.html
I ran into this same problem the other day. I believe there may be a bug in the templates used by the scaffolding functionality. You can either update the template or if you don't want to muck with the templates, run generate-all as Benoit mentioned then fix the generated view.
To update template:
- grails> install-templates
- open src/templates/scaffolding/renderEditor.template
- find the following line:
sb << ' value="${' << domainInstance << '.' << property.name << '}"'
and change to
sb << ' value="${' << domainInstance << '?.' << property.name << '}"'
To fix the generated view:
- grails> generate-all grailscompositiontest.ScafPerson
- open views/scafPerson/_form.gsp
- look for
<g:field name="id" type="number" value="${scafAddressInstance.id}" required=""/>
<g:field name="version" type="number" value="${scafAddressInstance.id}" required=""/>
and change to
<g:field name="id" type="number" value="${scafAddressInstance?.id}" required=""/>
<g:field name="version" type="number" value="${scafAddressInstance?.id}" required=""/>
Note you'll do it twice since you have homeAddress/workAddress