Monday, November 4, 2013

Grails Dynamic Scaffolding Gotcha

According to Section 10.1 of the Grails Reference documentation: "By default, all domain class properties are not nullable (i.e. they have an implicit nullable: false constraint)."

Having read that you might think that it would cause all domain fields to be required when using dynamic scaffolding.  Unfortunately this is not entirely true, as this quick experiment demonstrates.

Step 1: Create a grails application

grails create-app experiment

Step 2: Create a domain

grails create-domain-class experiment.FooBar

Step 3: Update the FooBar domain so that it looks like the following

package experiment;

class FooBar {
   String stringObject

   Character characterObject
   char primitiveCharacter
   
   Boolean booleanObject
   boolean primitiveBoolean
   
   // *** Number types
   Byte byteObject
   byte primitiveByte

   Short shortObject
   short primitiveShort

   Integer integerObject
   int primitiveInteger

   Long longObject
   long primitiveLong

   Float floatObject
   float primitiveFloat
         
   Double doubleObject
   double primitiveDouble
}

Step 4: Create a scaffolded controller for the FooBar domain

grails create-scaffold-controller experiment.FooBar

When you run the application and click on the FooBar controller, the following is what you'll see:



As you can see here the String object, the boolean primitive, and Boolean object are NOT marked as being required.  That means you can leave these fields empty.  In other words, it would appear that by default String and Boolean properties are allowed to be null.  Unfortunately, as the following unit test shows, this is not the case either.

Notice also that a Character object does not generate a text field, but a primitive char does.

package experiment

import grails.test.mixin.*
import org.junit.*

/**
 * See the API for {@link grails.test.mixin.domain.DomainClassUnitTestMixin} for usage instructions
 */
@TestFor(FooBar)
class FooBarTests {

   FooBar fooBar

   @Before
   void setup() {
      fooBar = new FooBar()

      fooBar.stringObject = "Hello World"

      char ch = 'G'
      fooBar.characterObject = new Character(ch)
      fooBar.primitiveCharacter = ch

      fooBar.booleanObject = new Boolean(true)
      fooBar.primitiveBoolean = true

      // *** Numbers
      byte b = 10
      fooBar.byteObject = new Byte(b)
      fooBar.primitiveByte = b

      short s = 1000
      fooBar.shortObject = new Short(s)
      fooBar.primitiveShort = s

      fooBar.integerObject = new Integer(123)
      fooBar.primitiveInteger = 123

      fooBar.longObject = new Long(10000L)
      fooBar.primitiveLong = 10000L

      fooBar.floatObject = new Float(234.5f)
      fooBar.primitiveFloat = 234.5f

      fooBar.doubleObject = new Double(123.4)
      fooBar.primitiveDouble = 123.4
   }

   void testHappy() {
      assert fooBar.validate()
   }

   void testStringObject() {
      // Recall Grails doesn't make String properties required fields.
      fooBar.stringObject = null
      assert fooBar.validate()
   }

   void testCharacterObject() {
      // Recall Grails doesn't create a text field for Character properties.
      fooBar.characterObject = null
      assert !fooBar.validate()
   }

   void testBooleanObject() {
      // Recall Grails doesn't make Boolean properties required fields.
      fooBar.booleanObject = null
      assert fooBar.validate()
   }

   // *** Numbers

   void testByteObject() {
      fooBar.byteObject = null
      assert !fooBar.validate()
   }

   void testShortObject() {
      fooBar.shortObject = null
      assert !fooBar.validate()
   }

   void testIntegerObject() {
      fooBar.integerObject = null
      assert !fooBar.validate()
   }

   void testLongObject() {
      fooBar.longObject = null
      assert !fooBar.validate()
   }

   void testFloatObject() {
      fooBar.floatObject = null
      assert !fooBar.validate()
   }


   void testDoubleObject() {
      fooBar.doubleObject = null
      assert !fooBar.validate()
   }
}
If you run the above unit test testStringObject and testBooleanObject fail which is in accordance with the Grails reference documentation.

Note: I didn't test the primitive types because they're given a default value and can't be nulled.

The H2 console will tell you that none of these fields can be null.

Note: Something else you might notice from the H2 console is that it uses a CHAR(255) to store a single character.  That seems like a lot of wasted space.  You can fix that inefficiency by using the following mapping block:

static mapping = {
    primitiveCharacter column: "primitiveCharacter", sqlType: "char", length: 1
}

The last experiment I did was to comment out all of FooBar's fields except the String and Boolean ones.  I re-ran the application and clicked create without filling in any fields.  Unsurprisingly, Grails saved the FooBar record.  When I viewed the saved record in the H2 console it told me that Grails had automatically set the Boolean fields to false and the String is an empty String.

No comments:

Post a Comment