0

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:

  1. 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
  2. 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?

Community
  • 1
  • 1
enlait
  • 607
  • 1
  • 6
  • 19
  • 1
    Just one consideration, `Hibernate` is an ORM while `Slick` is a layer that allows you to write typesafe queries, you can't expect the same functionalities and in this case in particular you can't expect Slick to fetch objects for you unless you specify that in a query using joins. If you are more into ORMs take a look at [SQueryl](http://squeryl.org/) for example. – Ende Neu Jul 06 '14 at 12:37
  • I don't dislike writing queries or joining tables. I'm more confused with how to represent the results in efficient manner – enlait Jul 06 '14 at 12:50
  • Thanks for the tip, I'll take a look at Squeryl, too. I only started playing around with Play, and almost the whole stack is unfamiliar. – enlait Jul 06 '14 at 13:04

1 Answers1

0

Following my comment:

How to handle join results is a matter of personal taste in my opinion, your approach is what I would also use, you have an ad hoc data structure which encapsulate your data and can be easily passed around (for example in views) and accessed.

Two other approaches which comes to mind are

  1. query for fields instead of objects, it's less legible and I usually hate working with tuples (or triples in this case) because I find the notation _._1 way less legible than MyClass.myField. This means doing something like this:

    val messageList = (
      for (
        u <- db.users;
        m <- db.messages if u.id === m.userId
      ) yield (u.displayName, m.date, m.text))
      .sortBy({ case (name, date, text) => date })
      .drop(n)
      .take(amount)
      .list()
    

    Which will return a triple and then can be passed in your view, something that is possible but I would never do.

  2. Another option is to pass the tuple of objects, very similar to your approach except for the last map

    val messageList: List[(User, Message)] = (
      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()
    

    Here you can pass in the view something like this:

    @(messagesAndAuthors: List[(User, Message)])
    

    and then access the data using tuples and classes access functionality, but again your template would be a collection of _1s and again that is horrible to read, plus you have to do something like messagesAndAuthors._1.name just to get one value.

In the end I prefer passing variables as clean as I can in my views (and I guess this is one of the few universally accepted principle in computer science) and hide the logic used in models, for this using an ad hoc case class is the way to go in my opinion. Case classes are a very powerful tool and it's nice to have it, to you it may looks not DRY and more verbose than other approaches (like Hibernate you talked about) but think that when a developer will read you code it will be as easy as it can get in this moment and that extra small class will save hours of head banging.

For the Session trouble see this related question, at the moment is not possible to avoid passing it around (as far as I know) but you should be able to contain this situation and avoid passing session in your template, I usually create a session in my entry point (most of the time a controller) and pass it to the model.

Note: the code is untested, there may be some mistakes.

Community
  • 1
  • 1
Ende Neu
  • 15,581
  • 5
  • 57
  • 68
  • I use play-slick too, so implicit `Session` in controller isn't much trouble. My initial attempt was to implement lazy loading of associations but I wasn't very successful at passing implicit `Session` in views. It was a horrible idea now that I think about it. I understand that with play and slick, everything must be loaded before I pass it to the view. – enlait Jul 06 '14 at 14:57
  • About your 2nd approach - I can also think of making something like a simple `case class MessageWithAuthor (u:User, m:Message){}` which is less code, but accessing it is not as pretty. Still better than tuples though but I am afraid that it might end in hell like `MessageWithAuthor`, `MessageWithResponses`, `MessageWithParent` etc. Or maybe make `MessageWithAllRelatedStuff`, with related stuff `Option`al - but then I'd lose compile-time check that I actually load things that I use. – enlait Jul 06 '14 at 14:58
  • You could use a case class which encapsulate `User` and `Message` but this won't be any DRYier than your previous approach, instead access would be more verbose (something like `MessageWithAuthor.u.name`). If you really are worried to end up with a case class for each situation, you should use tuples, in the end it's a tradeoff if you need 3 or 4 case classes to do the job, that's what I would do, for me legibility is more important than DRYness (to a certain length), then again if your use case is having all possible combinations, tuples is the way to go. – Ende Neu Jul 06 '14 at 15:17
  • I tried the thing with tuples; it's not all that bad since I can decompose them in templates with for comprehensions and matching. In the end it makes most sense to use tuples for weird combinations and classes for common ones. – enlait Jul 06 '14 at 16:25