Created
January 29, 2015 15:33
-
-
Save sam/414e87fc9df6a24dd15b to your computer and use it in GitHub Desktop.
[PlayFramework-2.0] Trouble figuring out custom mappings, pre-filled default data, and ScalaUtils "at-least-one" Every[T] collection type.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This code won't compile without some Play dependencies being satisfied. | |
// Like Controller, formats.parsing, etc. It's just for reference. | |
object ArticlesController extends Controller { | |
import java.util.Locale | |
// Our application has several translations for an Article: | |
val locales: List[Locale] = Seq(Locale.US, Locale.JAPAN) | |
def create = Action { | |
views.articles.edit(articleCreateForm) | |
// So far I haven't figured out if I can provide defaults for the form, based on locales, | |
// or if I must fill. | |
} | |
// This is just an "at least one" SeqLike. I like that the constraint | |
// "a valid article would have to have a title in at least one locale, doesn't matter which", | |
// can be enforced by the type-system. | |
import org.scalautils.Every | |
// Just a list that verifies it's nonEmpty! | |
def every[A](mapping: Mapping[A]): Mapping[Every[A]] = { | |
RepeatedMapping(mapping) | |
.verifying(_.nonEmpty) | |
.transform[Every[A]](list => Every(list.head, list.tail:_*), _.toList) | |
// There has to be a better way I'm not aware of. This feels so wrong, and I have | |
// no idea what `format` or `data` actually represent. | |
val locale: Mapping[Locale] = Forms.of(new Formatter[Locale] { | |
override val format = Some(("format.locale", Nil)) | |
def bind(key: String, data: Map[String, String]): Either[Seq[FormError], Locale] = parsing(Locale.forLanguageTag, "error.locale", Nil)(key, data) | |
def unbind(key: String, value: Locale): Map[String, String] = Map(key -> value.toLanguageTag) | |
}) | |
// Finally my form specific case class. Note that you can't construct it without at least | |
// one value in titles, ie: One(Locale.US -> "Foo Bar") | |
// So it can't be used with form.fill. | |
case class ArticleCreateDirective(titles: Every[(Locale, String)], notes: Option[String], active: Boolean) | |
// Here's my mapping. It really seems to be that I want the title to be optional | |
// as opposed to filtering by _.trim().nonEmpty. But then that means I need a bijective(?) | |
// transform from Every[(Locale, Option[String])] to Every[(Locale, String)]. | |
// Which is fine, that's easy to write. But where do my | |
// Locale.JAPAN -> None and Locale.US -> None default values come in then? | |
val titleMapping = every(tuple("locale" -> locale, "title" -> optional(text))) | |
.verifying(_ exists { case (_, title) => title.isDefined }) | |
val articleCreateForm = Form( | |
mapping( | |
"titles" -> titleMapping, | |
"notes" -> optional(text), | |
"active" -> boolean, | |
)(ArticleCreateDirective.apply)(ArticleCreateDirective.unapply)) | |
// Right now I don't have a solution I'm happy with. So what I'm doing is changing | |
// ArticleCreateDirective.titles to an Every[(Locale, Option[String])], and | |
// definining a companion-object def empty that populates the configured locales with | |
// None entries. | |
// | |
// I'll have to do the same for my UpdateDirective (not shown here). And then during | |
// persistence run my filter. | |
// | |
// The problem of all that is that I'm losing the entire point of using an Every | |
// if I can have an Every[Option[T]] that's actually empty when flattened. I'm only | |
// guaranteeing that I'll get a runtime error if I don't guard with verifying checks | |
// in the mappings (as above) instead of persisting bad data to the database. Which | |
// is better than nothing. But it's very dissapointing I can't figure out how to do | |
// this cleaner with some sort of RepeatedMappingWithADefaultSet, so that I could have | |
// optional(text) on the form side, still have my every mapping, my case classes can | |
// be clean of the Every[Option] hacks, and I can still pre-fill my default entries | |
// for the locales to be used with a helper.repeat call in my template. | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment