Unique constraint for repeated values
forms, playframework, playframework-2.0, scala, unique-constraint
Solution
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.
- Create the Constraint
//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)
}
}
- Validate Form Using Custom Constraint
val helloForm = Form(
tuple(
"numbers" -> nonEmptyText.verifying(uniqueNumbersConstraint)
))
- Utilities
//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)
- etc
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.
- Samples
Problem
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?