[2.4.2] How to map postgresql enum to scala model and enum?

357 views
Skip to first unread message

Rudy D.

unread,
Nov 17, 2015, 3:25:28 AM11/17/15
to play-framework

Hi,

I could find this question asked in many places but none of the methods which were described seemed to work for me.
I am just starting to explore Play with Scala and so I started a simple and functional CRUD application following this page. I created a Cat model and I want to add a status field in it with defined values (Awake, Sleeping, Dead). I created the postgresql enum easily with, added in an evolution :

CREATE TYPE cat_status AS ENUM ('awake', 'sleeping', 'dead');

 Then, I tried to define an object extending Enumeration and as well, using trait like in the code found here, in my model file:
CatStatus

But when I run "activator compile" I get the following error:

[error] XXX/app/models/Cat.scala:10: expected start of definition
[error] implicit val
catStatusTypeMapper = MappedColumnType.base[CatStatus, String](
[error]          ^


I tried moving the code within an object, like in here:

object myCatMapper{
implicit
val catStatusTypeMapper = MappedColumnType.base[CatStatus, String](
     
{
       
case Awake => "awake"
       
case Sleeping => "sleeping"
       
case Dead => "dead"
     
},{
       
case "awake" => Awake
       
case "sleeping" => Sleeping
       
case "dead" => Dead
     
}
   
)
}

But then, I get this error message:
[error] XXX/app/models/Cat.scala:11: not found: value MappedColumnType
[error] implicit val catStatusTypeMapper = MappedColumnType.base[CatStatus, String](
[error]                                     ^


I tried to add "import scala.slick.driver.PostgresDriver.simple._" and/or "import scala.slick.lifted._" but "object slick is not a member of package scala".

I would appreciate any help with how to achieve this simple thing.
Thank you all.

Marcin Burczak

unread,
Nov 17, 2015, 5:25:34 AM11/17/15
to play-framework
Try this:

class Repository(dbConfigProvider: DatabaseConfigProvider) {
 
val dbConfig = dbConfigProvider.get[JdbcProfile]
 
val driver: JdbcProfile = dbConfig.driver
 
import driver.api._

  implicit val catStatusTypeMapper = MappedColumnType.base[CatStatus, String](...
}

Rudy D.

unread,
Nov 17, 2015, 8:10:33 AM11/17/15
to play-framework
Thank you for the suggestion.
I move the definition of the mapper to my repository file which already dealt with the imports.
However, I cannot confirm any progress because now, I am stuck with an Json format error. If I understand, I must explicitly define how to map the Scala Enum type (enumeration or with trait).
which is logical somewhat. However, there again, I'm unable to find a single example which works in my case.

this is how the CatStatus is defined:
sealed trait CatStatus
case object Awake extends CatStatus
case object Sleeping extends CatStatus
case object Dead extends CatStatus

I tried following this page and got this:
object TaskStatus {
implicit val catStatusReads: Reads[CatStatus] = ((JsPath \"task_status").read[String])(CatStatus.apply _)
  implicit val catStatusWrites: Writes[CatStatus] = ((JsPath \"task_status").write[CatStatus])(unlift(TaskStatus.unapply))
}

which clearly doesn't cut it. output is :

[error] XXX/app/models/Cat.scala:26: value apply is not a member of object models.CatStatus
[error]         implicit val catStatusReads: Reads[CatStatus] = ((JsPath \"cat_status").read[String])(CatStatus.apply _)
[error]                                                                                                             ^
[error] XXX/app/models/Cat.scala:26: type mismatch;
[error]  found   : play.api.libs.json.Reads[String]
[error]  required: play.api.libs.json.Reads[models.CatStatus]
[error]         implicit val catStatusReads: Reads[CatStatus] = ((JsPath \"cat_status").read[String])(CatStatus.apply _)
[error]                                                                                                 ^
[error] XXX/app/models/Cat.scala:27: value unapply is not a member of object models.CatStatus
[error]         implicit val catStatusWrites: Writes[CatStatus] = ((JsPath \"cat_status").write[CatStatus])(unlift(CatStatus.unapply))
[error]                                                                                                                           ^


it didn't help to define the enum this way:
object CatStatus extends Enumeration {
  type CatStatus = Value
  val Awake = Value("awake")
  val Sleeping = Value("sleeping") 
  val Dead = Value("dead") 
  implicit val catStatusReads: Reads[CatStatus] = ((JsPath \"cat_status").read[String])(CatStatus.apply _)
  implicit val catStatusWrites: Writes[CatStatus] = ((JsPath \"cat_status").write[CatStatus])(unlift(CatStatus.unapply))
}

output was:
[error] XXX/app/models/Cat.scala:12: overloaded method value read with alternatives:
[error]   (t: String)play.api.libs.json.Reads[String] <and>
[error]   (implicit r: play.api.libs.json.Reads[String])play.api.libs.json.Reads[String]
[error]  cannot be applied to (Int => models.CatStatus.Value)
[error]   implicit val catStatusReads: Reads[CatStatus] = ((JsPath \"cat_status").read[String])(CatStatus.apply _)
[error]                                                                                  ^
[error] XXX/app/models/Cat.scala:13: value unapply is not a member of object models.CatStatus
[error]   implicit val catStatusWrites: Writes[CatStatus] = ((JsPath \"cat_status").write[CatStatus])(unlift(CatStatus.unapply))
[error]                                                                                                                     ^


Needless to say that I have no idea about what it is going on there and it just feels incredibly difficult to achieve something which is probably fairly easy.

If it was not clear before, I am trying to complete a simple CRUD application with the Play Framework using Scala, considering that my model (Cat) has an attribute (status) which has a defined set of values (Awake, Sleeping, Dead). This attribute is represented in the Postgresql database as an Enum.
So far, the only solid learning I have made is that I need the following 3:
- A way to represent the attribute with defined set of values in scala (Enumeration? trait? Both?)
- A way to map this representation to the db enum
- A way to serialize this representation in Json
Is that right or did I get this part twisted as well? Is there anything else missing?

I would be thankful for any guidance concerning this, with as much details as possible, as I'm so lost, I'm not even sure in which files I am supposed to put the code snippets I find online.
Thank you.

Marcin Burczak

unread,
Nov 17, 2015, 9:50:34 AM11/17/15
to play-framework
This is because lack of enums in Scala, https://groups.google.com/forum/#!topic/scala-sips/Bf82LxK02Kk

I always use sealed trait and write companion object with apply method by hand:
sealed trait CatStatus
case object Awake extends CatStatus
case object Sleeping extends CatStatus
case object Dead extends CatStatus
object CatStatus {

 
def apply(value: String): CatStatus = {
    value
match {
     
case "Awake" => Awake
     
case "Sleeping" => Sleeping
     
case "Dead" => Dead
   
}
 
}
}

To convert enum to string simple use toString method.

Rudy D.

unread,
Nov 17, 2015, 5:04:23 PM11/17/15
to play-framework
Thanks again. So I followed and added the apply method in the object CatStatus. The compiler complained that the CatStatus type did not have an implicit Json format to allow this:
object Cat {
  
  implicit val catFormat = Json.format[Cat]

}

so I added:
implicit val catStatusFormat = Json.format[CatStatus]

Then it started to complain that there was no unapply method on the same schema:
def unapply(value: CatStatus): String = {
  value match {
  case Awake => "Awake"
  case Sleeping => "Sleeping"
  case Dead => "Dead"
  }
  }

but " Unapply of object CatStatus has no parameters. Are you using an empty case class?". Looking further on the missing unapply methods, I found posts talking about reads and writes and... once again, I'm lost.
I found this two posts there and there which seem related to what I'm trying to achieve but I can't figure out how to adapt them to my case (with trait and case objects) or if this library can help (o though ideally, I would like to understand what is going on).
Sorry if my questions are so basic and thanks again for the help.

Marcin Burczak

unread,
Nov 18, 2015, 3:54:40 AM11/18/15
to play-framework
case class Cat(name: String, status: CatStatus)

object Cat {

 
implicit val catFormat: Format[Cat] = (
   
(JsPath \ "name").format[String] and
   
(JsPath \ "status").format[String].inmap(CatStatus.apply(_), (s: CatStatus) => s.toString)
 
)(Cat.apply, unlift(Cat.unapply))
}

Rudy D.

unread,
Dec 3, 2015, 2:26:32 PM12/3/15
to play-framework
Hi,

Thank you. I ran into additional issues with this but ultimately, I decided to use "enumeratum" which seems to make the manipulation of this trait enum a bit easier. Thanks to you, I think I have a better understand of what's going on, even though, I still have to investigate.

Cheers
Reply all
Reply to author
Forward
0 new messages