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