Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Enum mapper #240

Closed
wants to merge 1 commit into from

2 participants

@leon

Enum wrapper for form

In Models

    object UserStatus extends Enumeration {
      val Free = Value("free")
      val Busy = Value("busy")

      val options = values.map(a => (a.toString -> a.toString.capitalize))(collection.breakOut)
    }

    case class User(
      status: UserStatus.Value = UserStatus.Free
    )

In Controller

    val userForm = Form(
        mapping(
          "status" -> enum(UserStatus)
        )(User.apply)(User.unapply)
    )

In View

    @select(
      userForm("status"),
      UserStatus.options,
      '_label -> "Status",
      '_default -> "Choose Status",
      '_showConstraints -> false
    )
@julienrf
Collaborator

Otherwise I think it’s an interesting contribution :)

@leon

I've fixed the message and removed the implicit.

I've got a question for you though.

Have you had any problems importing Enumerations?
I've done some reading, and according to the scala homepage they have added a type alias to enum which points the enum it's inner class called Value.

What this means is that I cannot call a MyEnum.withName() since I don't have a reference to the Enumeration, but to the inner class Value.

What's your take on Enumerations and how we should best support them?

@julienrf
Collaborator

I don’t know, your patch seems good. It would be perfect if you could add an example of use in src/test/data/FormSpec.scala.

@leon

I've added a test that shows my problem.
The test fails telling me it can't find the UserStatus enum.

Anyone got a clue?

leon@7577a35

@julienrf
Collaborator

That’s because you wrote enum[UserStatus] instead of enum(UserStatus).
By the way, IMHO the test contains too much boilerplate, you could reduce it to the following:

  "render a form with an enum" in {
    import play.api.data._
    import play.api.data.Forms._

    object Status extends Enumeration {
      val Free = Value("free")
      val Busy = Value("busy")
    }

    val statusForm = Form("status" -> enum(Status))

    val enumData = Map("status" -> "free")
    statusForm.bind(enumData).get must equalTo(Status.Free)
  }
@julienrf
Collaborator

By the way, could you squash all your commit into a single one?

@leon

Thanks for all the help!

I think it's ready for prime time now :)

@leon

Moved to this new one instead

#253

@leon leon closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 20, 2012
  1. @leon

    Added Enum Formatter and Form type

    leon authored leon committed
This page is out of date. Refresh to see the latest.
View
1  framework/src/play/src/main/resources/messages
@@ -22,3 +22,4 @@ error.minLength=Minimum length is {0}
error.maxLength=Maximum length is {0}
error.email=Valid email required
error.pattern=Must satisfy {0}
+error.enum=Invalid value
View
16 framework/src/play/src/main/scala/play/api/data/Forms.scala
@@ -403,11 +403,23 @@ object Forms {
def checked(msg: String): Mapping[Boolean] = boolean verifying (msg, _ == true)
+ /**
+ * Constructs a simple mapping for a text field (mapped as `scala.Enumeration`)
+ *
+ * For example:
+ * {{{
+ * Form("gender" -> enum(Gender))
+ * }}}
+ *
+ * @param enum the enumeration
+ */
+ def enum[E <: Enumeration](enum: E): Mapping[E#Value] = of(enumFormat(enum))
+
// ----------------------------------------------
//
// --- Deprecated members, to remove in Play 2.1
//
- // ----------------------------------------------
+ // ----------------------------------------------
@deprecated("Use mapping(...) instead", "2.0")
def of[R, A1](apply: Function1[A1, R], unapply: Function1[R, Option[(A1)]])(a1: (String, Mapping[A1])): Mapping[R] = {
@@ -553,4 +565,4 @@ object Forms {
@deprecated("Use tuple(...) instead", "2.0")
def of[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18](a1: (String, Mapping[A1]), a2: (String, Mapping[A2]), a3: (String, Mapping[A3]), a4: (String, Mapping[A4]), a5: (String, Mapping[A5]), a6: (String, Mapping[A6]), a7: (String, Mapping[A7]), a8: (String, Mapping[A8]), a9: (String, Mapping[A9]), a10: (String, Mapping[A10]), a11: (String, Mapping[A11]), a12: (String, Mapping[A12]), a13: (String, Mapping[A13]), a14: (String, Mapping[A14]), a15: (String, Mapping[A15]), a16: (String, Mapping[A16]), a17: (String, Mapping[A17]), a18: (String, Mapping[A18])): Mapping[(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18)] = of((a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8, a9: A9, a10: A10, a11: A11, a12: A12, a13: A13, a14: A14, a15: A15, a16: A16, a17: A17, a18: A18) => (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18), (t: (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18)) => Some(t))(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18)
-}
+}
View
15 framework/src/play/src/main/scala/play/api/data/format/Format.scala
@@ -170,5 +170,20 @@ object Formats {
*/
implicit val sqlDateFormat: Formatter[java.sql.Date] = sqlDateFormat("yyyy-MM-dd")
+ /**
+ * Default formatter for `scala.Enumeration`
+ *
+ */
+ def enumFormat[E <: Enumeration](enum: E): Formatter[E#Value] = new Formatter[E#Value] {
+ def bind(key: String, data: Map[String, String]) = {
+ Formats.stringFormat.bind(key, data).right.flatMap { s =>
+ scala.util.control.Exception.allCatch[E#Value]
+ .either(enum.withName(s))
+ .left.map(e => Seq(FormError(key, "error.enum", Nil)))
+ }
+ }
+ def unbind(key: String, value: E#Value) = Map(key -> value.toString)
+ }
+
}
View
23 framework/src/play/src/test/scala/play/data/FormSpec.scala
@@ -61,16 +61,16 @@ object FormSpec extends Specification {
myForm hasErrors () must beEqualTo(true)
}
-
+
"apply constraints on wrapped mappings" in {
import play.api.data._
import play.api.data.Forms._
-
+
val form = Form(
"foo" -> text.verifying("first.digit", s => (s.headOption map {_ == '3'}) getOrElse false)
.transform[Int](Integer.parseInt _, _.toString).verifying("number.42", _ < 42)
)
-
+
"when it binds data" in {
val f1 = form.bind(Map("foo"->"0"))
f1.errors.size must equalTo (1)
@@ -87,7 +87,7 @@ object FormSpec extends Specification {
f4.errors.size must equalTo (1)
f4.errors.find(_.message == "number.42") must beSome
}
-
+
"when it is filled with data" in {
val f1 = form.fillAndValidate(0)
f1.errors.size must equalTo (1)
@@ -166,5 +166,20 @@ object FormSpec extends Specification {
userForm.hasErrors() must equalTo(false)
(user == null) must equalTo(false)
}
+
+ "render a form with an enum" in {
+ import play.api.data._
+ import play.api.data.Forms._
+
+ object Status extends Enumeration {
+ val Free = Value("free")
+ val Busy = Value("busy")
+ }
+
+ val statusForm = Form("status" -> enum(Status))
+
+ val enumData = Map("status" -> "free")
+ statusForm.bind(enumData).get must equalTo(Status.Free)
+ }
}
Something went wrong with that request. Please try again.