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

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)

Related

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

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)

Persisting a recursive data model with SORM

For my project, I would like to make a tree model; let's say it's about files and directories. But files can be in multiple directories at the same time, so more like the same way you add tags to email in gmail.
I want to build a model for competences (say java, scala, angular, etc) and put them in categories. In this case java and scala are languages, agila and scrum are ways of working, angular is a framework / toolkit and so forth. But then we want to group stuff flexibly, ie play, java and scala are in a 'backend' category and angular, jquery, etc are in a frontend category.
I figured I would have a table competences like so:
case class Competence (name: String, categories: Option[Category])
and the categories as follows:
case class Category ( name: String, parent: Option[Category] )
This will compile, but SORM will generate an error (from activator console):
scala> import models.DB
import models.DB
scala> import models.Category
import models.Category
scala> import models.Competence
import models.Competence
scala> val cat1 = new Category ( "A", None )
cat1: models.Category = Category(A,None)
scala> val sav1 = DB.save ( cat1 )
sorm.Instance$ValidationException: Entity 'models.Category' recurses at 'models.Category'
at sorm.Instance$Initialization$$anonfun$2.apply(Instance.scala:216)
at sorm.Instance$Initialization$$anonfun$2.apply(Instance.scala:216)
at scala.Option.map(Option.scala:146)
at sorm.Instance$Initialization.<init>(Instance.scala:216)
at sorm.Instance.<init>(Instance.scala:38)
at models.DB$.<init>(DB.scala:5)
at models.DB$.<clinit>(DB.scala)
... 42 elided
Although I want the beautiful simplicity of sorm, will I need to switch to Slick for my project to implement this? I had the idea that link tables would be implicitly generated by sorm. Or could I simply work around the problem by making a:
case class Taxonomy ( child: Category, parent: Category )
and then do parsing / formatting work on the JS side? It seems to make the simplicity of using sorm disappear somewhat.
To give some idea, what I want is to make a ajaxy page where a user can add new competences in a list on the left, and then link/unlink them to whatever category tag in the tree he likes.
I encountered the same question. I needed to define an interation between two operant, which can be chained(recursive). Like:
case class InteractionModel(
val leftOperantId: Int,
val operation: String ,
val rightOperantId: Int,
val next: InteractionModel)
My working around: change this case class into Json(String) and persist it as String, when retreiving it, convert it from Json. And since it's String, do not register it as sorm Entity.
import spray.json._
case class InteractionModel(
val leftOperantId: Int,
val operation: String ,
val rightOperantId: Int,
val next: InteractionModel) extends Jsonable {
def toJSON: String = {
val js = this.toJson(InteractionModel.MyJsonProtocol.ImJsonFormat)
js.compactPrint
}
}
//define protocol
object InteractionModel {
def fromJSON(in: String): InteractionModel = {
in.parseJson.convertTo[InteractionModel](InteractionModel.MyJsonProtocol.ImJsonFormat)
}
val none = new InteractionModel((-1), "", (-1), null) {
override def toJSON = "{}"
}
object MyJsonProtocol extends DefaultJsonProtocol {
implicit object ImJsonFormat extends RootJsonFormat[InteractionModel] {
def write(im: InteractionModel) = {
def recWrite(i: InteractionModel): JsObject = {
val next = i.next match {
case null => JsNull
case iNext => recWrite(i.next)
}
JsObject(
"leftOperantId" -> JsNumber(i.leftOperantId),
"operation" -> JsString(i.operation.toString),
"rightOperantId" -> JsNumber(i.rightOperantId),
"next" -> next)
}
recWrite(im)
}
def read(value: JsValue) = {
def recRead(v: JsValue): InteractionModel = {
v.asJsObject.getFields("leftOperantId", "operation", "rightOperantId", "next") match {
case Seq(JsNumber(left), JsString(operation), JsNumber(right), nextJs) =>
val next = nextJs match {
case JsNull => null
case js => recRead(js)
}
InteractionModel(left.toInt, operation, right.toInt, next)
case s => InteractionModel.none
}
}
recRead(value)
}
}
}
}

Play Framework:Error type mismatch; found :Int required: play.api.mvc.Result

I am trying to save form values to a database, and play I am getting the error:
type mismatch; found :Int required: play.api.mvc.Result
And here is my code :
Application.scala
import play.api._
import play.api.mvc._
import play.api.data._
import play.api.data.Forms._
import views.html.defaultpages.badRequest
import play.api.data.validation.Constraints._
import models.User
import models.UserData
object Application extends Controller {
val RegisterForm = Form(
mapping(
"fname" -> nonEmptyText(1, 20),
"lname" -> nonEmptyText(1, 20),
"email" -> email,
"userName" -> nonEmptyText(1, 20),
"password" -> nonEmptyText(1, 20),
"age" -> number,
"choice" -> text,
"gender" -> text
)(User.apply)(User.unapply)
.verifying("Ag should be greater then or equal to 18", model => model.age match {
case (age) => age >= 18
})
)
def index = Action {
Ok(views.html.index(RegisterForm))
}
def register = Action { implicit request =>
RegisterForm.bindFromRequest().fold(
hasErrors => BadRequest(views.html.index(hasErrors)),
success => {
User.save(success)
}
)
}
}
User.scala
import anorm._
import play.api.db.DB
import anorm.SqlParser._
import play.api.Play.current
case class User (
fname: String,
lname: String,
email: String,
userName: String,
password: String,
age: Int,
choice: String,
gender: String
)
object User {
val userinfo = {
get[String]("fname") ~
get[String]("lname") ~
get[String]("email") ~
get[String]("userName") ~
get[String]("password") ~
get[Int]("age") ~
get[String]("choice") ~
get[String]("gender") map {
case fname~lname~email~userName~password~age~choice~gender =>
User(fname, lname, email, userName, password, age, choice, gender)
}
}
def save(ud: User) = {
DB.withConnection { implicit c =>
SQL ("insert into userinfo(fname,lname,email,userName,password,age,choice,gender) values ({fname},{lname},{email},{userName},{password},{age},{choice},{gender})")
.on('fname -> ud.fname, 'lname ->ud.lname ,'email ->ud.email, 'userName->ud.userName , 'password->ud.password ,'age->ud.age, 'choice->ud.choice,'gender->ud.gender)
.executeUpdate()
}
}
}
i took help from the scala cook book for this save functions but i am having problem as i am new in play scala maybe the mistake was minor one so please help me,all i want is to store all the input values in DB which was entered in the form
The problem is with your controller function register:
def register = Action { implicit request =>
RegisterForm.bindFromRequest().fold(
hasErrors => BadRequest(views.html.index(hasErrors)),
success => {
// You need to return a `Result` here..
// Maybe redirect to login?
// Redirect("/login")
User.save(success)
}
)
}
The problem is that User.save returns an Int, and the controller function is expecting a Result, like Ok(..), Redirect(...), etc. You need to do something with the result of User.save to return a Result.
It would be better if you used executeInsert() instead of executeUpdate(), as executeInsert returns Option[Long] by default--which will help you determine if the save was successful.
e.g.:
def save(ud: User): Option[Long] = {
DB.withConnection {implicit c =>
SQL("insert into userinfo(fname,lname,email,userName,password,age,choice,gender) values ({fname},{lname},{email},{userName},{password},{age},{choice},{gender})")
.on('fname -> ud.fname, 'lname ->ud.lname ,'email ->ud.email, 'userName->ud.userName , 'password->ud.password ,'age->ud.age, 'choice->ud.choice,'gender->ud.gender)
.executeInsert()
}
}
Then within your controller function use it like this:
def register = Action { implicit request =>
RegisterForm.bindFromRequest().fold(
hasErrors => BadRequest(views.html.index(hasErrors)),
success => {
User.save(success) map { newId =>
Redirect("/login") // or whatever you want this to do on success
} getOrElse {
InternalServerError("Something has gone terribly wrong...")
}
}
)
}
You can replace my placeholder results with something a little more meaningful in your application.
The save(ud:User) method returns the integer value. Action should always return Result, so write
success => {
val result = User.save(success)
Ok(result.toString)
}

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")
}
)
}

Form binding with custom mapping to object - how?

I have these case class
case class Blog(id:Long, author:User, other stuff...)
case class Comment(id:Long, blog:Blog, comment:String)
and a form on the client side that submits the data
blog_id:"5"
comment:"wasssup"
I'm writing some simple code to let a user add a comment to a blog.
The user is logged in so the his user_id is not needed from the client side, we know who he is...
I would like to bind the blog_id to a Blog object loaded from db, and if it doesn't exist show an error.
The examples on play framework docs are not helpful.
They only show mappings for forms that represent a single Object and all of its fields.
Here I'm representing a tuple of a (b:Blog, comment:String) and for the Blog I'm only supplying it's id.
I'd like to have a mapping that would provide me with the conversion + validation + error messages, so i can write something like:
val form = Form(
tuple(
"blog_id" -> blogMapping,
"comment" -> nonEmptyText
)
)
form.bindFromRequest().fold(...
formWithErrors => {...
}, {
case (blog, comment) => {do some db stuff to create the comment}
...
The "blogMapping" wlil work like other mappings, it will bind the posted data to an object, in our case a blog loaded from db, and in case it's not successful it will provide an error that we can use on the formWithErrors => clause.
I'm not sure how to accomplish this, the docs are kinda lacking here...
any help is appreciated!
I ended up looking at how playframwork's current bindings look like and implementing something similar, but for Blog:
implicit def blogFromLongFormat: Formatter[Blog] = new Formatter[Blog] {
override val format = Some(("Blog does not exist", Nil))
def bind(key: String, data: Map[String, String]) = {
scala.util.control.Exception.allCatch[Long] either {
data.get(key).map(s => {
val blog_id = s.toLong
val blog = Daos.blogDao.retrieve(blog_id)
blog.map(Right(_)).getOrElse(Left(Seq(FormError(key, "Blog not found", Nil))))
}).get
} match {
case Right(e:Either[Seq[FormError],Blog]) => e
case Left(exception) => Left(Seq(FormError(key, "Invalid Blog Id", Nil)))
case _ => Left(Seq(FormError(key, "Error in form submission", Nil)))
}
}
def unbind(key: String, value: Blog) = Map(key -> value.id.toString)
}
val blogFromLongMapping: Mapping[Blog] = Forms.of[Blog]
To me, this doesn't really look like a binding problem.
The issue is around the Model-View-Controller split. Binding is a Controller activity, and it's about binding web data (from your View) to your data model (for use by the Model). Querying the data, on the other hand, would very much be handled by the Model.
So, the standard way to do this would be something like the following:
// Defined in the model somewhere
def lookupBlog(id: Long): Option[Blog] = ???
// Defined in your controllers
val boundForm = form.bindFromRequest()
val blogOption = boundForm.value.flatMap {
case (id, comment) => lookupBlog(id)
}
blogOption match {
case Some(blog) => ??? // If the blog is found
case None => ??? // If the blog is not found
}
However, if you are determined to handle database lookup in your binding (I'd strongly advise against this, as it will lead to spaghetti code in the long run), try something like the following:
class BlogMapping(val key: String = "") extends Mapping[Blog] {
val constraints = Nil
val mappings = Seq(this)
def bind(data: Map[String, String]) = {
val blogOpt = for {blog <- data.get(key)
blog_id = blog.toLong
blog <- lookupBlog(blog_id)} yield blog
blogOpt match {
case Some(blog) => Right(blog)
case None => Left(Seq(FormError(key, "Blog not found")))
}
}
def unbind(blog: Blog) = (Map(key -> blog.id.toString), Nil)
def withPrefix(prefix: String) = {
new BlogMapping(prefix + key)
}
def verifying(constraints: Constraint[Blog]*) = {
WrappedMapping[Blog, Blog](this, x => x, x => x, constraints)
}
}
val blogMapping = new BlogMapping()
val newform = Form(
tuple(
"blog_id" -> blogMapping,
"comment" -> nonEmptyText
)
)
// Example usage
val newBoundForm = newform.bindFromRequest()
val newBoundBlog = newBoundForm.get
The main thing we've done is to create a custom Mapping subclass. This can be a good idea under some circumstances, but I'd still recommend the first approach.
You can do it all in the form definition.
I have made some simple scala classes and objects from your example.
models/Blog.scala
package models
/**
* #author maba, 2013-04-10
*/
case class User(id:Long)
case class Blog(id:Long, author:User)
case class Comment(id:Long, blog:Blog, comment:String)
object Blog {
def findById(id: Long): Option[Blog] = {
Some(Blog(id, User(1L)))
}
}
object Comment {
def create(comment: Comment) {
// Save to DB
}
}
controllers/Comments.scala
package controllers
import play.api.mvc.{Action, Controller}
import play.api.data.Form
import play.api.data.Forms._
import models.{Comment, Blog}
/**
* #author maba, 2013-04-10
*/
object Comments extends Controller {
val form = Form(
mapping(
"comment" -> nonEmptyText,
"blog" -> mapping(
"id" -> longNumber
)(
(blogId) => {
Blog.findById(blogId)
}
)(
(blog: Option[Blog]) => Option(blog.get.id)
).verifying("The blog does not exist.", blog => blog.isDefined)
)(
(comment, blog) => {
// blog.get is always possible since it has already been validated
Comment(1L, blog.get, comment)
}
)(
(comment: Comment) => Option(comment.comment, Some(comment.blog))
)
)
def index = Action { implicit request =>
form.bindFromRequest.fold(
formWithErrors => BadRequest,
comment => {
Comment.create(comment)
Ok
}
)
}
}