Did you know? DZone has great portals for Python, Cloud, NoSQL, and HTML5!

Marcin Świerczyński is a software developer especially interested in Java and Python languages, Java-based technologies and mobile applications development. He strongly believes in Software Craftsmanship as a way to build reliable and maintainable software. Marcin has posted 3 posts at DZone. View Full User Profile

Null Value on Save Issue in Grails

04.12.2010
Email
Views: 10061
  • submit to reddit

If you’ve used Grails, you’re probably familiar with a domain class and its “constraints” block. There you can define conditions which have to be met by class’s fields. For example, you can enforce that a field have to be not-empty using “blank: false” condition. It’d be intuitive not to define conditions for the field you don’t care of. Unfortunately, there is a small trap here. Let’s use an example to explain it.

Let’s define an Invoice class with a few fields: number, draw date and payment date. The number and draw data are obligatory while the payment date isn’t, because you’re able to pay for an invoice by cash.

class Invoice { String number; Date drawDate; Date paymentDate; 
static constraints = { number(blank: false); drawDate(blank: false); } }

It looks good, doesn’t it? You can generate controller and views for that class and test it in a browser. Try to leave a payment date field empty and save an invoice. Success – it works! OK, so let’s create a BootStrap entry for our new class. Again, try to omit its optional field – paymentDate.

def invoice = new Invoice(number: “1/2010”, drawDate: new Date()).save(); 

What value does invoice variable have? Null! What’s wrong? Why does it work in a browser and not directly in a code?

The answer is quite straight, but I haven’t found it in a documentation. The default, implicit value of a field’s constraint is “nullable: false”. When you fills in a form in your browser, you really sends a blank value – empty string. This string isn’t null so it meets “nullable: false” criteria. On the other hand, if you creates a new object in your code and you omits a field, you pass a null value and hence the validator doesn’t allow to create an object! Unfortunately, Grails doesn’t provide any descriptive message on what’s really going under the hood.

What can you do? You can set an explicit constraint for such a field: “nullable: true”. The complete class would be:

class Invoice { String number; Date drawDate; Date paymentDate; 
static constraints = { number(blank: false); drawDate(blank: false); paymentDate(nullable: true); } }  

Now, the Invoice creation code will pass without a problem and will return an expected object.

Personally, I suggest to set “nullable: true” constraint for every field which is optional and “blank: false” for every obligatory field.

0
Your rating: None
Published at DZone with permission of its author, Marcin Świerczyński.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

wilson ikeda replied on Mon, 2010/04/12 - 9:04am

Hmm, blank constraints only applies to String fields.

Heidi Elliott replied on Mon, 2010/04/12 - 9:44am

There is documentation on constraints: http://grails.org/doc/1.2.x/ref/Constraints/Usage.html

In the documentation for the null constraint specifically, it mentions that all properties on domain objects are not nullable by default. You will always have to place a nullable:true on the properties that are optional. You can set a global set of contraints in the Config.groovy file.

grails.gorm.default.constraints = {
'*'(nullable:true)
}

But if you did this, you would need to place a nullable:false on all your required properties. Placing contraints on specific properties in your individual domain classes will override these global contraints. I would think that if you have more optional properties versus required ones on the domain objects in your application, the global constraint might be the best option.

I tried out your example in a test application. If you were using scaffolding, three boxes (i.e., day, month, year selection boxes) for the date properties appear in the view. They do not allow the user to select blank values for these fields. So you would never get an error on your invoice object using the scaffolded views and controller.

As far as the error you saw in the bootstrap, the save() method will return null if it could not save your domain object. You can find out what is wrong with your object during an attempted save by creating your object first and then calling save(). You can also use hasErrors() and the errors collection to see what is going on. The errors collection on the domain object will give you specifics on the error(s).

def invoice = new Invoice(number: “1/2010”, drawDate: new Date())
invoice.save();

println invoice.hasErrors() //will return false
println invoice.errors //will print out a list of the errors on the object

 

Marcin Świerczyński replied on Mon, 2010/04/12 - 12:15pm

Thank you for interesting comments.

You're right with default "nullable" value. My intention was to show some kind of inconsistence - if you use a String typed field in a browser, empty value will pass, while it'll return an error in a code. Probably the example with Dates wasn't the best - I'm sorry.

I'm grateful for your feedback and valuable examples.

Peter Ledbrook replied on Wed, 2010/04/14 - 5:32am

Hi Marcin,

This is indeed an issue that can trip people up. I typically recommend the same approach as you: "nullable" and "blank" should have the same value. If they have different values, you may not get the behaviour you expect.

Having said that, "nullable: true" and "blank: false" can be useful, for example if you want to programatically create instance with a null field, but users should never be able to do the same. You just have to be wary about what the exact behaviour is for an empty string. That's something I should look into and add to the documentation.

Jim Norman replied on Mon, 2010/06/28 - 4:09pm

My takeaway from dealing with this subject: don't declare member variables in a domain object as 'int'. The "nullable:true" discussion is applicable to "Integer", but not "int".

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.