I am trying to define a form in play! 2.0.4 with the following properties and constraints:
The form handles repeated values (lets conveniently assume that these values are of type number
). So this will get us to something like this:
"numbers" -> list(number)
Each number must be unique, i.e. it must be unique with regard to all the other numbers submitted and it must be unique to the numbers that are already existent in the database (this can be checked via some function check(num: Int): Boolean
).
The form error should be specific to the number, that is not unique. I don't want a general form error saying "There is duplicate number".
What would be the best way to go?
The trick here is to define a custom Constraint
sort of like this example. The custom Constraint
can then be used on the Mapping[T]
to verify a field in the form with the verifying
method.
The custom Constraint
contains the logic to return a ValidationResult
that is either Valid
or Invalid
. An error message can be passed to an Invalid
result which is where you can specify what is duplicated or exists in the database.
See Play for Scala for a section on custom validation.
//Make this lazy to prevent java.lang.ExceptionInInitializerError at runtime.
lazy val uniqueNumbersConstraint = Constraint[String](Some("Unique numbers constraint"), "")(checkNumbers)
//"Business Logic".
//Important part here is that the function returns a ValidationResult and complies with the signature for Constraint. i.e. f: (T) => ValidationResult
//Return Valid if n in numbers is not in database and there are no duplicates.
//Otherwise return Invalid and an error message showing what numbers are in the database or duplicated.
def checkNumbers(numbers: String):ValidationResult = {
val splitNums = numbers.split(" ").toList.map(_.toInt)
val dbnums = splitNums.partition(database.contains(_))
if(dbnums._1.isEmpty && uniquesAndDuplicates(splitNums)._2.isEmpty){
Valid
}else{
val duplicates = uniquesAndDuplicates(dbnums._2)._2
val error = "Database contains: " + dbnums._1 + ", duplicated values: " + duplicates
Invalid(error)
}
}
val helloForm = Form(
tuple(
"numbers" -> nonEmptyText.verifying(uniqueNumbersConstraint)
))
//Return unique values on left side and duplicate values on right side
def uniquesAndDuplicates(numbers: List[Int]):Tuple2[List[Int], List[Int]] = {
numbers.partition(i => numbers.indexOf (i) == numbers.lastIndexOf(i))
}
def checkNum(num: Int) = {
database.contains(num)
}
val database = List(5,6,7)
Note I defined numbers
as a String
in the form. When I defined it as list(number)
it kept evaluating to List()
. I think that is a binding issue. It's a fairly simple change to use List(1,2,3)
instead of "1 2 3"
if using list(number)
works.
How about something like:
def validateUnique(input: List[Int]): ValidationResult = {
// Assuming check return true if the input num doesn't exist yet
def check(num: Int): Boolean = num % 2 == 0
val unique = input.toSet
val dbDuplicates = unique.filterNot(check)
val formDuplicates = input.diff(unique.toSeq)
val duplicates = (dbDuplicates ++ formDuplicates).toList
duplicates match {
case List() => Valid
case _ => Invalid("Duplicates: " + duplicates.mkString(", "))
}
}
val uniqueConstraint = Constraint[List[Int]](validateUnique(_))
And then you can just use the new constraint with:
mapping(
...,
"ints" -> list(number).verifying(uniqueConstraint)
...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With