Search code examples
scalaplayframeworkplayframework-2.0slick

scala save slick result into new object


is there a way to save the result of a slick query into a new object?

This is my slick result, there is only one "object" in the list

val result: Future[Seq[ProcessTemplatesModel]] = db.run(action)

The result should be mapped on ProcessTemplatesModel because I want to access the values like this

process.title

Is this possible?

Thanks


Solution

  • TL;DR: you should keep the context as long as you can.

    Future denotes the fact that the value will be given at some time in the future (this is what I call some context for the value).

    The bad way to use it would be to block your thread, until such value is found, and then work with it.

    A better way is to tell your program: "Once the value is found (whenever that is), do something with it". That's a continuation, or call-back, and is implemented with map and flatMap in scala.

    Seq is another context for your value. It means that you actually have different possible values. If you want to make sure that you have at most one value, you can always do seq.headOption to switch context from Seq to Option.

    The bad way to use it would be to take the first value without bothering checking if it exists or not.

    A better way is to tell your program: "No matter how many values you have, do this for each of them".

    Now, how do you work in context? You use the Functor and/or Monad operators: map, flatMap.

    For instance, if you want to apply a function convertToSomethingElse to each element of your context, just do

    result.map(list => list.map(process => convertToSomethingElse(process))
    

    And you'll get a Future[Seq[SomethingElse]].

    Another example, if you want to save the result somewhere else, you'll probably have some IO, or database operations, which may take some time, and possibly fail. We will assume you have a function save(entity: ProcessTemplateModel): Future[Boolean] that allows you to save one of your models. The fact that the function will take some time (and that it will be started in another thread) and possibly fail is visible in the return type Future[Boolean] (Boolean is not important here, it's the fact that we have again the Future context that matters).

    Here, you will have to do (assuming you just want to save the first element in your list):

    val savedFirstResult: Future[Option[ProcessTemplatesModel]] = result.flatMap {list => 
      Future.traverse(list.headOption){ process =>  //traverse will switch the Future and Option contexts
        save(process)
      }
    }
    

    So as you can see, we can do most of what we want by staying inside the contexts that are returned by Slick. You shouldn't want to get outside of them because

    • most of the time, there's no need to, when you have map to use inside context some function for values outside context
    • extracting methods are most of the time unsafe: Option#get throws an exception if no element is in the Option, Await.result(future, duration) may block all computations or throw exceptions
    • responses in Play! can be given as Futures in a controller, using Action.async