Synesso
Synesso

Reputation: 39018

Include case class in another in lifted mapping

I want to include a rich type (case class) as a member of another whilst using SLICK lifted mapping. I'm not sure how.

This is what I have so far. (It follows the cake pattern for multiple DB support).

trait ChallengeComponent { this: Profile with UserComponent with GameComponent =>
  import profile.simple._

  class Challenges(tag: Tag) extends Table[Challenge](tag, "challenges") {
    def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
    def challengerId = column[String]("challenger_id")
    def * = (id, challengerId) <> (constructChallenge, extractChallenge)
    def challenger = foreignKey("challenger_fk", challengerId, users)(_.gPlusId)
  }

  private def constructChallenge(id: Option[Int], challengerId: String) =
    Challenge(id, user(challengerId).get)
  private def extractChallenge(c: Challenge) = (c.id, c.challenger.id)

  ...
}

case class Challenge(id: Option[Int], challenger: User)

where the UsersComponent trait is defined as:

trait UserComponent { this: Profile =>
  import profile.simple._

  class Users(tag: Tag) extends Table[User](tag, "users") {
    def id = column[String]("id", O.PrimaryKey)
    def displayName = column[String]("display_name", O.NotNull)
    def * = (gPlusId, displayName)
  }
  val users = TableQuery[Users]

  def user(gPlusId: String)(implicit session: Session): Option[User] =
    users.filter(_.id === id).firstOption

The problem is that there is no implicit Session available when trying to construct the User.

What is a workable pattern for embedding one type in another? Preferably in a lazy fashion, but without polluting the case class with persistence code.

Upvotes: 0

Views: 363

Answers (1)

wongelz
wongelz

Reputation: 119

You can't do that using the * projection. You want case class that map directly to your tables:

case class User(id: String, displayName: String)
case class Challenge(id: Option[Int], challengerId: String)

class Users(tag: Tag) extends Table[User](tag, "users") {
  def id = column[String]("id", O.PrimaryKey)
  def displayName = column[String]("display_name", O.NotNull)
  def * = (id, displayName) <> (User.tupled, User.unapply)
}

class Challenges(tag: Tag) extends Table[Challenge](tag, "challenges") {
  def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
  def challengerId = column[String]("challenger_id")
  def * = (id, challengerId) <> (Challenge.tupled, Challenge.unapply)
}

Then in your example, what you're really after is a query that joins 2 tables:

def getChallengeAndChallenger(id: Int): Option[(Challenge, User)] = db withSession {
  implicit session =>
    val q = for {
      c <- challenges if c.id === id
      u <- users if u.id === c.challengerId
    } yield (c, u)
    q.firstOption
}

Of course you can then replace (map) the tuple to another case class if you wish.

Upvotes: 2

Related Questions