Skip to content
This repository

Enum mapper #240

Closed
wants to merge 1 commit into from

2 participants

Leon Radley Julien Richard-Foy
Leon Radley
leon commented

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

Otherwise I think it’s an interesting contribution :)

Leon Radley
leon commented

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

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
leon commented

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

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

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

Leon Radley
leon commented

Thanks for all the help!

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

Leon Radley
leon commented

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

Showing 1 unique commit by 2 authors.

Apr 20, 2012
Leon Radley Added Enum Formatter and Form type 3478c9c
This page is out of date. Refresh to see the latest.
1  framework/src/play/src/main/resources/messages
@@ -22,3 +22,4 @@ error.minLength=Minimum length is {0}
22 22
 error.maxLength=Maximum length is {0}
23 23
 error.email=Valid email required
24 24
 error.pattern=Must satisfy {0}
  25
+error.enum=Invalid value
16  framework/src/play/src/main/scala/play/api/data/Forms.scala
@@ -403,11 +403,23 @@ object Forms {
403 403
 
404 404
   def checked(msg: String): Mapping[Boolean] = boolean verifying (msg, _ == true)
405 405
 
  406
+  /**
  407
+   * Constructs a simple mapping for a text field (mapped as `scala.Enumeration`)
  408
+   *
  409
+   * For example:
  410
+   * {{{
  411
+   *   Form("gender" -> enum(Gender))
  412
+   * }}}
  413
+   *
  414
+   * @param enum the enumeration
  415
+   */
  416
+  def enum[E <: Enumeration](enum: E): Mapping[E#Value] = of(enumFormat(enum))
  417
+
406 418
   // ----------------------------------------------
407 419
   //
408 420
   // --- Deprecated members, to remove in  Play 2.1
409 421
   //
410  
-  // ----------------------------------------------  
  422
+  // ----------------------------------------------
411 423
 
412 424
   @deprecated("Use mapping(...) instead", "2.0")
413 425
   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 {
553 565
   @deprecated("Use tuple(...) instead", "2.0")
554 566
   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)
555 567
 
556  
-}
  568
+}
15  framework/src/play/src/main/scala/play/api/data/format/Format.scala
@@ -170,5 +170,20 @@ object Formats {
170 170
    */
171 171
   implicit val sqlDateFormat: Formatter[java.sql.Date] = sqlDateFormat("yyyy-MM-dd")
172 172
 
  173
+  /**
  174
+   * Default formatter for `scala.Enumeration`
  175
+   *
  176
+   */
  177
+  def enumFormat[E <: Enumeration](enum: E): Formatter[E#Value] = new Formatter[E#Value] {
  178
+    def bind(key: String, data: Map[String, String]) = {
  179
+      Formats.stringFormat.bind(key, data).right.flatMap { s =>
  180
+        scala.util.control.Exception.allCatch[E#Value]
  181
+          .either(enum.withName(s))
  182
+          .left.map(e => Seq(FormError(key, "error.enum", Nil)))
  183
+      }
  184
+    }
  185
+    def unbind(key: String, value: E#Value) = Map(key -> value.toString)
  186
+  }
  187
+
173 188
 }
174 189
 
23  framework/src/play/src/test/scala/play/data/FormSpec.scala
@@ -61,16 +61,16 @@ object FormSpec extends Specification {
61 61
       myForm hasErrors () must beEqualTo(true)
62 62
 
63 63
     }
64  
-    
  64
+
65 65
     "apply constraints on wrapped mappings" in {
66 66
       import play.api.data._
67 67
       import play.api.data.Forms._
68  
-      
  68
+
69 69
       val form = Form(
70 70
           "foo" -> text.verifying("first.digit", s => (s.headOption map {_ == '3'}) getOrElse false)
71 71
                      .transform[Int](Integer.parseInt _, _.toString).verifying("number.42", _ < 42)
72 72
       )
73  
-      
  73
+
74 74
       "when it binds data" in {
75 75
         val f1 = form.bind(Map("foo"->"0"))
76 76
         f1.errors.size must equalTo (1)
@@ -87,7 +87,7 @@ object FormSpec extends Specification {
87 87
         f4.errors.size must equalTo (1)
88 88
         f4.errors.find(_.message == "number.42") must beSome
89 89
       }
90  
-      
  90
+
91 91
       "when it is filled with data" in {
92 92
         val f1 = form.fillAndValidate(0)
93 93
         f1.errors.size must equalTo (1)
@@ -166,5 +166,20 @@ object FormSpec extends Specification {
166 166
     userForm.hasErrors() must equalTo(false)
167 167
     (user == null) must equalTo(false)
168 168
   }
  169
+
  170
+  "render a form with an enum" in {
  171
+    import play.api.data._
  172
+    import play.api.data.Forms._
  173
+
  174
+    object Status extends Enumeration {
  175
+      val Free = Value("free")
  176
+      val Busy = Value("busy")
  177
+    }
  178
+
  179
+    val statusForm = Form("status" -> enum(Status))
  180
+
  181
+    val enumData = Map("status" -> "free")
  182
+    statusForm.bind(enumData).get must equalTo(Status.Free)
  183
+  }
169 184
 }
170 185
 
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.