6

For example I have this case class:

case class User(
  var identityId: IdentityId, //Its a user created class
  var firstName: String,
  var lastName: String,
  var fullName: String,
  var email: Option[String],
  var avatarUrl: Option[String],
  var authMethod: AuthenticationMethod,
  var oAuth1Info: Option[OAuth1Info] = None,
  var oAuth2Info: Option[OAuth2Info] = None,
  var passwordInfo: Option[PasswordInfo] = None) extends Identity {

  def this() = this(null, "", "", "", None, None, null, None, None, None)

}

Its actually a securesocial identity, now identityId is an object of case class:

case class IdentityId(
var userId:String,
var providerId:String
)

So how do I create a projection class for situations like, this?? If I had to create a projection class datatypes like String,Int it would have been no problem but what about user defined objects and classes??

mane
  • 1,149
  • 16
  • 41
  • You make the field identityId a foreign key to a IdentityId onject. – Ende Neu Aug 16 '14 at 12:10
  • You mean create a different table for IdentityId, and add a foreign key in Identity case class?? No can't do since, Identity is a default class from SecureSocial lib, and I cant change that. Is there no other way to deal with nested case classes?? – mane Aug 16 '14 at 12:13

1 Answers1

7

I did some research and found some very useful answers here Link1 and Link2

The second link didn't have implementations for PasswordInfo so, here I have provided my own implementation, though this approach seems a bit more code, can anyone suggest a more efficient approach that is if there is one:

*Note: And the second link was very helpful, its actually whole working project example Based on Play, Slick and SecureSocial thanks to Lunatech and others.

class Users(tag: Tag) extends Table[User](tag, "user") {

  implicit def string2AuthenticationMethod = MappedColumnType.base[AuthenticationMethod, String](
    authenticationMethod => authenticationMethod.method,
    string => AuthenticationMethod(string))

  implicit def tuple2OAuth1Info(tuple: (Option[String], Option[String])): Option[OAuth1Info] = tuple match {
    case (Some(token), Some(secret)) => Some(OAuth1Info(token, secret))
    case _ => None
  }

  implicit def tuple2OAuth2Info(tuple: (Option[String], Option[String], Option[Int], Option[String])): Option[OAuth2Info] = tuple match {
    case (Some(token), tokenType, expiresIn, refreshToken) => Some(OAuth2Info(token, tokenType, expiresIn, refreshToken))
    case _ => None
  }

  implicit def tuple2PasswordInfo(tuple: (Option[String], Option[String], Option[String])) = tuple match {
    case (Some(hasher), Some(password), salt) =>
      Some(PasswordInfo(hasher, password, salt))
    case _ => None
  }

  implicit def tuple2IdentityId(tuple: (String, String)): IdentityId = tuple match {
    case (userId, providerId) => IdentityId(userId, providerId)
  }

  def uid = column[Long]("id", O.PrimaryKey, O.AutoInc)
  def userId = column[String]("userId")
  def providerId = column[String]("providerId")
  def email = column[Option[String]]("email")
  def firstName = column[String]("firstName")
  def lastName = column[String]("lastName")
  def fullName = column[String]("fullName")
  def authMethod = column[AuthenticationMethod]("authMethod")
  def avatarUrl = column[Option[String]]("avatarUrl")
  // oAuth 1
  def token = column[Option[String]]("token")
  def secret = column[Option[String]]("secret")
  // oAuth 2
  def accessToken = column[Option[String]]("accessToken")
  def tokenType = column[Option[String]]("tokenType")
  def expiresIn = column[Option[Int]]("expiresIn")
  def refreshToken = column[Option[String]]("refreshToken")
  //PasswordInfo
  def hasher = column[Option[String]]("hasher")
  def password = column[Option[String]]("password")
  def salt = column[Option[String]]("salt")

  def * : ProvenShape[User] = {
    val shapedValue = (
      userId,
      providerId,
      firstName,
      lastName,
      fullName,
      email,
      avatarUrl,
      authMethod,
      token,
      secret,
      accessToken,
      tokenType,
      expiresIn,
      refreshToken,
      hasher,
      password,
      salt).shaped
    shapedValue.<>({
      tuple =>
        User.apply(
          identityId = tuple2IdentityId(tuple._1, tuple._2),
          firstName = tuple._3,
          lastName = tuple._4,
          fullName = tuple._5,
          email = tuple._6,
          avatarUrl = tuple._7,
          authMethod = tuple._8,
          oAuth1Info = (tuple._9, tuple._10),
          oAuth2Info = (tuple._11, tuple._12, tuple._13, tuple._14),
          passwordInfo = (tuple._15, tuple._16, tuple._17))
    }, {
      (u: User) =>
        Some {
          (u.identityId.userId,
            u.identityId.providerId,
            u.firstName,
            u.lastName,
            u.fullName,
            u.email,
            u.avatarUrl,
            u.authMethod,
            u.oAuth1Info.map(_.token),
            u.oAuth1Info.map(_.secret),
            u.oAuth2Info.map(_.accessToken),
            u.oAuth2Info.flatMap(_.tokenType),
            u.oAuth2Info.flatMap(_.expiresIn),
            u.oAuth2Info.flatMap(_.refreshToken),
            u.passwordInfo.map(_.hasher),
            u.passwordInfo.map(_.password),
            u.passwordInfo.flatMap(_.salt))
        }
    })
  }
}
mane
  • 1,149
  • 16
  • 41
  • 3
    Personally, I think this is a major short-coming of Slick. This is very much a manual row-mapping/projection with the added burden of creating a `Table` class. I think `Table` should support mapping multiple columns to a single type (if possible). – Muel Aug 29 '14 at 16:50
  • I understand there is no way to automate or auto-generate this or any other slick classes definition from a regular case class.... Does the benefit of using slick queries over the data make it worth delving into crafting all of this boilerplate for you??! – matanster Feb 13 '15 at 19:27
  • 1
    You can have multiple, projections, each using the <> operators to map certain columns to case classes and then refer to existing ones in others. This way you can easily build nested case classes as long as all fields are in the same tables. Multiple examples on the mailing list. If they are not in the same table, read this: http://slick.typesafe.com/doc/2.1.0/orm-to-slick.html – cvogt Feb 14 '15 at 22:27