2013-04-09 113 views
1

我有这些案例类将自定义映射绑定到对象的形式 - 如何?

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从数据库加载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加载的博客,如果它不成功,它会提供一个我们可以在formWithErrors =>子句中使用的错误。

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

+0

我接受詹姆斯回答,但使用了一些不同的东西,我会添加我的答案,以便其他人可以查看它。 – samz 2013-04-11 09:09:08

回答

2

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

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

因此,要做到这一点,标准的方式是类似以下内容:

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

我们所做的主要事情是创建一个自定义映射子类。在某些情况下,这可能是一个好主意,但我仍然会推荐第一种方法。

+0

在第一种方法中,您如何习惯性地处理表单错误? – senz 2014-06-18 08:29:06

+1

如果表单中有一些验证(因此它可能无效),那么您可能最好单独处理表单验证。因此,不是使用'boundForm.value.flatMap',而是使用'boundform.fold'之类的东西,并在成功分支中查找博客。 – 2014-06-18 08:46:46

+0

然后,它仍然是一个深层嵌套的意大利面恶梦): – senz 2014-06-18 08:52:39

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

这是KISS的解决方案,但我不喜欢它,因为它使2次访问数据库。一个用于验证,另一个用于加载。如果验证成功,我已经想要博客obj,为什么又要查看它?即使它因为索引而超快,orm可以从缓存中加载它。此外,我还在许多其他地方使用该绑定,因此不得不编写相同的checkIfExistsThenLoad代码块很烦人。 – samz 2013-04-11 09:07:28

+0

@samz我明白你的观点。我已经更新了我的建议,以便从数据库中读取一次博客,如果没有找到,则会给出消息,否则将在注释中使用博客对象。 – maba 2013-04-11 09:33:14

+0

@samz我的编辑后有任何意见吗? – maba 2013-04-11 14:23:57

3

我结束了在看playframwork当前绑定看起来怎么样和实施类似的东西,但对于博客:

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] 
+0

+1感谢提醒我我的代码库中已经有JodaTime的自定义绑定器,完全忘记了他们在那里;-) – virtualeyes 2013-09-17 06:35:54