2013-04-09 174 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

相關問題