I have a problem with defining a custom Constraint on my form. This is my form:
val form = Form(
mapping(
"field" -> boolean
)(MyForm.apply)(MyForm.unapply)
If I do the following:
val form = Form(
mapping(
"field" -> boolean
)(MyForm.apply)(MyForm.unapply).verifying("my constraint", f => f.field == true)
then it works fine. However, if I try to use the following custom defined Constraint , then a null pointer exception is thrown and I do not knowing why:
val form = Form(
mapping(
"field" -> boolean
)(MyForm.apply)(MyForm.unapply).verifying(myconstraint)
val myconstraint : Constraint[MyForm] = Constraint("myconstraint")({
form =>
if(form.field == true){
Valid
}else {
Invalid(Seq(ValidationError("problem")))
}
})
I tried to use println to debug but I nothing is printed in the console.
Try moving myconstraint definition above the form definition like so
val myconstraint : Constraint[MyForm] = Constraint("myconstraint")({ ...
...
val form = Form( ...
due to possible forward reference problem where we end up referencing an uninitialised value.
Related
In scala play, a form can be created as :
val userForm = Form(
mapping(
"name" -> text,
"age" -> number
)(User.apply)(User.unapply)
)
or :
val loginForm = Form(
tuple(
"email" -> text,
"password" -> text
)
)
I have digged into Form.scala in github. And the code piece that makes these possible is as follows :
object Form {
/**
* Creates a new form from a mapping.
*
* For example:
* {{{
* import play.api.data._
* import play.api.data.Forms._
* import play.api.data.format.Formats._
*
* val userForm = Form(
* tuple(
* "name" -> of[String],
* "age" -> of[Int],
* "email" -> of[String]
* )
* )
* }}}
*
* #param mapping the form mapping
* #return a form definition
*/
def apply[T](mapping: Mapping[T]): Form[T] = Form(mapping, Map.empty, Nil, None)
My questions are :
1- What is mapping? I cannot find it. I suppose it should be something like object mapping or at least case class mapping ? I cannot find it in the source code.
2- I also dont understand the part after mapping(), where (User.apply)(User.unapply) is added. It is said they are to construct and deconstruct the Form. I dont quite get it though.
3- How can tuple can be used in place of Mapping ? In the api, I cannot find any clue that they are related at all.
Thank you.
Tuple in play forms are simple Tuples in scala, while mapping lets you to map your form into your own type like case class ,an example of this can be.
case class User(name:String,age:Int)
if you want to your form to be mapped to case class then you can do like this,
val userForm = Form(
mapping(
"name" -> text,
"age" -> number
)(User.apply)(User.unapply)
)
and then you can directly bind your case class User to your form-data by
val res = request.bindFormRequest.get
here res will be of the type User or an instance of User class
but you can simply use tuple for this like,
val userForm = Form(
tuple(
"email" -> text,
"password" -> text
)
)
and get the result as follows
val res = request.bindFormRequest.get
here res will be of the type (String,Int) and you can access name and age as res._1 , res._2 resp.
Now, coming to your mapping function explanation. mapping function takes two other functions apply and unapply. apply and unapply methods are used to convert form one form to other form. Eg:
object User {
def apply(name: String, age: Int) = {
name + "," + age.toString
}
def unapply(nameAndAge: String) = {
val r = nameAndAge.split(",")
(r.head, r.last)
}
}
println(User("curious", 23))
println(User.unApply("curious,23"))
Here, apply method takes two parameters name and age of String and Int type resp. converts it to a String after concatenating them and unapply takes a String Type parameter and returns a String and Int type parameters. i.e apply and unapply methods are like ,A=>T and T=>A resp.
for more information on forms you can refer to play framework's Doc.
for more refrence on Mapping trait the source code is here.
https://github.com/playframework/playframework/blob/master/framework/src/play/src/main/scala/play/api/data/Form.scala
I came across the below method here
implicit def toLazyOr[T](cons: Constraint[T]) = new {
def or(other: Constraint[T]) = Constraint { field: T =>
cons(field) match {
case Valid => other(field)
case Invalid => Invalid
}
}
}
I defined toLazyOr method and then I am trying to use it in my code. But, I am not sure how do I use this.
I tried:
val adminForm = Form(
mapping(
"email" -> (email verifying toLazyOr(nonEmpty, minLength(4)) )
)
And:
val adminForm = Form(
mapping(
"email" -> (email verifying toLazyOr(nonEmpty or minLength(4)) )
)
Both are not working and my scala knowledge for the moment is very basic.
Please help.
Without knowing very much about play:
If the implicit conversion is in scope, the following should work:
val adminForm = Form(
mapping(
"email" -> (email verifying (nonEmpty or minLength(4)))
))
That's the thing about implicit conversions: You don't have to call them explicitly. See this answer for more information about where the compiler is looking for implicits.
I'm having trouble identifying some weird behavior while using fillAndValidate on a Form. Here is the simplest use case I could think of to isolate the issue:
object Application extends Controller {
case class SimpleForm(val field: String)
val form = Form(
mapping(
"field" -> text.verifying("message", (s: String) => false)
)(SimpleForm.apply)(SimpleForm.unapply))
def index = Action { implicit request =>
println("bindFromRequest: " + form.bindFromRequest.errors)
println("fillAndValidate: " + form.fillAndValidate(SimpleForm("value")).errors)
Ok
}
}
When requesting the index page with the query ?field=value, I'm getting the following on the console:
bindFromRequest: List(FormError(field,message,WrappedArray()))
fillAndValidate: List(FormError(field,message,WrappedArray()))
Which is, as far as I know, the expected behavior.
However, when using global errors, such as verifying the mapping directly:
val form = Form(
mapping(
"field" -> text
)(SimpleForm.apply)(SimpleForm.unapply)
.verifying("message", (s: SimpleForm) => false))
The behavior I get is quite surprising:
bindFromRequest: List(FormError(,message,WrappedArray()))
fillAndValidate: List()
Why is fillAndValidate ignoring the error? Is there a way to get around this issue?
My application contain big form with 18 fields. It is processed with standard form mapping, like this:
val bigForm = Form(
mapping(
"id" -> of[ObjectId],
"title" -> text,
// And another 16 fields...
...
)
)
And all was well, but today I decided to add one more field and here comes the problem - mapping is not able to take more than 18 arguments.
What should I do then? I thinking of combining some fields into the structure, but additional structure requires additional formatter, JSON serializer and deserializer, too much work. I'm looking for a general solution, more fields are likely to appear in the future.
Another solution I'm thinking about is to handle form manually, without Form's.
Are there better solutions?
You can use nested mappings, e.g.
val bigForm = Form(
mapping(
"id" -> of[ObjectId],
"title" -> text,
"general" -> mapping(
...
)(GeneralInfo.apply)(GeneralInfo.unapply),
"advanced" -> mapping(
...
)(AdvancedInfo.apply)(AdvancedInfo.unapply)
)
)
Another possibility is using view objects and updating only the part that was submitted (e.g. via separate forms or AJAX):
val generalForm = Form(
mapping(
"title" -> text,
...
)
)
def updateGeneral(id: ObjectId) = Action { implicit request =>
MyObject.findById(id).map { myObj =>
generalForm.bindFromRequest.fold(
fail => BadRequest(...),
form => {
val newObj = myObj.copy(title = form.title, ...)
MyObject.save(newObj)
Ok(...)
}
)
}.getOrElse(NotFound)
}
In the play! framework, using scala,say that i have a form such as follows:
import play.api.data._
import play.api.data.Forms._
import play.api.data.validation.Constraints._
case class User(someStringField: String, someIntField: Int)
val userForm = Form(
mapping(
"someStringField" -> text,
"someIntField" -> number verifying(x => SomeMethodThatReceivesAnIntAndReturnsABoolean(x))
)(User.apply)(User.unapply)
)
where SomeMethodThatReceivesAnIntAndReturnsABoolean is a method that performs some logic on the int to validate it.
However, i would like to be able to consider the value of the someStringField when validating the someIntField, is there a way to achieve this in play framework's forms? I know that i can do something like:
val userForm = Form(
mapping(
"someStringField" -> text,
"someIntField" -> number
)(User.apply)(User.unapply)
.verifying(x => SomeFunctionThatReceivesAnUserAndReturnsABoolean(x))
and then i would have the entire user instance available passed to the validation function. The problem with that approach is that the resulting error would be associated with the entire form instead of being associated with the someIntField field.
Is there a way to get both things, validate a field using another field and maintain the error associated to the specific field i wish to validate, instead of the entire form?
I have the same requirements with adding validation to fields depending on the value of other fields. I´m not sure how this is done in idiomatic PLAY 2.2.1 but I came up with the following solution. In this usage I´m degrading the builtin "mapping" into a simple type converter and apply my "advanced inter field" validation in the "validateForm" method. The mapping:
val userForm = Form(
mapping(
"id" -> optional(longNumber),
"surename" -> text,
"forename" -> text,
"username" -> text,
"age" -> number
)(User.apply)(User.unapply)
)
private def validateForm(form:Form[User]) = {
if(form("username").value.get == "tom" || form("age").value.get == "38") {
form
.withError("forename", "tom - forename error")
.withError("surename", "tom - surename error")
}
else
form
}
def update = Action { implicit request =>
userForm.bindFromRequest.fold({
formWithErrors => BadRequest(users.edit(validateForm(formWithErrors)))
}, { user =>
val theForm = validateForm(userForm.fill(user))
if(theForm.hasErrors) {
BadRequest(users.edit(theForm))
} else {
Users.update(user)
Redirect(routes.UsersController.index).flashing("notice" -> s"${user.forename} updated!")
}
})
}
Even though it works I´m urgently searching for a more idiomatic version...
EDIT: Use a custom play.api.data.format.Formatter in idiomatic play, more on http://workwithplay.com/blog/2013/07/10/advanced-forms-techniques/ - this lets you programmatically add errors to a form. My Formatter looks like this:
val usernameFormatter = new Formatter[String] {
override def bind(key: String, data: Map[String, String]): Either[Seq[FormError], String] = {
// "data" lets you access all form data values
val age = data.get("age").get
val username = data.get("username").get
if(age == "66") {
Left(List(FormError("username", "invalid"), FormError("forename", "invalid")))
} else {
Right(username)
}
}
override def unbind(key: String, value: String): Map[String, String] = {
Map(key -> value)
}
}
}
Registered in the form mapping like this:
mapping(
[...]
"username" -> of(usernameFormatter),
[....]
I believe what you're looking for is play.api.data.validation.Constraint.
Say you have a RegisterForm with a list of predefined cities and an otherCity field and you need either the cities or otherCity to be supplied, i.e., otherCity should be validated if cities is not provided:
case class RegisterForm(
email: String,
password: String,
cities: Option[List[String]],
otherCity: Option[String]
)
You can write a custom Constraint around this:
val citiesCheckConstraint: Constraint[RegisterForm] = Constraint("constraints.citiescheck")({
registerForm =>
// you have access to all the fields in the form here and can
// write complex logic here
if (registerForm.cities.isDefined || registerForm.otherCity.isDefined) {
Valid
} else {
Invalid(Seq(ValidationError("City must be selected")))
}
})
And your form definition becomes:
val registerForm = Form(
mapping(
"email" -> nonEmptyText.verifying(emailCheckConstraint),
"password" -> nonEmptyText.verifying(passwordCheckConstraint),
"cities" -> optional(list(text)),
"other_city" -> optional(text)
)(RegisterForm.apply)(RegisterForm.unapply).verifying(citiesCheckConstraint)
)
In this example emailCheckConstraint and passwordCheckConstraint are additional custom constraints that I defined similar to citiesCheckConstraint. This works in Play 2.2.x.
UPDATE:
Works on Play 2.3.8 as well.
if you don't mind having a prefix for you params you can group the related params:
val aForm = Form(
mapping(
"prefix" -> tuple(
"someStringField" -> text,
"someIntField" -> number
) verifying (tup => your verification)
)(tup => User.apply(tup._1, tup._2)(User.unapply...)
I use something similar just without the surrounding mapping.
You will have to adjust the apply/unapply a little and pass the arguments manually for it to compile.
The error will be registered to the "prefix" group.
I also find it weird that you cannot register errors on any field you'd like using FormError when verifying the form...
Thanks to Tom Myer, Here what I used
class MatchConstraint[A](val targetField:String, val map:(String, Map[String, String]) => A, val unmap:A => String) extends Formatter[A] {
override def bind(key: String, data: Map[String, String]): Either[Seq[FormError], A] = {
val first = data.getOrElse(key, "")
val second = data.getOrElse(targetField, "")
if (first == "" || !first.equals(second)) {
Left(List(FormError(key, "Not Match!")))
}
else {
Right(map(key, data))
}
}
override def unbind(key: String, value: A): Map[String, String] = Map(key -> unmap(value))
}
And here what's my form look like
val registerForm = Form(
mapping(
"email" -> email.verifying(minLength(6)),
"password" -> text(minLength = 6),
"passwordConfirmation" -> of(new MatchConstraint[String]("password", (key, data) => data.getOrElse(key, ""), str => str))
)(RegisterData.apply)(RegisterData.unapply)
)
I guess that they map the scala-code to JSR-Validation. There it's definitely not possible. There are some arguments to do this. Mainly that a validation should be simple and not make complex logic. How ever I still miss this too. OVal from play1 was better for me.
In the documentation:
Playframework Documentation
You can see the following code:
val userFormConstraintsAdHoc = Form(
mapping(
"name" -> text,
"age" -> number
)(UserData.apply)(UserData.unapply) verifying("Failed form constraints!", fields => fields match {
case userData => validate(userData.name, userData.age).isDefined
})
)
Mainly just verify after the unapply and you have all the fields mapped, so you can make a more complete validation.