I am using play 2.3 with slick 2.1
I have two related entities - Message
and User
(a simplified example domain). Messages are written by users.
A recommended way (the only way?) of expressing such a relation is by using explicit userId
in Message
My classes and table mappings look like this:
case class Message (
text: String,
userId: Int,
date: Timestamp = new Timestamp(new Date().getTime()),
id: Option[Int] = None) {}
case class User (
userName: String,
displayName: String,
passHash: String,
creationDate: Timestamp = new Timestamp(new Date().getTime()),
lastVisitDate: Option[Timestamp] = None,
// etc
id: Option[Int] = None){}
class MessageTable(tag: Tag) extends Table[Message](tag, "messages") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def text = column[String]("text")
def userId = column[Int]("user_id")
def date = column[Timestamp]("creation_date")
def * = (title, text, userId, date, id.?) <> (Post.tupled, Post.unapply
def author = foreignKey("message_user_fk", userId, TableQuery[UserTable])(_.id)
}
class UserTable(tag: Tag) extends Table[User](tag, "USER") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def username = column[String]("username")
def passHash = column[String]("password")
def displayname = column[String]("display_name")
def * = (username, passHash,created, lastvisit, ..., id.?) <> (User.tupled, User.unapply)
}
And a convenient helper object:
object db {
object users extends TableQuery(new UserTable(_)) {
// helper methods, compiled queries
}
object messages extends TableQuery(new MessageTable(_)) {
// helper methods, compiled queries
}
}
Now, this is all perfect internally, but if I want to display the actual message, I want the message class to be able to return it's author name when used in templates.
Here are my considerations:
- I don't want (and I wasn't able to anyway) to pass around implicit slick
Session
where it doesn't belong - like templating engine and model classes - I'd like to avoid requesting additional data for messages one-by-one in this particular case
I am more familiar with Hibernate than Slick; in Hibernate I'd use join-fetching. With Slick, the best idea I came up with is to use another data holder class for display:
class LoadedMessage (user:User, message:Message) {
// getters
def text = message.text
def date = message.date
def author = user.displayName
}
object LoadedMessage {
def apply( u:User , m:Message ) = new LoadedMessage(u, m)
}
and populate it with results of join query:
val messageList: List[LoadedMessage] = (
for (
u <- db.users;
m <- db.messages if u.id === m.userId
) yield (u, m))
.sortBy({ case (u, m) => m.date })
.drop(n)
.take(amount)
.list.map { case (u, m) => LoadedMessage(u, m) }
and then pass it wherever. My concern is with this extra class - not very DRY, so unnecessary conversion (and doesn't seem I can make it implicit), much boilerplate.
What is the common approach? Is there a way to cut on extra classes and actually make a model able to return it's associations?