Search code examples
scalarefactoringslickslick-2.0scalaquery

Slick: CRUD extension: How to encapsulate implicit mapping:BaseColumnType[T]


There is the following API for Slick CRUD (Slick-2.1.0, Scala-2.11.4):

trait HasId {
  type Id
  def id: Option[Id]
}
trait HasIdColumn[E <: HasId] {
  def id: scala.slick.lifted.Column[E#Id]
}
trait SlickExtensions {
    val driver: scala.slick.driver.JdbcProfile
    import driver.simple._

    trait BaseDAO[T <: Table[E], E] {
      val query: TableQuery[T]
    }
    trait HasIdActions[T <: Table[E] with HasIdColumn[E], E <: HasId] 
      extends BaseDAO[T, E] {
      //line L1: this implicit val is needed to execute query.filter(_.id === id)
      // what can be done in order to save the user from the necessity 
      // to override this value?
      implicit val mappingId: BaseColumnType[E#Id]

      def findById(id: E#Id)(implicit session: Session): Option[E] = 
          query.filter(_.id === id).firstOption
      ...
    }
}

I apply this SlickExtensions as follows:

case class Entity(id: Option[Long] = None, value: String) extends HasId { 
   type Id = Long }

trait EntityComponent extends SlickExtensions {
    import driver.simple._

    class EntitiesTable(tag: Tag) extends Table[Entity](tag, "entities") 
     with HasIdColumn[Entity] {
      def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
      def value = column[String]("value", O.NotNull)
      def * = (id.?, value) <>(Entity.tupled, Entity.unapply)
    }

    object Entities extends HasIdActions[EntitiesTable, Entity] {
      val query = TableQuery[EntitiesTable]
      /* line L2: from slick library: ImplicitColumnTypes */
      override implicit val mappingId = driver.simple.longColumnType
    }
  }

End point to execute queries:

val c = new EntityComponent {
    lazy val driver = play.api.db.slick.Config.driver
}

db.withSession { implicit session =>
    c.Entities.findById(1) foreach println
}

The main question is how to get rid of "implicit val mappingId" overriding in line L2?

I tried to create a class:

abstract class IdImplicits[E<:HasId](implicit val mappingId:BaseColumnType[E#Id])

and inherited it as follows:

object Entities extends IdImplicits[EntitiesTable, Entity] 
   with HasIdActions[EntitiesTable, Entity] {
      val query = TableQuery[EntitiesTable]
      //override implicit val mappingId: driver.simple.longColumnType
}

However it seems to me that such approach is redundant. It would be great if I could hide "implicit val mappingId" inside SlickExtensions.

Here is the link to the same question


UPD:

In my project, I'd like to add HasName, HasValue[V] and some other mixins to construct the following DAOs:

object EntitiesDAO extends HasIdActions 
  with HasNameActions 
  with HasValueActions with NameToIdActions with ValueToIdActions {
    ...
    override def nameToId(name:String):Option[E#Id]
    override def valueToId(value:E#ValueType):Option[E#Id]
    ...
}

It leads to the following problems:

1) implicits for BaseColumnTypes, mentioned in my topic, should be taken into consideration for HasId, HasValue mixins

2) If implicits BaseColumnTypes are used as parameters of constructor of abstract classes then these classes can't be mixed in one EntityDAO object (the problem is described here).

3) If one abstract class is used for each variant of EntityDAO, then we get ugly-looking combinations, for example:

abstract class IdValueNameImplicits[E <: HasId with HasValue with HasName]
     (implicit val idMapper:BaseColumnType[E#Id], 
      implicit val valueMapper:BaseColumnType[E#ValueType])

Solution

  • You can't do that because you are inside a trait and E#Id is only defined when you have a concrete implementation of it.

    As you already discovered, you have to define your BaseColumnType when your trait is implemented because only then you have a defined type for E#Id.

    Another option is not to have a trait but an abstract class where you can have a implicit BaseColumnType passed to the constructor.

    I have a small project that does exactly what you are looking for. You can find it here: https://github.com/strongtyped/active-slick

    There is also an Activator template. http://typesafe.com/activator/template/slick-active-record

    You can use it as is or as inspiration for your own.

    Have fun!