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?

Original source