7

I have a domain class that needs to have a date after the day it is created in one of its fields.

class myClass {
  Date startDate
  String iAmGonnaChangeThisInSeveralDays
  static constraints = {
    iAmGonnaChangeThisInSeveralDays(nullable:true)
    startDate(validator:{
        def now = new Date()
        def roundedDay = DateUtils.round(now, Calendar.DATE)
        def checkAgainst
        if(roundedDay>now){
            Calendar cal = Calendar.getInstance();
            cal.setTime(roundedDay);
            cal.add(Calendar.DAY_OF_YEAR, -1); // <--
            checkAgainst = cal.getTime();
        }
        else checkAgainst = roundedDay

        return (it >= checkAgainst)
    })
  }
}

So several days later when I change only the string and call save the save fails because the validator is rechecking the date and it is now in the past. Can I set the validator to fire only on create, or is there some way I can change it to detect if we are creating or editing/updating?

@Rob H I am not entirely sure how to use your answer. I have the following code causing this error:

myInstance.iAmGonnaChangeThisInSeveralDays = "nachos"
myInstance.save()
if(myInstance.hasErrors()){
  println "This keeps happening because of the stupid date problem"
}
skaffman
  • 398,947
  • 96
  • 818
  • 769
Mikey
  • 4,692
  • 10
  • 45
  • 73

2 Answers2

14

You can check if the id is set as an indicator of whether it's a new non-persistent instance or an existing persistent instance:

startDate(validator:{ date, obj ->
   if (obj.id) {
      // don't check existing instances
      return
   }
   def now = new Date()
   ...
}
Burt Beckwith
  • 75,342
  • 5
  • 143
  • 156
0

One option might be to specify which properties you want to be validated. From the documentation:

The validate method accepts an optional List argument which may contain the names of the properties that should be validated. When a List is passed to the validate method, only the properties defined in the List will be validated.

Example:

// when saving for the first time:
myInstance.startDate = new Date()
if(myInstance.validate() && myInstance.save()) { ... }

// when updating later
myInstance.iAmGonnaChangeThisInSeveralDays = 'New Value'
myInstance.validate(['iAmGonnaChangeThisInSeveralDays'])
if(myInstance.hasErrors() || !myInstance.save(validate: false)) {
    // handle errors
} else {
    // handle success
}

This feels a bit hacky, since you're bypassing some built-in Grails goodness. You'll want to be cautious that you aren't bypassing any necessary validation on the domain that would normally happen if you were to just call save(). I'd be interested in seeing others' solutions if there are more elegant ones.

Note: I really don't recommend using save(validate: false) if you can avoid it. It's bound to cause some unforeseen negative consequence down the road unless you're very careful about how you use it. If you can find an alternative, by all means use it instead.

Rob Hruska
  • 118,520
  • 32
  • 167
  • 192
  • Not entirely sure how to use this; please see my edit to my original question. – Mikey May 09 '11 at 22:07
  • 1
    @Mikey - Updated my answer. I need to run a quick test to make sure it's right. If not, it can be updated to use `save(validate: false)` to avoid Grails calling `validate()` after you already did it yourself. – Rob Hruska May 09 '11 at 22:16
  • @Mikey - Made another update after doing some testing. Perhaps it will work for you. – Rob Hruska May 09 '11 at 22:25
  • cool I'm just gonna live super dangerously and merely change my broken code to include validate: false – Mikey May 09 '11 at 22:25
  • @Mikey - FWIW, yeah, that does feel pretty dangerous. I hesitate to even recommend using it in my answer here. There are other options, too. You could have a transient property on the domain that indicates whether or not `startDate` should be validated, and then use that in the custom validator. – Rob Hruska May 09 '11 at 22:28