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
}
)
}
}
Related
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)
}
}
}
}
I have a class that I am trying to deserialize using the json4s CustomSerializer functionality. I need to do this due to the inability of json4s to deserialize mutable collections.
This is the basic structure of the class I want to deserialize (don't worry about why the class is structured like this):
case class FeatureValue(timestamp:Double)
object FeatureValue{
implicit def ordering[F <: FeatureValue] = new Ordering[F] {
override def compare(a: F, b: F): Int = {
a.timestamp.compareTo(b.timestamp)
}
}
}
class Point {
val features = new HashMap[String, SortedSet[FeatureValue]]
def add(name:String, value:FeatureValue):Unit = {
val oldValue:SortedSet[FeatureValue] = features.getOrElseUpdate(name, SortedSet[FeatureValue]())
oldValue += value
}
}
Json4s serializes this just fine. A serialized instance might look like the following:
{"features":
{
"CODE0":[{"timestamp":4.8828914447482E8}],
"CODE1":[{"timestamp":4.8828914541333E8}],
"CODE2":[{"timestamp":4.8828915127325E8},{"timestamp":4.8828910097466E8}]
}
}
I've tried writing a custom deserializer, but I don't know how to deal with the list tails. In a normal matcher you can just call your own function recursively, but in this case the function is anonymous and being called through the json4s API. I cannot find any examples that deal with this and I can't figure it out.
Currently I can match only a single hash key, and a single FeatureValue instance in its value. Here is the CustomSerializer as it stands:
import org.json4s.{FieldSerializer, DefaultFormats, Extraction, CustomSerializer}
import org.json4s.JsonAST._
class PointSerializer extends CustomSerializer[Point](format => (
{
case JObject(JField("features", JObject(Nil)) :: Nil) => new Point
case JObject(List(("features", JObject(List(
(feature:String, JArray(List(JObject(List(("timestamp",JDouble(ts)))))))))
))) => {
val point = new Point
point.add(feature, FeatureValue(ts))
point
}
},
{
// don't need to customize this, it works fine
case x: Point => Extraction.decompose(x)(DefaultFormats + FieldSerializer[Point]())
}
))
If I try to change to using the :: separated list format, so far I have gotten compiler errors. Even if I didn't get compiler errors, I am not sure what I would do with them.
You can get the list of json features in your pattern match and then map over this list to get the Features and their codes.
class PointSerializer extends CustomSerializer[Point](format => (
{
case JObject(List(("features", JObject(featuresJson)))) =>
val features = featuresJson.flatMap {
case (code:String, JArray(timestamps)) =>
timestamps.map { case JObject(List(("timestamp",JDouble(ts)))) =>
code -> FeatureValue(ts)
}
}
val point = new Point
features.foreach((point.add _).tupled)
point
}, {
case x: Point => Extraction.decompose(x)(DefaultFormats + FieldSerializer[Point]())
}
))
Which deserializes your json as follows :
import org.json4s.native.Serialization.{read, write}
implicit val formats = Serialization.formats(NoTypeHints) + new PointSerializer
val json = """
{"features":
{
"CODE0":[{"timestamp":4.8828914447482E8}],
"CODE1":[{"timestamp":4.8828914541333E8}],
"CODE2":[{"timestamp":4.8828915127325E8},{"timestamp":4.8828910097466E8}]
}
}
"""
val point0 = read[Point]("""{"features": {}}""")
val point1 = read[Point](json)
point0.features // Map()
point1.features
// Map(
// CODE0 -> TreeSet(FeatureValue(4.8828914447482E8)),
// CODE2 -> TreeSet(FeatureValue(4.8828910097466E8), FeatureValue(4.8828915127325E8)),
// CODE1 -> TreeSet(FeatureValue(4.8828914541333E8))
// )
I've followed this application tutorial. Basically, in this app they create a todo item. I've replicated the model to a survey application, and I want to create a question that contains both question text and question type (binary, text entry etc). I get various degrees of errors regarding my question form formatting, etc but the main problem seems to be in the newQuestion action on
questionForm.bindFromRequest.fold(errors=> ..., question=> ...)
My code is formatted as follows:
QuestionController.scala:
def questions = Action {
Ok(views.html.question(Question.all(), questionForm))
}
def newQuestion = Action { implicit request =>
questionForm.bindFromRequest.fold(
errors => BadRequest(views.html.question(Question.all(), errors)),
question => {
Question.create(question)
Redirect(routes.QuestionController.questions)
}
)
}
def deleteQuestion(id: Long) = Action {
Question.delete(id)
Redirect(routes.QuestionController.questions)
}
val questionForm = Form(
"questiontext" -> nonEmptyText
)
CaseClass/companion object Question.scala:
case class Question (id:Long, questionText:String, questionType:String)
object Question {
val question = {
get[Long]("id") ~
get[String]("questiontext") ~
get[String]("questiontype") map {
case id~questiontext~questiontype => Question(id, questiontext,questiontype)
}
}
def all(): List[Question] = DB.withConnection { implicit c =>
SQL("select * from questions").as(question *)
}
def create(text:String) {
DB.withConnection {
implicit c =>
SQL("insert into questions(questiontext) values ({text})").on(
'text -> text
//'quesType -> qType
).executeUpdate()
}
}
def delete(id:Long) {
DB.withConnection{ implicit c =>
SQL("delete from questions where id = {id}").on(
'id -> id
).executeUpdate()
}
}
}
I essentially want to run create with two parameters as such:
def create(text:String,qType:String) {
DB.withConnection { implicit c =>
SQL("insert into questions(questiontext,questiontype) values ({text},{quesType})").on(
'text -> text,
'quesType -> qType
).executeUpdate()
}
}
But I'm unsure how to handle this in the bindFromRequest.fold as mentioned previously.
I tried editing the questionForm to use a mapping and using (Question.apply)(Question.unapply) as well as just a tuple and the questiontext -> nonEmptyText and questionType -> nonEmptyText contained in that tuple with no luck.
I edited the scala.html file to contain the right type of value accordingly with still no luck.
I can get it to save with one column but it crashes because I defined the questionType to be a required field in pgsql and the get fails on the ~get questionType from * after appending without a question type.
UPDATE:
I've edited it to try and emulate the code here:
https://www.playframework.com/documentation/2.4.1/ScalaForms
So now, I am using the following code:
def questions = Action {
Ok(views.html.question(Question.all(), questionForm))
}
def newQuestion = Action { implicit request =>
logger.error(s"wtf")
questionForm.bindFromRequest.fold(
errors => {BadRequest(views.html.question(Question.all(), errors))
},
question => {
Question.create(question:Question)
Redirect(routes.QuestionController.questions).flashing("success"->"Question saved successfully!")
}
)
}
def deleteQuestion(id: Long) = Action {
Question.delete(id)
Redirect(routes.QuestionController.questions)
}
val questionForm:Form[Question]=Form(mapping(
"id" -> longNumber,
"versionid" -> longNumber,
"questiontext" -> nonEmptyText,
"questiontype" -> nonEmptyText)(Question.apply)(Question.unapply)
)
I've updated my question.scala.html file to take #(questions: List[Question], questionForm: Form[(Question)]) as inputs.
when i hit submit, I keep getting the Log message of :wtf
Following the example i most recently linked, I am not understanding why it does not go to the case of question and continue to create the question in the database. I updated my database create function as the following as well:
def create(question:Question) {
DB.withConnection {
implicit c =>
SQL("insert into questions(questiontext,questiontype,versionid) values ({text},{quesType},{versId})").on(
'text -> question.questionText,
'quesType -> question.questionType,
'versId -> 1.toLong
).executeUpdate()
}
}
Thanks for the help thus far, I hope I can figure out what I am doing wrong shortly.
I'm trying to build a REST API using play 2.0. I have a User case class that contains some fields (like username & password) that shouldn't be updatable by the updateMember method.
Is there a good, functional way, of dealing with multiple Options somehow, because request.body.asJson returns an Option[JsValue], and my user lookup also returns an Option:
package controllers.api
import org.joda.time.LocalDate
import play.api.Play.current
import play.api.db.slick.{DB, Session}
import play.api.mvc._
import play.api.libs.json._
import play.api.libs.functional.syntax._
import models.{Gender, User, UserId}
import repositories.UserRepository
object Member extends Controller {
def updateMember(id: Long) = Action {
DB.withSession {
implicit session: Session =>
val json: JsValue = request.body.asJson // how to deal with this?
val repository = new UserRepository
repository.findById(new UserId(id)).map {
user =>
def usernameAppender = __.json.update(
__.read[JsObject].map { o => o ++ Json.obj("username" -> user.username) }
)
json.transform(usernameAppender) // transform not found
Ok("updated")
}.getOrElse(NotFound)
}
}
}
I could move the map call to where I try to parse the request, but then inside there I guess I'd need another map over the user Option like I already have. So in that style, I'd need a map per Option.
Is there a better way of dealing with multiple Options like this in FP?
You're basically dealing with a nested monad, and the main tool for working with such is flatMap, particularly if both options being None has the same semantic meaning to your program:
request.body.asJson.flatMap { requestJson =>
val repository = new UserRepository
repository.findById(new UserId(id)).map { user =>
def usernameAppender = __.json.update(
__.read[JsObject].map { o => o ++ Json.obj("username" -> user.username) }
)
requestJson.transform(usernameAppender)
Ok("updated") // EDIT: Do you not want to return the JSON?
}
}.getOrElse(NotFound)
But it might also be the case that your Nones have different meanings, in which case, you probably just want to pattern match, and handle the error cases separately:
request.body.asJson match {
case Some(requestJson) =>
val repository = new UserRepository
repository.findById(new UserId(id)).map { user =>
def usernameAppender = __.json.update(
__.read[JsObject].map { o => o ++ Json.obj("username" -> user.username) }
)
requestJson.transform(usernameAppender)
Ok("updated")
}.getOrElse(NotFound)
case None => BadRequest // Or whatever you response makes sense for this case
}
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)