Search code examples
scalaslick

How to use the mapped projection * <>


In slick you write a projection that:

defines how the columns are converted to and from the Person object.

the default format is:

def * = (id, name, age) <> ((Person.apply _).tupled, Person.unapply)

We can use it to directly map classes/tuples to database tables.
Can you use it to alter values as part of the conversion?

E.g. purely as an example, could you set a constant value in the Person object, but ignore it in the database? Or map the name as a String in the database, and as an enum in the Person object?


Solution

  • The following code compiles

    import slick.jdbc.PostgresProfile.api._
    
    trait Name
    object Name {
      case class Ordinary(s: String) extends Name
      case class Manager(s: String) extends Name
      case object NoName extends Name
    }
    
    case class Person(id: Long, name: Name, age: Int, isValid: Boolean)
    
    class Persons(tag: Tag) extends Table[Person](tag, "persons") {
      def id = column[Long]("id", O.PrimaryKey)
      def name = column[String]("name")
      def age = column[Int]("age")
    
      def * = (id, name, age) <> ({
        case (l, "NoName", i)                  => Person(l, Name.NoName, i, true)
        case (l, s, i) if s.startsWith("Mgr.") => Person(l, Name.Manager(s.stripPrefix("Mgr.")), i, true)
        case (l, s, i)                         => Person(l, Name.Ordinary(s), i, true)
      }, (p: Person) => p.name match {
        case Name.Ordinary(s) => Some((p.id, s, p.age))
        case Name.Manager(s)  => Some((p.id, "Mgr." + s, p.age))
        case Name.NoName      => Some((p.id, "NoName", p.age))
      })
    }
    

    Here we set isValid to be a constant value and map name to enum.