Play: Bind a form field to a double? - scala

Perhaps I'm just overlooking something obvious but I can't figure out how to bind a form field to a double in a Play controller.
For instance, assume this is my model:
case class SavingsGoal(
timeframeInMonths: Option[Int],
amount: Double,
name: String
)
(Ignore that I'm using a double for money, I know that's a bad idea, this is just a simplified example)
And I wanted to bind it like so:
object SavingsGoals extends Controller {
val savingsForm: Form[SavingsGoal] = Form(
mapping(
"timeframeInMonths" -> optional(number.verifying(min(0))),
"amount" -> of[Double],
"name" -> nonEmptyText
)(SavingsGoal.apply)(SavingsGoal.unapply)
)
}
I realized that the number helper only works for ints but I thought using of[] might allow me to bind a double. However, I get a compiler error on this:
Cannot find Formatter type class for Double. Perhaps you will need to import
play.api.data.format.Formats._
Doing so doesn't help as there's no double formatter defined in the API.
This is all just a long way of asking what's the canonical way of binding a form field to a double in Play?
Thanks!
edit: 4e6 pointed me in the right direction. Here's what I did using his help:
Using the snippets in his link, I added the following to app.controllers.Global.scala:
object Global {
/**
* Default formatter for the `Double` type.
*/
implicit def doubleFormat: Formatter[Double] = new Formatter[Double] {
override val format = Some("format.real", Nil)
def bind(key: String, data: Map[String, String]) =
parsing(_.toDouble, "error.real", Nil)(key, data)
def unbind(key: String, value: Double) = Map(key -> value.toString)
}
/**
* Helper for formatters binders
* #param parse Function parsing a String value into a T value, throwing an exception in case of failure
* #param error Error to set in case of parsing failure
* #param key Key name of the field to parse
* #param data Field data
*/
private def parsing[T](parse: String => T, errMsg: String, errArgs: Seq[Any])(key: String, data: Map[String, String]): Either[Seq[FormError], T] = {
stringFormat.bind(key, data).right.flatMap { s =>
util.control.Exception.allCatch[T]
.either(parse(s))
.left.map(e => Seq(FormError(key, errMsg, errArgs)))
}
}
}
Then, in my form mapping:
mapping(
"amount" -> of(Global.doubleFormat)
)

You don't need to use the the format in the global if you have the version 2.1 up.
Just import:
import play.api.data.format.Formats._
and use as:
mapping(
"amount" -> of(doubleFormat)
)

Actually, there is predefined formatter for Double on master branch. So you should either switch to 2.1-SNAPSHOT play version or just copy the implementation.

Related

What is the relation between tuple and mapping which can be given as argument into "object Form"

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

How to create anorm's parser with array?

I am using postgresql which supports array column field. To parse a row, I use this parser. It has error at the Array object. I guess I did it wrongly.
case class ServiceRequest(
id: Pk[Long],
firstname: String,
lastname: String,
images: Array[String])
val parser: RowParser[ServiceRequest] = {
get[Pk[Long]]("id") ~
get[String]("firstname") ~
get[String]("lastname") ~
Error here >>> get[Array[String]]("images") map {
case id ~ firstname ~ lastname ~ images=>
ServiceRequest(id, firstname, lastname, images)
}
}
Thanks
The Array[T] type is now natively supported in play 2.4.x, your don't have to roll your own converters.
But, it's still not nice to work with insert or update statements like:
def updateTags(id: Long, values: Seq[String]):Int = {
DB.withConnection { implicit conn =>
SQL("UPDATE entries SET tags = {value} WHERE id = {id}")
.on('value -> values, 'id -> id).executeUpdate
}
will give you an error
play - Cannot invoke the action, eventually got an error:
org.postgresql.util.PSQLException: ERROR: syntax error at or near "$2"
Put it simply, you should create the PreparedStatement using java.sql.Array type:
on('value -> conn.createArrayOf("varchar", value.asInstanceOf[Array[AnyRef]]), ...
As of now (play 2.4-M1), java.sql.Array is not converted to ParameterValue by default, which means
SQL(...).on(`arr -> values:java.sql.Array ) still gives a compilation error, you need another implicit conversion to make it compiling:
implicit object sqlArrayToStatement extends ToStatement[java.sql.Array] {
def set(s: PreparedStatement, i: Int, n: java.sql.Array) = s.setArray(i, n)
}
I have solved my problem by adding this converter:
implicit def rowToStringArray: Column[Array[String]] = Column.nonNull { (value, meta) =>
val MetaDataItem(qualified, nullable, clazz) = meta
value match {
case o: java.sql.Array => Right(o.getArray().asInstanceOf[Array[String]])
case _ => Left(TypeDoesNotMatch("Cannot convert " + value + ":" + value.asInstanceOf[AnyRef].getClass))
}
}
The converter transforms data type from postgresql's jdbc to java's data type for the parser in SELECT function. When you insert, you need another converter to convert from java's data type to postgresql's jdbc.
There is currently a pullrequest to add support for java.sql.Array as column mapping: https://github.com/playframework/playframework/pull/3062 .
Best,

scala play form how handle java bigdecimal

I try to fill a form with the values of System
def edit(id: Long) = IsAuthenticated { username => implicit request =>
User.findByEmail(username).map { user =>
System.findById(id).map { system =>
Ok(html.systems.editForm(id, systemForm.fill(system), Person.options, user))
}.getOrElse(NotFound)
}.getOrElse(Forbidden)
}
but some of 'system' values are java.math.BigDecimal
val systemForm = Form(
mapping(
"id" -> ignored(NotAssigned:Pk[BigDecimal]),
"sys_name" -> nonEmptyText,
"sys_desc" -> nonEmptyText,
"sys_owner1_id" -> longNumber,
"sys_owner2_id" -> optional(longNumber)
)(System.apply)(System.unapply)
)
and it says :
type mismatch; found : (anorm.Pk[java.math.BigDecimal], String, String, String, Option[String], java.math.BigDecimal, Option[java.math.BigDecimal]) => models.System required: (anorm.Pk[java.math.BigDecimal], String, String, Long, Option[Long]) => ?
how can i handle this?
Looks like problem in anorm or somewhere near.
As you may see from error description sys_owner1_id and sys_owner2_id are BigIntegers in query result, but declared as long in form.
I am not familiar with anorm, but solution is to declare these ids as long in anorm, or declare them as BigInteger in form mapping or convert from BigInteger to long in your query.

Play 2 - Scala - Forms Validators and radio buttons

I'm need render a Form with validators for this model:
Model:
case class Service (
name: String, description: String, unitcost: Long,
typo: Char, isactive: Char, modifiedby: String)
Controller:
import play.api.data.Form
import play.api.data._
import play.api.data.format.Formats._
import play.api.data.Forms._
object Services extends Controller {
....
....
private val servicesForm[Service] = Form(
mapping(
"name" -> nonEmptyText.verifying(
"validation.name.duplicate", Service.findByName(_).isEmpty),
"description" -> nonEmptyText,
"unitcost" -> longNumber,
"typo" -> of[Char],
"isactive" -> of[Char],
"modifiedby" -> nonEmptyText
) (Service.apply)(Service.unapply)
)
This code fails on every of[Char] saying that its needed import play.api.data.format.Formats._
but I was..
My second doubt is about how to put a radio button pair for each (typo and isactive)
thinking that typo has "M" and "A" like options and isactive has "Y" and "N".
PD: I think put this using a persistence model after...
The error indicates that the Form does not know how to handle the Char type. There is no default defined for the Char type.
To solve the problem you have two options:
Change the type from Char to String for which a default Formatter exists
Supply a Formatter for Char
A formatter would look something like this (note that it does not have the correct error handling)
implicit val charFormat = new Formatter[Char] {
def bind(key: String, data: Map[String, String]):Either[Seq[FormError], Char] =
data.get(key)
.filter(_.length == 1)
.map(_.head)
.toRight(Seq(FormError(key, "error.required", Nil)))
def unbind(key: String, value: Char) = Map(key -> value.toString)
}

Play! framework 2.0: Validate field in forms using other fields

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.