Slick: расширение CRUD: как инкапсулировать неявное сопоставление: BaseColumnType [T]

Существует следующий API для 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
      ...
    }
}

Я применяю этот SlickExtensions следующим образом:

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
    }
  }

Конечная точка для выполнения запросов:

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

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

Главный вопрос заключается в том, как избавиться от переопределения «неявного val mappingId» в строке L2?

Я попытался создать класс:

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

и унаследовал его следующим образом:

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

Однако мне кажется, что такой подход избыточен. Было бы здорово, если бы я мог скрыть «неявный val mappingId» внутри SlickExtensions.

Вот ссылка на тот же вопрос


ОБНОВЛЕНИЕ:

В моем проекте я хотел бы добавить HasName, HasValue[V] и некоторые другие примеси для создания следующих DAO:

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]
    ...
}

Это приводит к следующим проблемам:

1) имплициты для BaseColumnTypes, упомянутые в моей теме, должны учитываться для миксинов HasId, HasValue

2) Если в качестве параметров конструктора абстрактных классов используются неявные BaseColumnTypes, то эти классы нельзя смешивать в одном объекте EntityDAO (проблема описано здесь).

3) Если для каждого варианта EntityDAO используется один абстрактный класс, то мы получаем некрасивые комбинации, например:

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

person John Mullins    schedule 24.11.2014    source источник


Ответы (1)


Вы не можете этого сделать, потому что вы находитесь внутри трейта, а E#Id определяется только тогда, когда у вас есть его конкретная реализация.

Как вы уже обнаружили, вы должны определить свой BaseColumnType, когда ваш трейт реализуется, потому что только тогда у вас есть определенный тип для E#Id.

Другой вариант — иметь не трейт, а абстрактный класс, в котором конструктору можно передать неявный BaseColumnType.

У меня есть небольшой проект, который делает именно то, что вы ищете. Вы можете найти его здесь: https://github.com/strongtyped/active-slick

Также есть шаблон активатора. http://typesafe.com/activator/template/slick-active-record

Вы можете использовать его как есть или в качестве вдохновения для себя.

Повеселись!

person Renato    schedule 26.11.2014
comment
Привет. Спасибо за ответ и за ссылку на ваш проект. Должен признаться, что уже ознакомился с вашим проектом и даже позаимствовал некоторые ваши идеи. Из-за ограниченного размера комментария я добавил дополнительную информацию по этому вопросу в свою основную тему (после метки UPD). - person John Mullins; 27.11.2014