Slick: расширение CRUD: как инкапсулировать неявное отображение:BaseColumnType[T]
Для Slick CRUD существует следующий API (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.
UPD:
В моем проекте я хотел бы добавить 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])
1 ответ
Вы не можете сделать это, потому что вы находитесь внутри черты, а E#Id определяется только тогда, когда у вас есть конкретная реализация этого.
Как вы уже обнаружили, вы должны определить свой BaseColumnType, когда ваша черта реализована, потому что только тогда у вас есть определенный тип для E#Id.
Другой вариант - не иметь признак, а абстрактный класс, в котором вы можете передать неявный BaseColumnType в конструктор.
У меня есть небольшой проект, который делает именно то, что вы ищете. Вы можете найти его здесь: https://github.com/strongtyped/active-slick
Также есть шаблон Активатора. http://typesafe.com/activator/template/slick-active-record
Вы можете использовать это как есть или как вдохновение для своего собственного.
Повеселись!