Form mapping that handles optional fields with custom validation - scala

My form looks like:
case class RegistrationForm(department: Option[String], name: String, email: String, employeeId: Option[Int])
Now what I need to do is, if the user entered the department input, then employeeId should be None. And if they left the department empty, then employeeId will be a required field.
My mapping is currently this but it doesn't handle this logic:
val registrationForm = Form(
mapping(
"department" -> optional(String)
"name" -> nonEmptyText,
"email" -> nonEmptyText,
"employeeId" -> optional(number)
)(RegistrationForm.apply)(RegistrationForm.unapply)
)
Also, in my form, how do I create a hidden input field and bind it to my form property, because sometimes my url will be like:
/users/register?employeeId=293838
So I want to create:
<input type="hidden" name="employeeId" value="???" />
So this employeeId hidden input should be bound to the form.

You can use verifying to build constraints between form fields after they've all successfully been bound to the case class.
val registrationForm = Form(
mapping(
"department" -> optional(String)
"name" -> nonEmptyText,
"email" -> nonEmptyText,
"employeeId" -> optional(number)
)(RegistrationForm.apply)(RegistrationForm.unapply)
.verifying("Some error message..", reg =>
(reg.department.isEmpty || reg.employeeId.isEmpty) && (reg.department.nonEmpty || reg.employeeId.nonEmpty)
)
)
The first argument of verifying is an error message to use when the constraint is broken, and the second is a function of the case class you're binding to that returns Boolean. If the function returns false, the Form will contain a global error. I'm not 100% sure that's the logic you're going for in my example, as your wording is a bit strange (sounds like you're describing exclusive OR). You can also chain multiple verifying operations together if needed.
I'm not sure what your question about hidden fields is asking. A hidden field within the html form will be sent to the server with everything else, and bind just as any other field will.

Related

Form binding <select multiple> to List in Play framework

I have a ticketing form that simplified contains a "subject" text field, a "content" textarea and a "to" multiple select.
I expected a multiple select to be able to bind to list(uuid) in Play's form mapping, but that is not the case.
request.body.asFormUrlEncoded("to") returns a Vector of several elements but .to in my mapping is empty. Looking at the source code it seems that Play requires me to put the "to" elements in to[0], to[1] etc. But for a multiple select that would be very inconvenient. Is there a better way?
case class CreateTicketFormModel(subject: String, content: String, to: List[UUID])
def CreateTicketForm = Form(mapping(
"subject" -> nonEmptyText,
"content" -> nonEmptyText,
"to" -> list(uuid)
)(CreateTicketFormModel.apply)(CreateTicketFormModel.unapply))

Scala Play Forms verify that one date occurs after another

My form has two dates, but I want to validate that the second date occurs after the first date. Is there a concise way to do this using Play's Form verification?
Here is the form I am working with:
val policyForm: Form[PolicyForm] = Form(mapping(
"amount" -> of[Double].verifying("You need to enter in the amount of bitcoins", _ > 0),
"beginDate" -> date("yyyy-MM-dd").verifying("You cannot have a start date before today", dayLater _ ),
"endDate" -> date("yyyy-MM-dd").verifying("End date has be after the start date", { d =>
???
})
)(PolicyForm.apply _)(PolicyForm.unapply _))
You can't reference other form fields within the same mapping until after they all successfully bind individually. That is, the constraint must be made on the outer mapping.
val policyForm: Form[PolicyForm] = Form {
mapping(
"amount" -> of[Double].verifying(...),
"beginDate" -> date("yyyy-MM-dd").verifying(...),
"endDate" -> date("yyyy-MM-dd")
)(PolicyForm.apply _)(PolicyForm.unapply _).verifying("End date has be after the start date", policyForm =>
policyForm.beginDate.before(policyForm.endDate)
)
}
This assumes java.util.Date, so replace with your own logic if you're using joda time, or something else.

Retrieving error status from Haskell MongoDB driver

I have a MongoDB with one collection user that contains a unique index on email:
import Data.Bson (Value (Int32))
import Database.MongoDB (Index (..), createIndex, (=:))
createIndex $ Index "user" [ "email" =: Int32 1 ] "email" True False
I have written a function that inserts a new user if the email address it is not already in use, and should fail if the email address is already taken:
import Data.Bson (Value (ObjId))
import Database.MongoDB (Action, ObjectId, (=:))
import qualified Database.MongoDB as M (insert)
data User = User
{ email :: Text
, firstName :: Text
, lastName :: Text
} deriving Show
data MongoEntity a = MongoEntity ObjectId a
createIfNotExists :: User
-> Action Handler (Either Text (MongoEntity User))
createIfNotExists (user#User {..}) = do
value <- M.insert "user" [ "email" =: email
, "firstName" =: firstName
, "lastName" =: lastName
]
case value of
ObjId objectId -> return (Right $ MongoEntity objectId user)
_ -> return $ Left "no document"
I would like to detect any errors throw by the M.insert (such as a duplicate key) and return an
error Text message.
Since the M.insert runs in the Action monad, I assume it sets an error status if it fails, but I can't figure out how to retrieve it. I assume that I need something like
error <- getErrorStatus -- or whatever it is called
immediately after the value <- M.insert ... line and then test the error in the case ... of expression.
By the way, I tried writing createIfNotExists using findAndModify but I couldn't capture a failure with that either.
I've never used this module, but reading the documentation, the best I could think of is :
Change the type for createIfNotExists
createIfNotExists :: MonadIO m =>
User -> Action m (Either String (MongoEntity User))
Use catchJust from Control.Exception.Base. The author seems to discourage the usage of Control.Monad.Error.Class.
main :: IO ()
main = do
pipe <- connect (host "127.0.0.1")
let user = User "jd#bar.baz" "John" "Doe"
eitherEntity <- catchJust writeFailureErrorStr
(access pipe master "user" $ createIfNotExists user)
(return . Left)
close pipe
writeFailureErrorStr :: Failure -> Maybe String
writeFailureErrorStr (WriteFailure _err str) = Just str
writeFailureErrorStr _other = Nothing
You probably want to check the _err code value too, since there could be other reasons for WriteFailure.
My understanding of the documentation is that it is not possible to handle errors out of IO.
I'm so confused by this way of doing that I think I'm probably wrong. Please show me a more elegant way if there is one.
More generally, I think that checking existence by try and fail is not very nice. I'd rather do a find request and insert if no result.
From Jean-Baptiste Potonnier's answer and after reading two articles by Michael Snoyman (http://www.yesodweb.com/blog/2014/06/exceptions-transformers and https://www.fpcomplete.com/user/snoyberg/general-haskell/exceptions/exceptions-and-monad-transformers), I rewrote my code as follows:
import Control.Exception.Lifted (handleJust)
import Data.Bson (Value (ObjId))
import qualified Data.Bson as B (Value)
import qualified Data.Text as T (pack)
import Database.MongoDB (Action, Failure (..), (=:))
import qualified Database.MongoDB as M (insert)
createIfNotExists :: User -> Action Handler (Either Failure (MongoEntity User))
createIfNotExists user =
handleJust writeFailureHandler (return . Left) $ do
value <- insertUser user
return $
case value of
ObjId objectId -> Right $ MongoEntity (T.pack $ show objectId) user
_ -> error "Unexpected missing document"
where
writeFailureHandler :: Failure -> Maybe Failure
writeFailureHandler writeFailure#WriteFailure{} = Just writeFailure
writeFailureHandler _ = Nothing
insertUser :: User -> Action Handler B.Value
insertUser User{..} =
M.insert "users" [ "email" =: email
, "firstName" =: firstName
, "lastName" =: lastName
]
using the lifted-base package.

Dependent validation for form mapping in play framework 2

Can I create mapping for form, where first validation depend from second value?
val orderForm = Form(
mapping(
"requiredDelivery" -> boolean,
"deliveryAddress" -> text,
"comment" -> text)
(Order.apply)(Order.unapply)
)
I want to check deliveryAddress for nonEmptyText if requiredDelivery is true
Try:
val orderForm = Form(
mapping(
"requiredDelivery" -> boolean,
"deliveryAddress" -> text,
"comment" -> text)
(Order.apply)(Order.unapply) verifying("Address is required!", fields => fields match {
case order => (order.requiredDelivery && order.deliveryAddress!="") || !order.requiredDelivery
})
)
Any form validation error that is a result from the "verifying" clause after the mapping definition goes into the "global errors". The form helpers won't display these errors, so any typical template will come up blank but not able to successfully submit the form.
The fix is to add something like this to your form template:
#if(userForm.hasGlobalErrors) {
<ul>
#for(error <- userForm.globalErrors) {
<li>#error.message</li>
}
</ul>
}
You can learn more by looking at this page and skipping to the "Display errors in a view template" section - http://www.playframework.com/documentation/2.3-SNAPSHOT/ScalaForms

How do I implement User validations in Play!?

I have a User model, the scheme for which looks like this:
# --- First database schema
# --- !Ups
create sequence s_user_id;
create table user (
id bigint DEFAULT nextval('s_user_id'),
firstName varchar(128),
lastName varchar(128),
email varchar(128) unique
);
# --- !Downs
drop table user;
drop sequence s_user_id;
How can I validate the new users instead of just crashing with a
RuntimeException: Exception while executing statement : Unique index
or primary key violation:
?
Also, I'm not using any forms or views of any kind. I'm just creating an API...
You can use the forms to define and trigger your validation rules.
Note that you can use forms without UI, the data can became from what you want (WS, code, html ...)
For exemple :
case class User(name: String, age: Int)
val userForm = Form(
mapping(
"name" -> text.verifying(required),
"age" -> number.verifying(min(0), max(100))
) verifying("Your custom validation", fields => fields match {
case (n, a) => User.myCustomValidation(n,a)
}) (User.apply)(User.unapply)
)
val filledForm = userForm.fill(User("Bob", 18))
if (filledForm.hasErrors) {
// or filledForm.fold
}
See the ScalaForms documentation for more details, or a more complex exemple.