How to better parse the same table twice with Anorm?
Asked Answered
O

0

10

Suppose I have the following two classes:

case class User(id: Long, name: String)

case class Message(id: Long, body: String, to: User, from: User)

The RowParsers might look something like this:

val parser: RowParser[User] = {
    get[Long]("users.id") ~ get[String]("users.name") map {
        case id~name => User(id, name)
    }
}

val parser: RowParser[Message] = {
    get[Long]("messages.id") ~ 
    get[String]("messages.name") ~
    User.parser ~
    User.parser map {
        case id~body~to~from => Message(id, body, to, from)
    }
}

Except that this won't work, because when joining the users table twice, Anorm will only parse the first joined columns (twice), so that to and from will be the same.

This can be overcome using column aliases in the User parser:

def parser(alias: String): RowParser[User] = {
    getAliased[Long](alias + "_id") ~ getAliased[String](alias + "_name") map {
        case id~name => User(id, name)
    }
}

Then using different aliases for each User in the Message parser:

val parser: RowParser[Message] = {
    get[Long]("messages.id") ~ 
    get[String]("messages.name") ~
    User.parser("to") ~
    User.parser("from") map {
        case id~body~to~from => Message(id, body, to, from)
    }
}

This is okay for simple scenarios, but what if User is more complicated and has an Address with a State and Country, etc? That would require aliasing multiple tables and parsers, which gets quite messy.

Is there a better way to accomplish this? That is, I'm looking to select all of the relevant data in one query, and parse it using parser combinators, without a massive column aliasing scheme.

Obey answered 26/12, 2014 at 4:6 Comment(8)
Don't think there is other way than aliasing, as join can fetch different 'entities' from a table used several time in a query, each need to be made distinct with aliases.Burgoyne
You don't really need your method, it's possibly more compact just to write: getAliased[String]("to_name") ~ etc, directly in the parser?Indention
@Indention But then I would need two parsers, one with to_name and one with from_name, etc. The example is also fairly simplified, the User parser would really have many more fields, and nested parsers.Obey
Ah, point taken. My case was only a single attribute. I think your solution is close to optimal - you are parameterising the alias as it were. (BTW, should users.id and users.name not be message.id and message body?)Indention
Yeah, that was a typo. Thanks. The aliasing solution works okay, but it can get rather messy with nesting. It also makes queries look extremely ugly.Obey
How is the query more ugly, do you want to post an example? (I am going to be facing your situation soon, so interested!). Your def parser basically just replaces val parser for the user injecting the alias, so seems pretty good to me.Indention
Instead of having u1.*, u2.*, I have to enumerate all of the aliases like: u1.id as to_id, u1.name as to_name, u2.id as from_name, u2.name as from_name... which gets unwieldy when more columns are needed. And up until this point I've avoided nesting anything within User to keep the query from exploding in size.Obey
Rather than .*, I already use a variable sqlSelectorFields, so I think I'm going to parameterise this to sqlSelectorFields(alias).Indention

© 2022 - 2024 — McMap. All rights reserved.