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 Radley Julien Richard-Foy
Leon Radley

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
    )
Julien Richard-Foy
Collaborator

Otherwise I think it’s an interesting contribution :)

Leon Radley

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?

Julien Richard-Foy
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 Radley

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

Julien Richard-Foy
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)
  }
Julien Richard-Foy
Collaborator

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

Leon Radley

Thanks for all the help!

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

Leon Radley

Moved to this new one instead

#253

Leon Radley 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 Radley

    Added Enum Formatter and Form type

    leon authored leon committed
This page is out of date. Refresh to see the latest.
1  framework/src/play/src/main/resources/messages
View
@@ -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
16 framework/src/play/src/main/scala/play/api/data/Forms.scala
View
@@ -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)
-}
+}
15 framework/src/play/src/main/scala/play/api/data/format/Format.scala
View
@@ -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)
+ }
+
}
23 framework/src/play/src/test/scala/play/data/FormSpec.scala
View
@@ -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.