In the Slick examples there are a few examples of joining where one of the resulting columns can be nulls, as it can be the case when doing left, right, or outer joins. For
Not the cleanest solution (uses scalaz 7.0.6 and shapeless 2.0.1), but this works for now (Slick 2.0.1):
Using the ?
projection above, it's possible to create a Slick projection that converts the tuple of Option
values => Option[TupleN]
=> Option[YourClass]
.
option
ProjectionNote: sequence
is used to convert a tuple of Option
values to Option[TupleN]
. Code for sequence
is defined at the bottom of this answer.
Add to Suppliers
. Assumes Supplier
is a case class.
import scalaz._, Scalaz._
import SequenceTupleOption._
def option = (id.?, name.?, street.?) <> (optionApply, optionUnapply)
def optionApply(t: (Option[Int], Option[String], Option[String])): Option[Comment] = {
sequence(t).map(Supplier.tupled)
}
def optionUnapply(oc: Option[Supplier]): Option[(Option[Int], Option[String], Option[String])] = None
option
Projectionval explicitLeftOuterJoin = for {
(c, s) <- Coffees leftJoin Suppliers on (_.supID === _.id)
} yield (c, s.option)
sequence
, Converts Tuple of Option
Values to Option[TupleN]
This is the hard part that Travis Brown wrote. sequence
converts from a tuple of Option
values to a Option[TupleN]
(using scalaz and shapeless).
import scalaz._, Scalaz._
import shapeless._, ops.hlist.{ RightFolder, Tupler }
object SequenceTupleOption {
object applicativeFolder extends Poly2 {
implicit def caseApplicative[A, B <: HList, F[_]](implicit
app: Applicative[F]
) = at[F[A], F[B]] {
(a, b) => app.ap(a)(app.map(b)(bb => (_: A) :: bb))
}
}
def sequence[T, EL <: HList, L <: HList, OL <: HList, OT](t: T)(implicit
gen: Generic.Aux[T, EL],
eq: EL =:= L,
folder: RightFolder.Aux[L, Option[HNil], applicativeFolder.type, Option[OL]],
tupler: Tupler.Aux[OL, OT]
): Option[OT] =
eq(gen.to(t)).foldRight(some(HNil: HNil))(applicativeFolder).map(tupler(_))
}
Usage for sequence
:
import scalaz._, Scalaz._
import SequenceTupleOption._
case class Person(id: Int, name: String, age: Int)
val t = (Option(1), Option("Bob"), Option(40))
val person: Option[Person] = sequence(t).map(Person.tupled) // Some(Person(1,Bob,40))
High-level overview of what sequence
does (not proper types):
Option
to an shapeless HList[Option[_]]
.sequence
over the HList[Option[_]]
to a Option[HList[_]]
HList
back to a tuple.