I have the following domain class in my grails project:
class Vacation {
Date start
Date end
User vacationer
static constraints = {
start(validator: {return (it >= new Date()-1)})
}
}
Is it possible to add a validator that requires end to be equal or greater than start?
Cheers
Use
start(validator: {
val, obj ->
val < obj.properties['end']
})
You can directly access property "end", since obj is the object of the class Vacation only, where it is defined.
Use:
start(validator: {
val, obj ->
val < obj.end
})
Related
I try to validate a nested domain class instance on a command object.
Having the following command object
package demo
import grails.databinding.BindingFormat
class SaveEventCommand {
#BindingFormat('yyyy-MM-dd')
Date date
Refreshment refreshment
static constraints = {
date validator: { date -> date > new Date() + 3}
refreshment nullable: true
}
}
And having the following domain class with its own constraints
package demo
class Refreshment {
String food
String drink
Integer quantity
static constraints = {
food inList: ['food1', 'food2', 'food3']
drink nullable: true, inList: ['drink1', 'drink2', 'drink3']
quantity: min: 1
}
}
I need when refreshment is not nullable the command object validates the date property and check the corresponding restrictions in refreshment instance
For now try with this code in the controller:
def save(SaveEventCommand command) {
if (command.hasErrors() || !command.refreshment.validate()) {
respond ([errors: command.errors], view: 'create')
return
}
// Store logic goes here
}
Here through !command.refreshment.validate() I try to validate the refresh instance but I get the result that there are no errors, even when passing data that is not correct.
Thank you any guide and thank you for your time
I typically just include some code that will use a custom validator to kick off validation for any property that is composed of another command object. For example:
thePropertyInQuestion(nullable: true, validator: {val, obj, err ->
if (val == null) return
if (!val.validate()) {
val.errors.allErrors.each { e ->
err.rejectValue(
"thePropertyInQuestion.${e.arguments[0]}",
"${e.objectName}.${e.arguments[0]}.${e.code}",
e.arguments,
"${e.objectName}.${e.arguments[0]}.${e.code}"
)
}
}
})
This way it's pretty clear that I want validation to occur. Plus it moves all the errors up into the root errors collection which makes things super easy for me.
Two things I could think of:
Implement grails.validation.Validateable on your command object
What happens when you provide an invalid date? Can you see errors while validating?
We have a class like this in a Grails 2.4.3 application (migrated from 2.3.8):
#Validateable
class Foo {
Integer noDefault;
Integer withDefault = 1;
static constraints = {
noDefault(nullable:false)
withDefault(nullable:false)
}
}
This class is being instantiated in a complex configuration mechanism using a Map like this:
[
noDefault: 0,
withDefault: 2
]
(In fact the Map is part of a huge one, but the class constructor sees this small one.) Formerly the class worked if we omitted the withDefault entry from the config map, using the default value which is not null. In Grails 2.4.3, however, it tells me that this field cannot be null. I can fix it by letting it be null in the constraint, but it lets setting the explicite value null (and overwrite the default value), which causes problem during operation.
Do you know some workaround, which preserves the semantics and correct operation?
Thanx in advance, best regards: Balázs
What you are describing is not consistent with what I would expect and not consistent with the behavior I am seeing. The project at https://github.com/jeffbrown/validatedefaults contains the following code.
At https://github.com/jeffbrown/validatedefaults/blob/master/src/groovy/demo/Foo.groovy
// src/groovy/demo/Foo.groovy
package demo
import grails.validation.Validateable
#Validateable
class Foo {
Integer noDefault;
Integer withDefault = 1;
static constraints = {
noDefault(nullable:false)
withDefault(nullable:false)
}
}
The test at https://github.com/jeffbrown/validatedefaults/blob/master/test/unit/demo/FooSpec.groovy passes:
// test/unit/demo/FooSpec.groovy
package demo
import spock.lang.Specification
import grails.test.mixin.TestMixin
import grails.test.mixin.support.GrailsUnitTestMixin
#TestMixin(GrailsUnitTestMixin)
class FooSpec extends Specification {
void 'test validating default values'() {
given:
def map = [noDefault: 0]
def foo = new Foo(map)
expect:
foo.validate()
}
}
When I run the app I get the same behavior.
// grails-app/conf/BootStrap.groovy
import demo.Foo
class BootStrap {
def init = { servletContext ->
def map = [noDefault: 0]
def foo = new Foo(map)
// this prints true...
println "Foo is valid? : ${foo.validate()}"
}
def destroy = {
}
}
I hope that helps.
I'm using Constraints on my web forms and I've noticed that several forms have similar validations, for instance I have several types of form with a start date and an end date. In each case, I want to validate that the start date is before the end date. Here's the case class I'm creating from my form:
case class OrderSearchForm(orderId: Option[Int], startDate:Option[Long], endDate:Option[Long])
and my validation (Let's ignore the .get() for now):
def validateSearchDate = Constraint[OrderSearchForm]{
osf: OrderSearchForm => {
if (!osf.startDate.isEmpty && !osf.endDate.isEmpty && osf.startDate.get.compareTo(osf.endDate.get) > 0 )
Invalid("Begin Date is after End Date.")
else
Valid
}
}
Now, since I have lots of forms with a start date and an end date, I'd like to re-write my validation to work with all of the case classes representing these forms. I'm wondering whether the typeclass pattern can help me with this:
trait TwoDates[T] {
def twoDatesTuple(t: T): (Option[Long], Option[Long])
}
trait TwoDatesOSF extends TwoDates[OrderSearchForm] {
def twoDatesTuple(t: OrderSearchForm) = (t.startDate, t.endDate)
}
implicit object TwoDatesOSF extends trait TwoDatesOSF
def validateSearchDate = Constraint[TwoDates[_]] { t: TwoDates[_] => ... (as above)}
but applying does not work:
validateSearchDate(OrderSearchForm(None, None, None))
yields:
error: type mismatch; found : OrderSearchForm required:
TwoDates[_]
betweenDates(osf)
1) Can I write generic validations using typeclasses? If so, what am I doing wrong?
2) Can I write generic validations while AVOIDING using super-classes (i.e.
abstract class TwoDates(start: Option[Long], end:Option[Long])
case class OrderSearchForm(orderId: Option[String], startDate:Option[Long], endDate:Option[Long]) extends TwoDates(startDate, endDate)
which seems awkward once multiple validations are in play)
Thanks!
I think you can use structural types:
private type TwoDates = { def startDate: Option[Date]; def endDate: Option[Date] }
def validateTwoDates = Constraint[TwoDates] { osf: TwoDates =>
if (!osf.startDate.isEmpty &&
!osf.endDate.isEmpty &&
osf.startDate.get.compareTo(osf.endDate.get) > 0) {
Invalid("Begin Date is after End Date.")
} else Valid
}
case class Something(
startDate: Option[Date],
endDate: Option[Date],
name: String)
private val form = Form(mapping(
"startDate" -> optional(date),
"endDate" -> optional(date),
"name" -> text)
(Something.apply)(Something.unapply).verifying(validateTwoDates))
I have several domain classes that are related and I am trying to figure out how to implement a constraint that depends on multiple domains. The jist of the problem is:
Asset has many Capacity pool objects
Asset has many Resource objects
When I create/edit a resource, need to check that total resources for an Asset doesn't exceed Capacity.
I created a service method that accomplishes this, but shouldn't this be done via a validator in the Resource domain? My service class as listed below:
def checkCapacityAllocation(Asset asset, VirtualResource newItem) {
// Get total Resources allocated from "asset"
def allAllocated = Resource.createCriteria().list() {
like("asset", asset)
}
def allocArray = allAllocated.toArray()
def allocTotal=0.0
for (def i=0; i<allocArray.length; i++) {
allocTotal = allocTotal.plus(allocArray[i].resourceAllocated)
}
// Get total capacities for "asset"
def allCapacities = AssetCapacity.createCriteria().list() {
like("asset", asset)
}
def capacityArray = allCapacities.toArray()
def capacityTotal = 0.0
for (def i=0; i<capacityArray.length; i++) {
capacityTotal += capacityArray[i].actualAvailableCapacity
}
if (allocTotal > capacityTotal) {
return false
}
}
return true
}
The problem I am having is using this method for validation. I am using the JqGrid plugin (with inline editing) and error reporting is problematic. If I could do this type of validation in the domain it would make things a lot easier. Any suggestions?
Thanks so much!
To use the service method as a validator, you'll need to inject the service into your domain, then add a custom validator that calls it. I think it'll look something like this:
class Asset {
def assetService
static hasMany = [resources: Resource]
static constraints = {
resources(validator: { val, obj ->
obj.assetService.checkCapacityAllocation(obj, val)
})
}
}
How about:
def resourceCount = Resource.countByAsset(assetId)
def assetCapacityCount = AssetCapacity.countByAsset(assetId)
if(resourceCount < assetCapacityCount) return true
return false
HTH
I have a Grails domain object that looks like this:
class Product {
Boolean isDiscounted = false
Integer discountPercent = 0
static constraints = {
isDiscounted(nullable: false)
discountPercent(range:0..99)
}
I'd like to add a validator to discountPercent that will only validate if isDiscounted is true, something like this:
validator: { val, thisProduct ->
if (thisProduct.isDiscounted) {
// need to run the default validator here
thisProduct.discountPercent.validate() // not actual working code
} else {
thisProduct.discountPercent = null // reset discount percent
}
Does anyone know how I can do this?
This is more or less what you need (on the discountPercent field):
validator: { val, thisProduct ->
if (thisProduct.isDiscounted)
if (val < 0) {
return 'range.toosmall' //default code for this range constraint error
}
if (99 < val) {
return 'range.toobig' //default code for this range constraint error
} else {
return 'invalid.dependency'
}
You can't both have a special validator that relies on something else and have a special validator, as you can't run a single validator on a field (that I know of), only on single properties. But if you run a validation on this property, you'll depend on yourself and go into endless recursion. Therefore I added the range check manually. In your i18n files you can set up something like full.packet.path.FullClassName.invalid.dependency=Product not discounted.
Good luck!