0

Let's say I have a case class called Blarg consisting of properties with primitive types:

case class Blarg(
  x: String,
  y: Int
)

Blargs are used in various classes, sometimes as Option[Blarg], sometimes as List[Blarg], and those classes are persisted. We also have a case class called OptionalComplexThing containing a property of type Option[Blarg]:

case class OptionalComplexThing(
  one: String,
  maybeInt: Option[Int],
  maybeBlarg: Option[Blarg],
  id: Option[Long] = None
)

The problem is how to create a default MappedProjection containing the appropriate sub-projection. Notice the blarg MappedProjection below. I want to be able to transform the blarg MappedProjection so all its Columns are Options. I don't want to hard-code this example - there should be a generic way to do this using combinators.

class _OptionalComplexThings(tag: Tag) extends Table[OptionalComplexThing](tag, "optional_complex_thing") {
  def one      = column[String]("one")
  def maybeInt = column[Option[Int]]("maybe_int") // specifying O.Nullable makes no difference
  def id       = column[Option[Long]]("id", O.PrimaryKey, O.AutoInc)

  // Blarg fields need to have their lifted types remapped according to MappedProjection usage.
  // Notice OptionalComplexThing's maybeBlarg property is an Option[Blarg].
  def x   = column[String]("x")  // want to map to column[Option[String]]("x")
  def y   = column[Int]("y")     // want to map to column[Option[String]]("y")

  def blarg = (x, y) <> (Blarg.tupled, Blarg.unapply)

  def * = (one, maybeInt, Option(blarg), id) <> (OptionalComplexThing.tupled, OptionalComplexThing.unapply)

  /* Error:(23, 46) No matching Shape found.
  Slick does not know how to map the given types.
  Possible causes: T in Table[T] does not match your * projection. Or you use an unsupported type in a Query (e.g. scala List).
    Required level: scala.slick.lifted.FlatShapeLevel
       Source type: (scala.slick.lifted.Column[String], scala.slick.lifted.Column[Option[Int]], Option[scala.slick.lifted.MappedProjection[Blarg,(String, Int)]], scala.slick.lifted.Column[Option[Long]])
     Unpacked type: (String, Option[Int], Option[Blarg], Option[Long])
       Packed type: Any
    def * = (one, maybeInt, Option(blarg), id) <> (OptionalComplexThing.tupled, OptionalComplexThing.unapply)
                                               ^ */
}

Here is a a GitHub project that demonstrates the question.

Mike Slinn
  • 7,705
  • 5
  • 51
  • 85

1 Answers1

0

You could write your custom versions of tupled/unapply methods:

case object Blarg {
  def fromOptions(maybeX: Option[String], maybeY: Option[Int]): Option[Blarg] = {
    (maybeX, maybeY) match {
      case (Some(x), Some(y)) => Some(Blarg(x, y))
      case _ => None
    }
  }

  def customTupled = (fromOptions _).tupled
  def customUnapply(arg: Option[Blarg]): Option[(Option[String], Option[Int])] = arg.map { blarg =>
    (Some(blarg.x), Some(blarg.y))
  }
}

class _OptionalComplexThings(tag: Tag) extends Table[OptionalComplexThing](tag, "optional_complex_thing") {
  def one      = column[String]("one")
  def maybeInt = column[Option[Int]]("maybe_int")
  def id       = column[Option[Long]]("id", O.PrimaryKey, O.AutoInc)
  def maybeX   = column[Option[String]]("x")
  def maybeY   = column[Option[Int]]("y")

  def blarg = (maybeX, maybeY) <> (Blarg.customTupled, Blarg.customUnapply)
  def * = (one, maybeInt, blarg, id) <> (OptionalComplexThing.tupled, OptionalComplexThing.unapply)
}

This might not be the shortest possible solution, but should work.

Paweł Jurczenko
  • 4,431
  • 2
  • 20
  • 24
  • This is the hand-crafted approach that I would like to avoid. The problem with this approach is that someone must maintain it, and as projects move into the future, people leave and case class properties change. Instead, I would like to use combinators. How could something similar be achieved without hand-crafting Blarg.fromOptions and customUnapply? – Mike Slinn May 14 '16 at 03:05