How to create a class instance from slick query? - scala

I am finding it difficult to figure out the best wy to create an instance of a class (a DTO class) which can be passed as json to the calling client.
I have the following class structure.
object Suppliers extends Table[(Int, String, String, String, String, String)]("SUPPLIERS") {
def id = column[Int]("SUP_ID", O.PrimaryKey) // This is the primary key column
def name = column[String]("SUP_NAME")
def street = column[String]("STREET")
def city = column[String]("CITY")
def state = column[String]("STATE")
def zip = column[String]("ZIP")
def * = id ~ name ~ street ~ city ~ state ~ zip
}
object Coffees extends Table[(Int,String, Double,Int, Int)]("COFFEES") {
def id = column[Int]("Id",O.PrimaryKey)
def name = column[String]("COF_NAME")
def price = column[Double]("PRICE")
def sales = column[Int]("SALES")
def total = column[Int]("TOTAL")
def * = id ~ name ~ price ~ sales ~ total
}
object CoffeeSuppliers extends Table[(Int,Int,Int)]("CoffeeSuppliers") {
def id = column[Int]("Id",O.PrimaryKey)
def supID = column[Int]("Sup_ID")
def coffeeID = column[Int]("Coffee_ID")
def supplier = foreignKey("SUP_FK", supID, Suppliers)(_.id)
def coffees = foreignKey("COF_FK", coffeeID,Coffees)(_.id)
def * = id ~ supID ~ coffeeID
}
I am using this simple join query to retrieve Supplier with id 101 and all the coffees he supplies.
val q3 = for {
((cs,c),s) <- CoffeeSuppliers innerJoin
Coffees on (_.coffeeID === _.id) innerJoin
Suppliers on (_._1.supID === _.id) if cs.supID === 101
} yield (cs,c,s)
The query is working fine and I am able to retrieve the data.
But, I want to construct a DTO class out of the query result. The Class structure is as fallows
case class CoffeeDTO(
id:Option[Int] = Some(0),
name:String[String] = "",
price:Double= 0.0
)
case class SupplierDTO (
id:Option[Int] = Some(0),
name:String = "",
coffees:List[CoffeeDTO] = Nil
)
How to create an instance of SupplierDTO and assign the values from the query result?

How about something like this:
q3.map{ case (cs,c,s) => ((s.id.?,s.name),(c.id.?,c.name,c.price)) } // remove not needed columns and make ids Options
.list // run query
.groupBy( _._1 ) // group by supplier
.map{ case (s,scPairs) => SupplierDTO( s._1, // map each group to a supplier with associated coffees
s._2,
scPairs.map(_._2) // map group of pairs to only coffee tuples
.map(CoffeeDTP.tupled) // create coffee objects
)}
.head // take just the one supplier out of the list

Related

Slick - many to many relationship

I am working on a Library data model where each book can have multiple authors and vice versa (many to many).
I want to pass a list of books to a html view page that each book includes a list of its author(s).
To do that I have defined the following tables for book and authors:
private class BookTable(tag: Tag) extends Table[Book](tag, "book") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def publishDate = column[Date]("publish_date")
def memberId = column[Option[Long]]("member_id")
def member = foreignKey("member_fk",memberId,members)(_.id)
type Data = (Long, String, Date, Option[Long])
def constructBook: Data => Book = {
case (id, name, publishDate, memberId) =>
Book(id, name, publishDate, memberId)
}
def extractBook: PartialFunction[Book, Data] = {
case Book(id, name, publishDate, memberId, _) =>
(id, name, publishDate, memberId)
}
def * = (id, name, publishDate, memberId) <> (constructBook, extractBook.lift)
}
private class AuthorBookTable (tag: Tag) extends Table[AuthorBook](tag, "author_book") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def authorId = column[Long]("author_id")
def bookId = column[Long]("book_id")
def memberId = column[Option[Long]]("member_id")
def author = foreignKey("author_fk",authorId,authors)(_.id)
def book = foreignKey("book_fk",bookId,books)(_.id)
def * = (id, authorId, bookId) <> ((AuthorBook.apply _).tupled, AuthorBook.unapply)
}
private class AuthorTable (tag: Tag) extends Table[Author](tag, "author") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def * = (id, name) <> ((Author.apply _).tupled, Author.unapply)
}
The book case class is as below:
case class Book(id: Long, name: String, publishDate: Date, memberId: Option[Long] = None, authors: Seq[Author]= Seq.empty)
{
def updateAuthors(authorss: Seq[Author]) = {
this.copy(authors=authorss)
}
}
In controller I use the below:
def getBooks = Action.async { implicit request =>
repo.getBooks.map { books =>
val booksWithAuthors=books.map( b=> {val updateB=b.updateAuthors( repo.getBookAuthors(b.id))
updateB})
Ok(Json.toJson(booksWithAuthors))
}
}
My question is about the getBookAuthors implementation shown below:
implicit def waitForFuture[A](f:Future[A]) = {
def res: A = Await.result(f, Duration.Inf)
res
}
def getBookAuthors(id: Long): Seq[Author] = {
val result=db.run {
val innerJoin = for {
(ab, a) <- authorBooks join authors on (_.authorId === _.id)
} yield (a, ab.bookId)
innerJoin.filter(_._2 === id).sortBy(_._1.name).map(_._1).result
}
waitForFuture(result)
}
My concern is that the getBookAuthors function is blocking and I am not sure if it's the best practice. Please advise if there is a better way to do this.
As you are saying, blocking methods are pretty bad in this context and you will lost the advantages of using a non-blocking library as Slick.
the getBookAuthors would be written as follows, returning a Future[Seq[Author]] thats needs to be managed in the caller
def getBookAuthors(id: Long): Future[Seq[Author]] =
db.run {
val innerJoin = for {
(ab, a) <- authorBooks join authors on (_.authorId === _.id)
} yield (a, ab.bookId)
innerJoin.filter(_._2 === id).sortBy(_._1.name).map(_._1).result
}
So the caller should be rewritten as:
def getBooks = Action.async { implicit request =>
repo.getBooks.flatMap { books =>
Future.sequence(
books.map { b =>
repo.getBookAuthors(b.id).map(authors => b.updateAuthors(authors))
}
).map { booksWithAuthors =>
Ok(Json.toJson(booksWithAuthors))
}
}
}
This means that, once you will have the books: Seq[Book] you will map over it to integrate the authors and this will end with a Seq[Future[Book]].
Then it can be transformed into a Future[Seq[Book]] (with authors) with the Future.sequence method.
Finally you need to flatMap on the outer Future to move from Future[Future[Seq[Book]]] to a simpler Future[Seq[Book]]
This second snippet can be refactored in a more clean way taking advantage of the for-comprehension that is a syntactic sugar for the flatMap
private def addAuthorsToBooks(books: Seq[Book]): Future[Seq[Book]] =
Future.sequence(
books.map { b =>
repo.getBookAuthors(b.id).map(authors => b.updateAuthors(authors))
}
)
def getBooks = Action.async { implicit request =>
for {
books <- repo.getBooks
booksWithAuthors <- addAuthorsToBooks(books)
} yield Ok(Json.toJson(booksWithAuthors))
}

Slick join two tables and get result of both

I have a Many to Many relationship setup like this:
Person <-> PersonField <-> Field
Now I want to query not only all the fields of a Person (I can do that), but a joined version of PersonField with Field of a Person. (I want to query/retrieve the Information in the Pivot/Intermediate Table "PersonField" as well!)
Person:
case class Person(id: Long, name: String)
{
def fields =
{
person <- Persons.all.filter(_.id === this.id)
field <- person.fields
} yield field
}
class Persons(tag: Tag) extends Table[Person](tag, "persons")
{
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def * = (id, name) <> (Person.tupled, Person.unapply)
def fields = PersonFields.all.filter(_.personID === id).flatMap(_.fieldFK)
}
object Persons
{
lazy val all = TableQuery[Persons]
}
Field:
case class Field(id: Long, name: String, description: Option[String])
class Fields(tag: Tag) extends Table[Field](tag, "fields")
{
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def description = column[Option[String]]("description")
def * = (id, name, description) <> (Field.tupled, Field.unapply)
}
object Fields
{
lazy val all = TableQuery[Fields]
}
PersonField:
case class PersonField(id: Long, personID: Long, fieldID: Long, value: String)
// TODO add constraint to make (personID, fieldID) unique
class PersonFields(tag: Tag) extends Table[PersonField](tag, "person_field")
{
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def personID = column[Long]("person_id")
def fieldID = column[Long]("field_id")
def value = column[String]("value")
def * = (id, personID, fieldID, value) <> (PersonField.tupled, PersonField.unapply)
def personFK = foreignKey("person_fk", personID, Persons.all)(_.id)
def fieldFK = foreignKey("field_fk", fieldID, Fields.all)(_.id)
}
object PersonFields
{
lazy val all = TableQuery[PersonFields]
}
Now to query all the fields of a Person I have a little helper-class:
def getFields(p: Person): Future[Seq[Field]] =
{
val query = p.fields
db.run(query.result)
}
So I can do
val personX ...
personX.onSuccess
{
case p: Person =>
{
val fields = helper.getFields(p)
fields.onSuccess
{
case f: Seq[Field] => f foreach println
}
}
}
Now each field of personX gets printed to the console. Works like a charm.
The thing is, I want to get the PersonField as well (with the Field)!
So I tried the following changes (among others that didn't work, which I can't remember)
In Person:
def fields =
{
for
{
person <- Persons.all.filter(_.id === this.id)
field <- person.fields join Fields.all on (_.fieldID === _.id)
} yield field
}
In PersonS
def fields = PersonFields.all.filter(_.personID === id) // No flatMap here!
then getFields(p: Person) looks like this:
def getFields(p: Person): Future[Seq[(PersonField, Field)]]
but
personX.onSuccess
{
case p: Person =>
{
val fields = helper.getFields(p)
fields.onSuccess
{
case f: Seq[(PersonField, Field)] => f map(f => println(f._1)}
}
}
}
gives me nothing, so I guess my join must be wrong. But what exactly am I doing wrong?
You can join all three, then yield the result
for {
((personField, person), field) <- PersonFields.all join Persons.all on (_.personId === _.id) join Fields.all on (_._1.fieldId === _.id)
if person.id === this.id
} yield (personField, person, field)
(I am not sure I got exactly what you were trying to get out of the query, so you can just edit the yield part )

Group By including empty rows

I want to create a query that returns all groups with users count (including empty groups)
SELECT g.id, count(relation.user_id) FROM groups g
FULL JOIN users2groups relation ON g.id=r.group_id
GROUP BY g.id;
for this model:
class Users(tag: Tag) extends Table[User](tag, "users") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
...
}
class Groups(tag: Tag) extends Table[Group](tag, "groups") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
...
}
val users = TableQuery[Users]
val groups = TableQuery[Groups]
/** relation for users and group */
class Users2Groups(tag: Tag) extends Table[(Long,Long)](tag, "users2groups") {
def userId = column[Long]("user_id")
def groupId = column[Long]("group_id")
def user = foreignKey("user_fk", userId, users)(_.id)
def group = foreignKey("group_fk", groupId, groups)(_.id)
def * = (userId, groupId)
def ? = (userId.?, groupId.?)
def pk = primaryKey("pk_user2group", (userId, userId))
}
This is my solution with slick:
val query = for {
(g, rel) <-
groups leftJoin
users2groups on (_.id === _.groupId)
} yield (g, rel.groupId.?)
val result = query.groupBy(_._1.id).map(e => (e._1, e._2.length)).list
result foreach println
But it doesn't work correctly. It returns the incorrect amount of users for empty groups (returns users count = 1 instead of 0).
My environment: scala-2.11.2, slick-2.1.0, PostgreSQL
I don't see what's wrong. Could well be a Slick bug. Please report one here: https://github.com/slick/slick

Dynamic table name override in Slick table

I have defined a slick table as below on DB view. Every day we create a dated view matching the same structure with name appended with date as T_SUMMARY_ i.e.T_SUMMARY_20131213.
Is there a way in slick where table name can be dynamically overridden to generate required query for a correct dated view.
object TSummary extends Table[(String)]("T_SUMMARY")
{
def id = column[String]("ROWID", O.PrimaryKey)
def tNum = column[Int]("T_NUM")
def tName = column[Int]("T_NAME")
def * = id
}
In Slick 1.0.1:
def mkSummaryTable(name: String) = new Table[(String)](name){
def id = column[String]("ROWID", O.PrimaryKey)
def tNum = column[Int]("T_NUM")
def tName = column[Int]("T_NAME")
def * = id
}
val current = mkSummaryTable("T_SUMMARY_20131213")
for( r <- Query(current) ) yield r.tName
For Slick 2.0 see https://groups.google.com/d/msg/scalaquery/95Z7AfxKP_4/omGnAtuN8FcJ:
class MyTable(tag: Tag, tableName: String) extends Table[(String)](tag, tableName){
def id = column[String]("ROWID", O.PrimaryKey)
def tNum = column[Int]("T_NUM")
def tName = column[Int]("T_NAME")
def * = id
}
val tQ = TableQuery[MyTable]((tag:Tag) => new MyTable(tag, "SomeTableName")) filter ...

Inner join doesn't work in Slick

Could you please tell me why I'm not getting inner join I'm expecting to get ?
I have following tables
case class Ability(id: UUID, can: Boolean, verb: String, subject: String, context: String)
object Abilities extends Table[Ability]("abilities"){
def id = column[UUID]("id", O.PrimaryKey)
def can = column[Boolean]("is_can")
def verb = column[String]("verb")
def subject = column[String]("subject")
def context = column[String]("context")
def * = id ~ can ~ verb ~ subject ~ context <> (Ability, Ability.unapply _)
}
case class Role(id: UUID, name : String)
object Roles extends Table[Role]("roles"){
def id = column[UUID]("id", O.PrimaryKey)
def name = column[String]("name")
def * = id ~ name <> (Role, Role.unapply _)
}
// And join table
case class AbilityRelationship(owner_id: UUID, obj_id: UUID, is_role: Boolean)
object AbilitiesMapping extends Table[AbilityRelationship]("abilities_mapping"){
def owner_id = column[UUID]("owner_id")
def obj_id = column[UUID]("obj_id")
def is_role = column[Boolean]("is_role")
def * = owner_id ~ obj_id ~ is_role <> (AbilityRelationship, AbilityRelationship.unapply _)
}
What I'm willing to do is to fetch list of Ability objects for particular owner (whether user or role). So following documentation I wrote following join query for it
val some_id = role.id
val q2 = for {
a <- Abilities
rel <- AbilitiesMapping
if rel.owner_id === some_id.bind
} yield (a)
But q2.selectStatement returns absolutely wrong query for it. Which is select x2."id", x2."is_can", x2."verb", x2."subject", x2."context" from "abilities" x2, "abilities_mapping" x3 where x3."owner_id" = ? in my case.
How should it be implemented?
Thanks.
Well, after multiple attempts I made it
val innerJoin = for {
(a, rel) <- Abilities innerJoin AbilitiesMapping on (_.id === _.obj_id) if rel.owner_id === some_id.bind
} yield a
But man... typesafe's documentation is really really weak for newcomers.
Try something like:
val q2 = for {
a <- Abilities
rel <- AbilitiesMapping
if a.id == rel.obj_id && rel.owner_id === some_id.bind
} yield (a)
BTW, you know you can annotate your foreign keys in the Table objects right?
Tried doing this as a comment to ruslan's answer, but I just dont have enough jedi powers:
Can you try if this desugar-ed version works?
val rightSide = AbilitiesMapping.filter(_.owner_id === some_id)
val innerJoin = (Abilities innerJoin (rightSide) on (
(l,r) => (l.id === r.obj_id)
).map { case (l, r) => l }