Skip to content

Instantly share code, notes, and snippets.

@sam
Created January 29, 2015 15:33
Show Gist options
  • Save sam/414e87fc9df6a24dd15b to your computer and use it in GitHub Desktop.
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 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