scalaslickslick-2.0

Mapping a class as a column type within a class


I understand how this is done when using types such as Long, Int, String etc.. But say I have a class that has fields within another class like so:

case class Foo(a:String, b:String)
case class Bar(foo:Option[Foo], c:String)

How would I set up a mapper for my custom type (the Foo in my Bar class)?

class Bars(tag:Tag) extends Table[Bar](tag, "BARS") {
  def foo = column[Foo]("FOO") // <- won't work
  def c = column[String]("C")

  def * = (foo, c) <> (Bar.tupled, Bar.unapply)
}

(documentation link)


Update:

DB Driver: slick.driver.PostgresDriver

Slick 2

I'm guessing the raw SQL would look like this:

"BARS" (
  "A" VARCHAR(254) NOT NULL,
  "B" VARCHAR(254) NOT NULL, 
  "C" VARCHAR(254) NOT NULL
);

Should be able to call Bar like so:

val bar = Bar(Foo("1", "2"), "3")
barTable.insert(bar)
bar.foo.a // 1
bar.foo.b // 2
bar.c // 3

Solution

  • You can write a mapper between the case class and some type that can be stored in the database.

    See an example from Slick here:http://slick.typesafe.com/doc/1.0.0/lifted-embedding.html, at the end of the page.

    One easy way in your case might be to transform your case class into json and store as a string. (And if your DB supports json type directly, like PostgreSQL, you can specify JSON type in column mapper, that would give you an advantage when making queries related to the content of your case classes.)

    import org.json4s._
    import org.json4s.native.Serialization
    import org.json4s.native.Serialization.{read, write}
    
    //place this in scope of your table definition
    implicit val FooTypeMapper = MappedTypeMapper.base[Foo, String](
      { f => write(f) },    // map Foo to String
      { s => read[Too](s) } // map String to Foo
    )
    
    class Bars(tag:Tag) extends Table[Bar](tag, "BARS") {
      def foo = column[Foo]("FOO") // <- should work now
      def c = column[String]("C")
    
      def * = (foo, c) <> (Bar.tupled, Bar.unapply)
    }
    

    With PostgreSQL >=9.3, you can also write:

    def foo = column[Foo]("FOO", O.DBType("json"))

    So that DB treats your json properly.

    UPDATE: there is a connection property that should be set if send a String for a JSON field. Something like this:

      val prop = new java.util.Properties
      prop.setProperty("stringtype", "unspecified")
    
      val db = Database.forURL(<db-uri>,
                               driver="org.postgresql.Driver",
                               user=<username>,
                               password=<password>,
                               prop=prop)