Grails conditional nullable validation or custom validator with nullable option
Asked Answered
M

2

7

I have a form to create a place. Depending of the country, the province (state, region) field is required or not.

When is not required, I want to be null, not empty string. I have code that makes all empty form fields, null:

def newparams = [:]
        place = new Place()
        params.each() { k, v ->
            if (v instanceof String && place.hasProperty(k)) {
                if (!v.trim().length()) {
                    newparams[k] = null
                } else {
                    newparams[k] = v
                }
            }
        }

        place = new Place(newparams)
        place.validate()

Now, in the place domain, I have a validator on the province:

province validator:  {val, obj -> if (obj.country in obj.requiresRegionCountries() && !obj.province) return [province.required]}

With this rule, I always get "province can't be null" even if it is required or not.

I think this is because the nullable validator that is set default to false.

If I am adding nullable: true, then even if province is required, the custom validator is skipped and it is possible to save with empty province (I think that is because it gets instantiated with null)

Now, I need somehow my custom validator and also ability to specify the nullable in my validator, something like this:

province validator:  {val, obj -> 
  if (obj.country in obj.requiresRegionCountries() && !obj.province) { 
    nullable: false
    return [province.required] }
  else {
    nullable: true
  }
}

How can I achieve this in Grails 2.0.3?

Muenster answered 29/5, 2012 at 8:21 Comment(0)
S
6

I can't tell with the code you've pasted but if your problem is that the default validation doesn't allow province to be null, have you tried explicitly allowing province to be null? You are allowed multiple validators for each field. So back in your original code, just specify the nullable validator as well:

province nullable: true, validator:  {val, obj -> 
  if (obj != null && obj.country in obj.requiresRegionCountries() && !obj.province) 
    return [province.required]
}

EDIT: In the custom validator, might also want to guard against the obj being null in the if condition.

EDIT2: Demo project showing the above validation working on grails 2.0.4

class Place {
String country
Province province

  static constraints = {
    province (nullable: true, validator: {val, obj ->
        if (obj.country == 'Canada' && !val) return ['province.required']
    })
  }
}

Controller...

 class MainController {
  def index() {
    def place = new Place(country: 'Canada')
    if (!place.validate()) {
        render "need province<br/>" + place.errors
    } else {
        render "cool"
    }

So the idea is that I have a dummy controller where I can invoke the index action which is hardcoded to create a Place domain instance similar to your example. Notice I only defined the country string so I can key my logic on that for the custom validation. I didn't define the province when creating the Place instance so it should be null. Under that scenario, the response page will print the following...

Output snippet ...

need province 
grails.validation.ValidationErrors: 1 .... does not pass custom validation]

If I remove the nullable: true constraint from Place, then the error is the null value as expected...

Output snippet ...

need province
grails.validation.ValidationErrors: 1 .... cannot be null]
Striction answered 6/7, 2012 at 14:30 Comment(4)
Thank you for your response, but there is a problem. If I set to be nullable and place.province is null, then custom validation is totally skipped. I want to be nullable for some countries but not in the others.Muenster
To be clear: if I do not put nullable true and the filed is null, the error "Can't be null" is trown and my custom validator is not executed. If I add nullable: true and the value is null, the validation will pass, my custom validator will not be executed. I want to be null for some countries and not null for others. This is something that I can't do with the current behavior.Muenster
Maybe it's a grails 2.0.3 bug. I'm running 2.0.4 and it's working for me with a simple demo project I created that I'll paste to my answer above so you can compareStriction
This code: static constraints = { province (nullable: true, validator: {val, obj -> if (obj.country == 'Canada' && !val) return ['province.required'] }) } in Grails 2.0.3 if the val is null, the validator will pass and the custom validator will never be called. But, I found out that, if you put the custom validator first, then the nullable, everything is working fineMuenster
M
13

After lots of research and feedback I found out 2 solutions that are working. One is in controller. Do not add any validation in model and add them dynamically from controller:

class PlacesController {
  def create() {
  def place = new Place(params.address)
  if (place.country in placesThatRequiresProvinceArray) {
      place.constrains.province.nullable = false
  } else {
      place.constrains.province.nullable = true
  }

}

The other solution is the one proposed by Tri in this thread, but put the custom validator before the nullable constraint (else the custom validator will not be called for null values):

static constraints = {
  province (validator: {val, obj ->
    if (obj.country == 'Canada' && !val)
      return ['province.required']
  }, nullable: true)
}
Muenster answered 12/7, 2012 at 8:11 Comment(0)
S
6

I can't tell with the code you've pasted but if your problem is that the default validation doesn't allow province to be null, have you tried explicitly allowing province to be null? You are allowed multiple validators for each field. So back in your original code, just specify the nullable validator as well:

province nullable: true, validator:  {val, obj -> 
  if (obj != null && obj.country in obj.requiresRegionCountries() && !obj.province) 
    return [province.required]
}

EDIT: In the custom validator, might also want to guard against the obj being null in the if condition.

EDIT2: Demo project showing the above validation working on grails 2.0.4

class Place {
String country
Province province

  static constraints = {
    province (nullable: true, validator: {val, obj ->
        if (obj.country == 'Canada' && !val) return ['province.required']
    })
  }
}

Controller...

 class MainController {
  def index() {
    def place = new Place(country: 'Canada')
    if (!place.validate()) {
        render "need province<br/>" + place.errors
    } else {
        render "cool"
    }

So the idea is that I have a dummy controller where I can invoke the index action which is hardcoded to create a Place domain instance similar to your example. Notice I only defined the country string so I can key my logic on that for the custom validation. I didn't define the province when creating the Place instance so it should be null. Under that scenario, the response page will print the following...

Output snippet ...

need province 
grails.validation.ValidationErrors: 1 .... does not pass custom validation]

If I remove the nullable: true constraint from Place, then the error is the null value as expected...

Output snippet ...

need province
grails.validation.ValidationErrors: 1 .... cannot be null]
Striction answered 6/7, 2012 at 14:30 Comment(4)
Thank you for your response, but there is a problem. If I set to be nullable and place.province is null, then custom validation is totally skipped. I want to be nullable for some countries but not in the others.Muenster
To be clear: if I do not put nullable true and the filed is null, the error "Can't be null" is trown and my custom validator is not executed. If I add nullable: true and the value is null, the validation will pass, my custom validator will not be executed. I want to be null for some countries and not null for others. This is something that I can't do with the current behavior.Muenster
Maybe it's a grails 2.0.3 bug. I'm running 2.0.4 and it's working for me with a simple demo project I created that I'll paste to my answer above so you can compareStriction
This code: static constraints = { province (nullable: true, validator: {val, obj -> if (obj.country == 'Canada' && !val) return ['province.required'] }) } in Grails 2.0.3 if the val is null, the validator will pass and the custom validator will never be called. But, I found out that, if you put the custom validator first, then the nullable, everything is working fineMuenster

© 2022 - 2024 — McMap. All rights reserved.