Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Grails conditional nullable validation or custom validator with nullable option

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?

like image 617
Eduard Avatar asked May 29 '12 08:05

Eduard


2 Answers

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)
}
like image 161
Eduard Avatar answered Sep 19 '22 15:09

Eduard


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]
like image 33
Tri Avatar answered Sep 22 '22 15:09

Tri