使用自定义映射到对象的表单绑定 - 如何?

时间:2013-04-09 14:10:03

标签: forms scala binding playframework

我有这些案例类

case class Blog(id:Long, author:User, other stuff...)
case class Comment(id:Long, blog:Blog, comment:String)

以及客户端提交数据的表单

blog_id:"5"
comment:"wasssup"

我正在编写一些简单的代码,让用户在博客中添加评论 用户已登录,因此客户端不需要他user_id,我们知道他是谁......

我想将blog_id绑定到从db加载的Blog对象,如果它不存在则显示错误。
有关游戏框架文档的示例没有用处 它们仅显示表示单个对象及其所有字段的表单的映射 在这里,我代表(b:Blog, comment:String)的元组,而Blog我只提供它id

我希望有一个映射可以为我提供转换+验证+错误消息,因此我可以编写如下内容:

val form = Form(
    tuple(
      "blog_id" -> blogMapping,
      "comment" -> nonEmptyText
    )
  )
  form.bindFromRequest().fold(...
  formWithErrors => {...
  }, {
    case (blog, comment) => {do some db stuff to create the comment}
  ...

" blogMapping" wlil像其他映射一样工作,它会将发布的数据绑定到一个对象,在我们的例子中是一个从db加载的博客,如果它不成功,它将提供我们可以在{{1}上使用的错误条款。

我不知道如何实现这一点,这里的文档有点缺乏...
任何帮助表示赞赏!

3 个答案:

答案 0 :(得分:3)

我最后看看playframwork的当前绑定是什么样子并实现类似的东西,但对于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]

答案 1 :(得分:2)

对我来说,这看起来并不像是一个绑定问题。

问题在于模型 - 视图 - 控制器拆分。绑定是一个Controller活动,它是关于将Web数据(从您的View)绑定到您的数据模型(供Model使用)。另一方面,查询数据将由模型处理。

因此,执行此操作的标准方法如下:

// 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
}

但是,如果您决定在绑定中处理数据库查找(我强烈反对这一点,因为从长远来看会导致意大利面条代码),请尝试以下内容:

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

我们做的主要是创建自定义Mapping子类。在某些情况下这可能是一个好主意,但我仍然建议采用第一种方法。

答案 2 :(得分:0)

您可以在表单定义中完成所有操作。

我从你的例子中做了一些简单的scala类和对象。

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