Scala Play convert a request to JSON and write it to MongoDB - scala

I'm using Play 2.5 and ReactiveMongo. It's a simple problem but I can't figure it out. I have a controller that receives a username and password from a view, I want to convert this request to a model of type UserModel and then convert this model to json and write it to MongoDB.
My controller:
class RegisterController #Inject() (val reactiveMongoApi: ReactiveMongoApi)
extends Controller with MongoController with ReactiveMongoComponents {
def registerPost = Action.async { request =>
implicit val accountWrites = new Writes[UserModel] {
def writes(account: UserModel) = Json.obj(
"username" -> account.username,
"password" -> account.password
)
} //needs this for some reason?
val future = collection.insert(request.body) //trying to insert to mongo
future.map(_ => Ok)
My model:
case class UserModel(username: String, password: String) {}
object UserModel {
implicit val format = Json.format[UserModel] //needs this for some reason?
val userModel = Form(
mapping(
"username" -> nonEmptyText,
"password" -> nonEmptyText
)(UserModel.apply)(UserModel.unapply))
}
My view:
#(userForm: Form[UserModel])(implicit messages: Messages)
<h1>Register</h1>
#helper.form(action = routes.RegisterController.registerPost()) {
#helper.inputText(userForm("username"))
#helper.inputText(userForm("password"))
<button type="submit" name="action" value="register">Register</button>
}

These are the key things you need to do:
=> Bind the form request in your controller method , registerPost . For this, you also need to have the form mapping settings. This mapping will help you to generate UserModel object using the form data inside the method. For doing all this, see here ScalaForms
=> You dont need writes here. You can use format to perform both json writes and reads.
=> Now convert the UserModel into JsValue as:
//usermodel will be generated by binding the form data
implicit val format = Json.format[UserModel]
val userModelJson = format.writes(usermodel)
=> Then you can simply call the repository as:
collection.insert(userModelJson)

Related

how to apply Form validators on object

I have an API in Scala. When I want to create a new User, i need validators on some fields (UserName, FirstName, LastName). I can't find a solution to apply the Form on my case class User. I thought using (User.apply) this will override automatically my User class, but this isn't happening.
User.scala
case class User(
id: Int = 0,
userName: String,
firstName: String,
lastName: String
)
object User {
import play.api.libs.json._
implicit val jsonWrites = Json.writes[User]
implicit val userReads = Json.reads[User]
def tupled = (User.apply _).tupled
}
// Form validator for post requests
object UserForm {
val form: Form[User] = Form(
mapping(
"id" -> number,
"userName" -> text(minLength = 5),
"firstName" -> text(minLength = 5),
"lastName" -> nonEmptyText
)(User.apply)(User.unapply)
)
}
UsersController.scala
def create = Action(parse.json) { implicit request => {
val userFromJson = Json.fromJson[User](request.body)
userFromJson match {
case JsSuccess(user: User, path: JsPath) => {
var createdUser = Await.result(usersRepository.create(user), Duration.Inf)
Ok(Json.toJson(createdUser))
}
case error # JsError(_) => {
println(error)
InternalServerError(Json.toJson("Can not create user"))
}
}
}
}
UsersRepository.scala
def create(user: User): Future[User] = {
val insertQuery = dbUsers returning dbUsers.map(_.id) into ((x, id) => x.copy(id = id))
val query = insertQuery += user
db.run(query)
}
(UsersRepository doesn't matter so much in this problem)
So, when I read the json, I think there need to apply the form validators
val userFromJson = Json.fromJson[User](request.body)
but nothing happening. Another problem is if I send from client a null parameter, will get an error, can not read and create an User.
For example :
"firstName": "" - empty userName will pass
"firstName": null - will fail
This depends on how is configured the User class in client (Angular), with the field null or empty in constructor. I prefer to set it null, even if is a string, if is not a required field (better NULL value in database, than an empty cell)
Try to use this:
request.body.validate[User]
One other thing, you shouldn't use
var createdUser = Await.result(usersRepository.create(user), Duration.Inf)
So use val´s over var´s and the other thing, you don't need to force the future to resolve, use it like this:
val createdUser = usersRepository.create(user)
createdUser.map(Ok(Json.toJson(_)))
And your action should be async.
Find a solution, my Form need to be part of object User, and not to be another object.
object User {
import play.api.libs.json._
implicit val jsonWrites = Json.writes[User]
implicit val userReads = Json.reads[User]
val form: Form[User] = Form(
mapping(
"id" -> number,
"userName" -> text(minLength = 2),
"firstName" -> text(minLength = 4),
"lastName" -> nonEmptyText
)(User.apply)(User.unapply)
)
def tupled = (User.apply _).tupled
}
and the method in Controller became :
def create = Action(parse.json) { implicit request => {
User.form.bindFromRequest.fold(
formWithErrors => BadRequest(Json.toJson("Some form fields are not validated.")),
success => {
val userFromJson = Json.fromJson[User](request.body)
userFromJson match {
case JsSuccess(user: User, path: JsPath) => {
val createdUser = Await.result(usersRepository.create(user), Duration.Inf)
Ok(Json.toJson(createdUser))
}
case error#JsError(_) => {
println(error)
InternalServerError(Json.toJson("Can not create user"))
}
}
}
)
}
}
I know, I need to format my code ... using IntelliJ is so annoying after Visual Studio :(

type mismatch; found : Int required: String when trying to persist the form data in play framework

I have a model as follows:
case class User(username: String, email: String) {
var id:Int = User.nextId
}
object User {
protected var currentId = 0
def nextId: Int = {
currentId += 1
currentId
}
}
And my Controller looks like this:
object Users extends Controller {
val users: scala.collection.mutable.Map[String, User] = new HashMap
val form = Form(
mapping(
"username" -> text,
"email" -> text
)(User.apply)(User.unapply))
def add = Action {
Ok(views.html.users.add(form))
}
def save = Action{
implicit request =>
val user = form.bindFromRequest.get
users.put(user.id, user)
Ok("contact saved")
}
}
But when I compile the code my browser throws an error telling there is a miss match in the type that is accepted by the users.put(user.id, user).
The error message looks as follows:
When I change the type of the id as string then it will work but I want to keep my id as integer and not string.
Is there any way I can achieve this. I am using play framework 2.11.1.
What is that I am missing. Any help would be greatly appreciated since I am new to play framework.
users is a map from String to User.
val users: scala.collection.mutable.Map[String, User] = new HashMap
The put method expects a key of type String and a value of type User, but you tried to use it with a key of type Int.
You need to change your map to the type
scala.collection.mutable.Map[Int, User]
or instead you could use a value of type String as key.

How to init case class from one JSON string instead of separate fields of form

New to the Play framework.
After reading the doc, I know the standard way to initialize case class by using form like this:
import play.api.data._
import play.api.data.format.Formats._
val userForm = Form(
mapping(
"name" -> of[String],
"age" -> of[Int],
"email" -> of[String]
)(User.apply)(User.unapply)
)
in this case, we provide separate textboxes for user to fill in in the web interface, after submit, the server code will init User according to the form. Works Great!
but, what if what I wanna provide is only one textbox, and user(actually the mobile guy who wanna test the web service API) should fill in a complete JSON string and then post it to server to create a User instance?
Would appreciate if you can provide both the view & controller code.
Taking your question literally (creating an object via JSON from a form field), it could look something like below. For completeness I've also provided the direct (and much simpler) way to unmarshall an object from JSON in a controller action (using content type application/json instead of application/x-form-urlencoded).
To extract a custom type (in your case a User object) from a form field you need to write a mapper to bind/unbind the data to/from a string. The userJsonMapper in the User's companion object does this, giving an error (error.jsonObj, for which you can provide an i18n translation) if JSON parsing fails.
The model code app/models/User.scala:
package models
case class User(name: String, age: Int, email: String)
object User {
// Create a generic JSON format using the macro. NB: This
// does *NOT* include validation of the email or age fields.
import play.api.libs.json.{Format, Json}
implicit val format: Format[User] = Json.format[User]
// Create a form and a field binder that validates the contents
// of the "json" field to ensure it is valid JSON for a user.
import play.api.data.Form
import play.api.data.Forms._
import play.api.data.FormError
import play.api.data.format.Formatter
// The actual form object
val jsonForm: Form[User] = Form(
single("json" -> of(userJsonMapper))
)
// Map a User object to/from a form field
def userJsonMapper: Formatter[User] = new Formatter[User] {
def bind(key: String, data: Map[String, String]) = {
play.api.data.format.Formats.stringFormat.bind(key, data).right.flatMap { s =>
scala.util.control.Exception.allCatch[User]
.either(Json.parse(s).as[User])
.left.map(e => Seq(FormError(key, "error.jsonObj", Nil)))
}
}
def unbind(key: String, value: User) = Map(key -> Json.stringify(Json.toJson(value)))
}
}
The template code app/views/createUserForm.scala.html:
#(form: Form[User], action: Call)(implicit request: RequestHeader)
<html>
<body>
#helper.form(action = action, 'role -> "form") {
#helper.inputText(form("json"))
<button type="submit">Create User</button>
}
</body>
</html>
The route file conf/routes:
GET /createUser controllers.Users.createUser
POST /createUser controllers.Users.createUserPost
# For good measure, include an action to create
# a user directly from JSON via a JSON content-type.
POST /createUserJson controllers.Users.createUserPostJson
The controller code app/controllers/Users.scala:
package controllers
import play.api.mvc._
import models.User
object Users extends Controller {
def createUser = Action { implicit request =>
Ok(views.html.createUserForm(
User.jsonForm,
controllers.routes.Users.createUserPost()
))
}
def createUserPost = Action { implicit request =>
User.jsonForm.bindFromRequest.fold(
// Form data is bad - re-render form.
errForm => BadRequest(views.html.createUserForm(
errForm,
controllers.routes.Users.createUserPost()
)),
user =>
// Create user here
Ok(s"User: $user")
)
}
}
// A more direct way of doing this is to POST the data
// as application/json and unmarshall it directly.
def createUserPostJson = Action(parse.json) { implicit request =>
request.body.validate[User].fold(
errs => BadRequest(play.api.libs.json.JsError.toFlatJson(errs)),
user => Ok(s"User: $user")
)
}
After reading this thread: Scala Play form validation: different forms for one case class - is it possible?, figured out a way to do what I want.
The canonical way of extracting fields from form to create an instance is like this:
import play.api.data._
import play.api.data.format.Formats._
val userForm = Form(
mapping(
"name" -> of[String],
"des" -> of[String],
...
)(User.apply)(User.unapply)
)
here we create a form and then bind the target object's apply and unapply methods to it, thanks to these methods, the form now has the ability to extract fields and turn them into Object.
What I wanna do is a form which can turn a JSON string(instead of separate fields) into an object, then why not provide the form with functions like this:
json:String => User
yes, additional to User's apply&unapply function, we add:
def applyStr(jsonStr: String): User = {
val json: JsValue = Json.parse(jsonStr)
val validateResult: JsResult[User] = json.validate[User](UserReads)
validateResult match{
case s: JsSuccess[User] => s.get
case e: JsError => null
}
}
def unapplyToStr(user: User): Option[String] = {
Some(Json.toJson(user).toString())
}
and create a form like this:
val userJSONForm = Form(
mapping(
"json" -> text
)(User.applyStr)(User.unapplyToStr)
)
now we have a form which can turn json string into User, what left is familiar, we create an action in controller to display form:
def createByJson() = Action {
Ok(views.html.user_json(userJSONForm))
}
the corresponding view is like:
#(userJsonForm: Form[User])
#helper.form(action = routes.UserController.saveJson()) {
#helper.textarea(userJsonForm("json"))
<input type="submit" />
}
after submit, the form will be delivered to UserController.saveJson():
def saveJson() = Action{implicit request =>
userJSONForm.bindFromRequest.fold(
formWithErrors => {
BadRequest(views.html.user_json(formWithErrors))
},
user => {
//do sth to the user
...
Ok("get user")
}
)
}

Play! Form that selects an item from a separate mongo collection

So for a system I am developing I am trying to do something similar to this:
If I have a Model called User, that has an _id (ObjectId), username, password, and then I am trying to create a new appointment, my form would look for a patient (display the patient name in the dropdown but really would pick up the patient's ObjectId), and appointment time.
Now I've looked everywhere and can't find anything remotely close to the solution i'm trying to attain.
In Application.scala, I have:
val appointmentForm= Form(
tuple(
"patient" -> nonEmptyText, // ObjectId
"startTime" -> nonEmptyText))
I am not sure how to quite work my view in order to reflect the patient. I know you have to do something like this:
#select(appointmentForm("patient"), options(..)
Can anyone give me any ideas as to how I can look up the patients for this example to pick up a Mongo ObjectId.
The ORM I am using btw is https://github.com/leon/play-salat
here is an example how I would do it:
routes:
GET /test controllers.Test.show
POST /test controllers.Test.submit
view:
#(f: Form[(ObjectId, String)], users: Seq[(ObjectId, String)])
#import helper._
#form(action = routes.Test.submit) {
#select(f("patient"), options = users.map(user => (user._1.toString, user._2)))
#inputText(f("startTime"))
<input type="submit" value="Submit!">
}
controller:
package controllers
import org.bson.types.ObjectId
import play.api.data.format.Formatter
import play.api.mvc._
import play.api.data.Forms._
import play.api.data._
import play.api.data.FormError
import play.api.Logger
object Test extends Controller {
/**
* Converts an ObjectId to a String and vice versa
*/
implicit object ObjectIdFormatter extends Formatter[ObjectId] {
def bind(key: String, data: Map[String, String]) = {
val error = FormError(key, "error.required.ObjectId", Nil)
val s = Seq(error)
val k = data.get(key)
k.toRight(s).right.flatMap {
case str: String if (str.length() > 0) => Right(new ObjectId(str))
case _ => Left(s)
}
}
def unbind(key: String, value: ObjectId) = Map(key -> value.toStringMongod())
val objectId: Mapping[ObjectId] = of[ObjectId]
}
// import to get objectId into scope
import ObjectIdFormatter._
// define user tuples consisting of username and ObjectId for the dropdown. In real lif the list is probably fetched from the db
def users: Seq[(ObjectId, String)] =
Seq((new ObjectId("4f456bf744aed129d04db1bd"), "dieter"), (new ObjectId("4faa410b44aec5a0a980599f"), "eva"))
val appointmentForm= Form(
tuple(
"patient" -> objectId, // use the ObjectIdFormatter
"startTime" -> nonEmptyText))
def show = Action {
Ok(views.html.test(appointmentForm, users))
}
def submit = Action { implicit request =>
appointmentForm.bindFromRequest.fold(
formWithErrors => {
Logger.warn("errors: " + formWithErrors.errors)
BadRequest(views.html.test(formWithErrors, users))
},
formContent => {
Logger.info("formContent: " + formContent)
Ok(views.html.test(appointmentForm, users))
})
}
}
Fyi, I was able to finally solve the problem after seeing this wonderful comment by maxmc. It turns out my problem was really a fundamental scala issue. I did not realize that List is an implementation of Seq. so using Mongo, in this example, what you have to do is in your code is the following:
CONTROLLER
def newAppointment= Action {
val pList = Patient.findAll.toList
Ok(views.html.admin.newuser(appointmentForm, pList))
}
View:
#(appointmentForm: Form[(String, String, String)], pList : List[Patient])
...
...
...
#select(
appointmentForm("patient"),
pList.map{ p =>
p.id.toString -> (p.patientName)
},
'_default -> "--- Select a Patient ---",
'_label -> "Patient"
)
the Model.findAll function returns Iterator[Type]. We don't want that. We need to retrieve a list that we can traverse inside the view. That's why you do findAll.toList . From there, the #select will map the pList and for each entry from the database have the id be associated to the patient name. Note that it is a string, because the Seq for the #Select tag requires Seq(String, String)

How do I access post data from scala play?

I have a route that is type "POST". I am sending post data to the page. How do I access that post data. For example, in PHP you use $_POST
How do I access the post data in scala and play framework?
As of Play 2.1, there are two ways to get at POST parameters:
1) Declaring the body as form-urlencoded via an Action parser parameter, in which case the request.body is automatically converted into a Map[String, Seq[String]]:
def test = Action(parse.tolerantFormUrlEncoded) { request =>
val paramVal = request.body.get("param").map(_.head)
}
2) By calling request.body.asFormUrlEncoded to get the Map[String, Seq[String]]:
def test = Action { request =>
val paramVal = request.body.asFormUrlEncoded.get("param").map(_.head)
}
Here you've got good sample how it's done in Play:
https://github.com/playframework/Play20/blob/master/samples/scala/zentasks/app/controllers/Application.scala
val loginForm = Form(
tuple(
"email" -> text,
"password" -> text
) verifying ("Invalid email or password", result => result match {
case (email, password) => User.authenticate(email, password).isDefined
})
)
/**
* Handle login form submission.
*/
def authenticate = Action { implicit request =>
loginForm.bindFromRequest.fold(
formWithErrors => BadRequest(html.login(formWithErrors)),
user => Redirect(routes.Projects.index).withSession("email" -> user._1)
)
}
It's described in the documentation of the forms submission
as #Marcus points out, bindFromRequest is the preferred approach. For simple one-off cases, however, a field
<input name="foo" type="text" value="1">
can be accessed via post'd form like so
val test = Action { implicit request =>
val maybeFoo = request.body.get("foo") // returns an Option[String]
maybeFoo map {_.toInt} getOrElse 0
}
Here you've got good sample how it's done in Play 2:
def test = Action(parse.tolerantFormUrlEncoded) { request =>
val paramVal = request.body.get("param").map(_.head).getorElse("");
}