Making Composition in Grails GORM Work

I felt that composition in GORM is a subject that required more information than what I found to be currently available. It is a very handy feature that can allow for a lot of flexibility and better code organization. Complex data types can be created and potentially shared by several domain classes across the project. 

After some research on the Web, I picked up some useful bits of info. There were no complete examples available though, so I decided to go ahead and create my own. It took some experimentation on my part, but in the end, I made composition behave nicely in GORM. I created a GitHub repo with a very simple Grails project to showcase the complete working example. My goals for this sample project were as following:
  • Organizing the project by placing the embedded class(es) in their own file(s). Doing so will make these class(es) available to all domain classes if the need arises.
  • Fixing the scaffolded views to work with the embedded classes and their constraints.
The project consists of the following classes:
  • User (domain) class
  • UserController class
  • Address model class
The following technologies were used for this sample project:
  • Grails 2.3.5
  • Groovy & Grails Toolsuite 3.4.0
The project can be found on gorm-composition-example GitHub repo.

All external sources will be cited at the bottom of this post.

Placing the embedded Address class in its own file.

According to Grails docs, if the two classes (User and Address) are defined in two different files inside domain directory, two database tables will be generated instead of one. One for each domain class. Following the docs' recommendation, we would need to place the Address class in the same file as the User domain class. Right under it to be exact. Yet, I feel that each class would be better served inside its own file. I like things neat and organized and the idea that one, or possibly several classes would be shoved in the same file, didn't seem ideal to me. Luckily, Grails allows for custom Groovy (and Java) classes to be defined, separately from the domain/controller structure. It even provides a directory specifically for this purpose: src/groovy. The example Address class was created inside gorm.composition.example.model package that you can see in a gist below. I only added 3 states to keep things simple for this example.

Things to note:
  • Constraints for Address' properties are defined inside the Address class.
  • toString method is used to properly format the display of the Address' data.
  • Changing Address class while app is running will more than likely cause an error. Re-compiling the project after editing Address class is needed.
After the Address class is created, it needs to be added to User domain class. Please see the gist below for the example.

Things to note:
  • Import the Address class via this statement gorm.composition.example.model.Address.
  • Declare the instance of Address class using Address homeAddress.
  • Specify that the homeAddress property will be embedded inside the current table with static embedded = ['homeAddress'].
And so everything should be fine at this point, but then running the app and navigating to user creation page, the following error appears on screen "Cannot get property 'constraints' on null object." The image below showcases this error.



Cannot get property 'constraints' on null object

Fixing the scaffolded views.

So the problem here is that the scaffolded views, or more exactly views/user/_form.gsp  doesn't deal well with embedded objects. The view seems to recognize that embedded object property is used, but it fails to recognize that we are dealing with a User domain class' property. Good news is that _form.gsp is shared by all CRUD views and the only file that requires modifications applied. It also serves as a good starting point and the changes we are going to make don't really warrant writing the entire form from scratch.

The first thing that needs to be done is to generate the views, so that we can apply the necessary changes to _form.gsp. The two gists below show the _form.gsp before and after the applied changes.

Originally generated _form.gsp (before our changes).




Updated _form.gsp (after our changes)


Things to note:
  • All references to addressInstance are replaced with userInstance?.homeAddress (i.e., userInstance?.homeAddress?.state). This change does not apply to constraints though, which we'll touch on shortly.
  • The field names have been updated to use homeAddress.property (i.e., homeAddress.zipCode, etc...). This will properly map the _form.gsp fields' homeAddress data to User domain class.
  • Constraints are static and as such can be referenced using class name. This is how it should be done in our case, since homeAddress instance is initially null. The convention is ClassName.constraints.constrainedProperty.constrainRule. So for example to reference zipCode constraint, we can use Address.constraints.zipCode.matches).
  • Using Address class' name without prefixing it with package will throw an error unless we import the Address class at the top of _form.gsp. Hence the <%@ page import="gorm.composition.example.model.Address" %>.

Sources

http://grailssoapbox.blogspot.com/2010/02/accessing-grails-domain-constraints-at.html

http://grails.1312388.n4.nabble.com/Problem-creating-a-new-object-which-includes-an-embedded-object-td4637111.html

Comments