2013-04-09 2 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 объекта, загруженного из БД, и если он Безразлично 't существует ошибка.
Примеры в документах с платформой play не помогают.
Они отображают только отображения для форм, которые представляют собой один объект и все его поля.
Здесь я представляю кортеж (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} 
    ... 

The «blogMapping» wlil работы, как и другие отображения , он привяжет опубликованные данные к объекту, в нашем случае - блог, загруженный из db, и в случае неудачи он предоставит ошибку, которую мы можем использовать в предложении formWithErrors =>.

Я не уверен, как это сделать, документы здесь не хватает ...
любая помощь приветствуется!

+0

Я принял Джеймс отвечает, но использовал что-то немного другое, я добавлю свой ответ, чтобы другие могли его просмотреть. – samz

ответ

2

Для меня это не похоже на проблему привязки.

Проблема связана с разделом Model-View-Controller. Связывание - это активность контроллера, и речь идет о привязке веб-данных (от вашего представления) к вашей модели данных (для использования моделью). С другой стороны, запрос данных будет обрабатываться Моделью.

Таким образом, стандартный способ сделать это было бы что-то вроде следующего:

// 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. Это может быть хорошей идеей при некоторых обстоятельствах, но я по-прежнему рекомендую первый подход.

+0

В первом подходе, как вы идиоматически обрабатываете ошибки формы? – senz

+1

Если в форме есть какая-либо проверка (так что это может быть недействительно), то вам, вероятно, лучше всего отработать проверку формы отдельно. Поэтому вместо использования 'boundForm.value.flatMap', используйте что-то вроде' boundform.fold' и найдите блог в ветке успеха. –

+0

Тогда он по-прежнему будет глубоко вложенным кошмаром спагетти): – senz

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 поездки в db. один для проверки, а другой для загрузки. Если проверка завершится успешно, я уже хочу иметь блог obj, зачем беспокоиться о нем снова? даже если он очень быстрый из-за индексации, или orm может загрузить его из кеша. Кроме того, я использую эту привязку во многих других местах, поэтому мне приходится писать один и тот же файл checkIfExistsThenLoad, вызывающий раздражение. – samz

+0

@samz Я вижу вашу мысль. Я обновил свое предложение, чтобы блог читался один раз из БД, и если он не найден, будет предоставлено сообщение, иначе объект блога будет использоваться в комментариях. – maba

+0

@samz У вас есть комментарии после моего редактирования? – maba

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

Смежные вопросы