Grails: can I make a validator apply to create only (not update/edit)
Asked Answered
C

2

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"
}
Catamnesis answered 9/5, 2011 at 21:43 Comment(1)
Will it ever be the case that startDate gets updated after the initial save?Malia
E
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()
   ...
}
Exterminatory answered 9/5, 2011 at 22:32 Comment(1)
This is exactly what I needed + a rather nice hack.Catamnesis
M
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.

Malia answered 9/5, 2011 at 21:57 Comment(5)
Not entirely sure how to use this; please see my edit to my original question.Catamnesis
@Catamnesis - 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.Malia
@Catamnesis - Made another update after doing some testing. Perhaps it will work for you.Malia
cool I'm just gonna live super dangerously and merely change my broken code to include validate: falseCatamnesis
@Catamnesis - 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.Malia

© 2022 - 2024 — McMap. All rights reserved.