0

I am using play-slick for my scala Play! dummy rest API.

So, I have to fetch records from multiple tables. But, they are interdependent, i.e.

Table_1   Table_2
id1       id2
id2

To fetch a record from Table_2 I have to fetch a record from Table_1 and then using id2 fetch from Table_2.

My controller:

def getEntity(id : Long) = Action.async {
  table1DAO.findById(id) map { t1 =>
    t1 map { t1Entity =>
      table2DAO.findById(t1Entity.id2) map { t2 =>
        t2 map { t2Entity =>
          Ok(Json.toJson(CombiningClass(t1Entity, t2Entity)))
        } getOrElse { Ok(Json.toJson(t1Entity)) }
      }
    } getOrElse { NoContent }
  }
}

After compilation, I get:

[error] DummyController.scala:17: overloaded method value async with alternatives:
[error]   [A](bodyParser: play.api.mvc.BodyParser[A])(block: play.api.mvc.Request[A] => scala.concurrent.Future[play.api.mvc.Result])play.api.mvc.Action[A] <and>
[error]   (block: play.api.mvc.Request[play.api.mvc.AnyContent] => scala.concurrent.Future[play.api.mvc.Result])play.api.mvc.Action[play.api.mvc.AnyContent] <and>
[error]   (block: => scala.concurrent.Future[play.api.mvc.Result])play.api.mvc.Action[play.api.mvc.AnyContent]
[error]  cannot be applied to (scala.concurrent.Future[Object])
[error]   def getEntity(id : Long) = Action.async {
[error]                                ^
[error] one error found

Here's my DAO method:

def findById(id : Long): Future[Option[A]] = {
  db.run(tableQ.filter(_.id === id).result.headOption)
}

PS: I'm very new to functional paradigm and scala, so if you can, pardon my ignorance.

Xstian
  • 8,184
  • 10
  • 42
  • 72
mlemboy
  • 387
  • 2
  • 3
  • 15
  • Try to annotate the top `map`: `table1DAO.findById(id).map[Result] { t1 => ... }` to make sure you give a `Future[Result]`. P.S. The readability of your code would benefit from using for-comprehension. – cchantep Jun 12 '16 at 08:00

1 Answers1

1

To simply solve your problem:

def getEntity(id : Long) = Action.async {
  findById(id) flatMap {
    case Some(t1Entity) =>
      findById(t1Entity.id2) map { t2Opt =>
        t2Opt map { t2Entity =>
          Ok(Json.toJson(t1Entity, t2Entity))
        } getOrElse { Ok(Json.toJson(t1Entity)) }
      }
    case None => Future.successful(NoContent)
  }
}

The problem here is that you can't flatMap Option and Future together in scala. Here is a fantastic and simple article concerning this topic (with the custom FutureO monad implementation as a solution). Long story short, I would use the cats library (or even scalaz library) and OptionT feature. I slightly simplified your code.

def getEntity(id : Long) = Action.async {
  (for {
    t1 <- daoFindById(id)
    t2 <- daoFindById(t1.id2)
  } yield (t1, t2)).map{
    result => Ok(Json.toJson(result))
  }.getOrElse(NoContent)
}

case class T(id2: Long)
def daoFindById(id : Long): OptionT[Future, T] = {
  OptionT[Future, T](db.run(tableQ.filter(_.id === id).result.headOption))
}

You can now easily flatMap over this OptionT monad, and don't care if you are dealing with Option or Future (for comprehension in scala is only a syntactic sugar).

liosedhel
  • 518
  • 5
  • 14
  • This is a good answer although possibly a little bit advanced for someone getting started with Scala. This won't give you the first entity if the second entity can't be found. Cats also has [a tutorial about `OptionT`](http://typelevel.org/cats/tut/optiont.html). – Peter Neyens Jun 12 '16 at 09:04
  • 1
    Your update solves both my comments. It's a little more beginner friendly and will return the result the OP expects, so good job ! – Peter Neyens Jun 12 '16 at 09:17
  • 1
    Yes, not all business logic is preserved (I wanted only to demonstrate usefulness of cats library). I added simple solution written without the help of external libraries. – liosedhel Jun 12 '16 at 09:19