Playframework 2.4 slickでManyToMany

引き続き、PlayFramework2.4を学習中。
今回はORM(Object Relation Mapper)について。
2.1頃から、ORMとしてSquerylを使用していたが、今回PlaySlickを試してみることにした。
設定方法は、こちらを参照。利用方法も記載されているが、断片的なコードなので、なかなか動かすのに苦労する。

そんな向きには、こちらのコードがおすすめ。
ただ、OneToManyのサンプルはあるが、ManyToManyは見当たらない。

ManyToManyを自作してみた。
OneToManyであれば、モデルの属性として、リレーションを張りたいモデルの外部キーを設定すればよい。サンプルのCumputersクラスであれば、CompaniesクラスのidをcompanyIdとする。

class Computers(tag: Tag) extends Table[Computer](tag, "Computer") {
    implicit val dateColumnType = MappedColumnType.base[Date, Long](d => d.getTime, d => new Date(d))
    
    def id = column[Long]("ID", O.PrimaryKey, O.AutoInc)
    def name = column[String]("NAME")
    def introduced = column[Option[Date]]("INTRODUCED")
    def discontinued = column[Option[Date]]("DISCONTINUED")
    def companyId = column[Option[Long]]("COMPANY_ID")

ManyToManyの場合、リレーション用クラスが必要。
プロジェクトのメンバーを実装してみる場合

  class Users(tag: Tag) extends Table[User](tag, "User") {
    
    def id = column[Long]("ID", O.PrimaryKey, O.AutoInc)
    def name = column[String]("NAME")
    
    def * = (id.?, name) <> (User.tupled, User.unapply _)
  }
  class Projects(tag: Tag) extends Table[Project](tag, "Project") {
    def id  = column[Long]("ID", O.PrimaryKey, O.AutoInc)
    def name = column[String]("NAME")
    def description = column[String]("DESCRIPTION")
    def * = (id.?, name, description) <> (Project.tupled, Project.unapply _)
  }
  class ProjectUsers(tag: Tag) extends Table[ProjectUser](tag, "ProjectUser") {
    def userId  = column[Long]("USER_ID")
    def projectId = column[Long]("PROJECT_ID")
    def roll = column[Long]("ROLL")
    def * = (userId, projectId, roll) <> (ProjectUser.tupled, ProjectUser.unapply _)
  }

上記のProjectUsersクラスがそれに相当。
ただ、ProjectsクラスとProjectUsersクラス。
UsersクラスとProjectUsersクラス。
それぞれの組み合わせは、OneToManyの関係とみなせば、queryは以下のように記述できる。

class UsersDAO @Inject() (protected val dbConfigProvider: DatabaseConfigProvider) extends ProjectsComponent with ProjectUsersComponent with HasDatabaseConfigProvider[JdbcProfile]{
  import driver.api._
  
  class Users(tag: Tag) extends Table[User](tag, "User") {
    
    def id = column[Long]("ID", O.PrimaryKey, O.AutoInc)
    def name = column[String]("NAME")
    
    def * = (id.?, name) <> (User.tupled, User.unapply _)
  }
  private val users = TableQuery[this.Users]
  private val projects = TableQuery[super.Projects]
  private val projectUsers = TableQuery[super.ProjectUsers]

  def findById(id: Long): Future[User] = db.run(users.filter ( _.id === id ).result.head)
  def count(): Future[Int] = db.run(users.map ( _.id ).length.result)
  def count(filter: String): Future[Int] = db.run(users.filter { user => user.name.toLowerCase like filter.toLowerCase()}.length.result)

  def list(page: Int = 0, pageSize: Int = 10, orderBy: Int = 1, filter: String = "%"): Future[Seq[(User, Option[Project], Option[ProjectUser])]] = {
    val offset = pageSize * page
    val query =
      (for {
        (userAndRelation, project) <- users joinLeft projectUsers on (_.id === _.userId) joinLeft projects on (_._2.map(_.projectId) === _.id)
        if userAndRelation._1.name.toLowerCase like filter.toLowerCase
      } yield (userAndRelation._1, project, userAndRelation._2))
        .sortBy(rows => rows._1.name.asc)
        .drop(offset)
        .take(pageSize)
   for {
      totalRows <- count(filter)
      list = query.result.map { rows => rows.collect { case (user, project, projectUser) => (user, project, projectUser) }}
      result <- db.run(list)

    } yield result
  }

利用側の実装はこちら。
リレーションクラスの属性(roll)も取得可能。

  def listUser() = Action.async{implicit request =>
    val users = usersDao.list(0, 10, 1, "%")
    var result: String = "************* User / Project / Roll ****************\n"
    users.map(us => {us.map(us2 => us2._3.get.roll match {
          case 0L => result = result.+(us2._1.name + " / " + us2._2.get.name + " / " + "コーダー\n")
          case 1L => result = result.+(us2._1.name + " / " + us2._2.get.name + " / " + "リーダー\n") 
          case _ => result = result.+(us2._1.name + " / " + us2._2.get.name + " / " + "無効な資格\n")
      })
    Ok(result)
    })
  }