How to use Slick's mapped tables with foreign keys?

database, orm, persistence, scala, slick

Solution

The short answer is: Use ids instead of object references and use Slick queries to dereference ids. You can put the queries into methods for re-use.

That would make your case classes look like this:

case class Task(id: Option[Long], title: String, nextTaskId: Option[Long])
case class TaskList(label: String, firstTaskId: Option[Long])

I'll publish an article about this topic at some point and link it here.

Problem

I'm struggling with Slick's lifted embedding and mapped tables. The API feels strange to me, maybe just because it is structured in a way that's unfamiliar to me. I want to build a Task/Todo-List. There are two entities: - Task: Each task has a an optional reference to the next task. That way a linked list is build. The intention is that the user can order the tasks by his priority. This order is represented by the references from task to task. TaskList: Represents a TaskList with a label and a reference to the first Task of the list. `case class Task(id: Option[Long], title: String, nextTask: Option[Task])` `case class TaskList(label: String, firstTask: Option[Task])` Now I tried to write a data access object (DAO) for these two entities. ``` import scala.slick.driver.H2Driver.simple._ import slick.lifted.MappedTypeMapper implicit val session: Session = Database.threadLocalSession val queryById = Tasks.createFinderBy( t => t.id ) def task(id: Long): Option[Task] = queryById(id).firstOption private object Tasks extends Table[Task]("TASKS") { def id = column[Long]("ID", O.PrimaryKey, O.AutoInc) def title = column[String]("TITLE") def nextTaskId = column[Option[Long]]("NEXT_TASK_ID") def nextTask = foreignKey("NEXT_TASK_FK", nextTaskId, Tasks)(_.id) def * = id ~ title ~ nextTask <> (Task, Task.unapply _) } private object TaskLists extends Table[TaskList]("TASKLISTS") { def label = column[String]("LABEL", O.PrimaryKey) def firstTaskId = column[Option[Long]]("FIRST_TASK_ID") def firstTask = foreignKey("FIRST_TASK_FK", firstTaskId, Tasks)(_.id) def * = label ~ firstTask <> (Task, Task.unapply _) } ``` Unfortunately it does not compile. The problems are in the `*` projection of both tables at `nextTask` respective `firstTask`. - `could not find implicit value for evidence parameter of type scala.slick.lifted.TypeMapper[scala.slick.lifted.ForeignKeyQuery[SlickTaskRepository.this.Tasks.type,justf0rfun.bookmark.model.Task]]` - `could not find implicit value for evidence parameter of type scala.slick.lifted.TypeMapper[scala.slick.lifted.ForeignKeyQuery[SlickTaskRepository.this.Tasks.type,justf0rfun.bookmark.model.Task]]` I tried to solve that with the following TypeMapper but that does not compile, too. ``` implicit val taskMapper = MappedTypeMapper.base[Option[Long], Option[Task]]( option => option match { case Some(id) => task(id) case _ => None }, option => option match { case Some(task) => task.id case _ => None }) ``` - `could not find implicit value for parameter tm: scala.slick.lifted.TypeMapper[Option[justf0rfun.bookmark.model.Task]]` - `not enough arguments for method base: (implicit tm: scala.slick.lifted.TypeMapper[Option[justf0rfun.bookmark.model.Task]])scala.slick.lifted.BaseTypeMapper[Option[Long]]. Unspecified value parameter tm.` Main question: How to use Slick's lifted embedding and mapped tables the right way? How to I get this to work? Thanks in advance.

Original source