0
votes

I'm struggling to get custom defined mapping between my case classes and database tables due to type inference problems. This is an example of what I'm getting, which is just an extremely trimmed down version from the original use case, but it should be enough to illustrate the problem:

case class Example(id: Int, aString: String, optional: Option[Int], extra: String)

class Examples(tag: Tag) extends Table[Example](tag, "EXAMPLE") {
  def id = column[Int]("ID", O.AutoInc, O.PrimaryKey)
  def aString = column[String]("A_STRING")
  def optional = column[Option[Int]]("AN_OPTIONAL")

  override def * = (id, aString, optional) <> (constructExample, extractExample)

  private def constructExample(id: Int, aString: String, optional: Option[Int]): Example = {
    Example(id, aString, optional, "MY EXTRA DATA")
  }

  private def extractExample(e: Example): Option[(Int, String, Option[Int])] = {
    Some((e.id, e.aString, e.optional))
  }
}

Here I'm defining 2 custom functions to deal with the conversion between my case class and the database row. The problem is that the <> method is not able to infer the tuple types for constructing, giving the following error:

Error:(60, 50) type mismatch; found : (Int, String, Option[Int]) => SlickExampleRepository.this.Example required: ? => ? override def * = (id, aString, optional) <> (constructExample, extractExample)

In the Slick docs one can find the following:

it can also be used with arbitrary mapping functions. In these cases it can be useful to call .shaped on a tuple on the left-hand side in order to get its type inferred properly. Otherwise you may have to add full type annotations to the mapping functions.

There's no example for it, but I went ahead and tried the following:

override def * = (id, aString, optional).shaped <> (constructExample, extractExample)

This seems to only partially solve the problem, giving the following error:

Error:(60, 57) type mismatch; found : (Int, String, Option[Int]) => SlickExampleRepository.this.Example required: ((Int, String, Option[Int])) => ? override def * = (id, aString, optional).shaped <> (constructExample, extractExample)

So the final work-around I found for this was to change the signature of the constructExample function to receive a tuple and return an Example object, like so:

private def constructExample(tuple: (Int, String, Option[Int])): Example = {
  Example(tuple._1, tuple._2, tuple._3, "MY EXTRA DATA")
}

But this is quite horrible and error prone, given that we're defining potentially quite long tuples and accessing its elements using ._1 and the like. Any hints on how to get this to work in a nice way?

Many thanks

1
can the 'extra' field be inferred from the other parameters? i mean do you really need a custom mapping? Because you could simply do: override def * = (id, aString, optional) <> (Example.tupled, Example.unapply) - fGo
Thank you for reading the long question and your suggestion. The fact is that I'm adding that field just to illustrate the problem with a short-ish example, but the real use case is more around multiple de-normalised database fields constructing inner objects composing the main case class. For instance, two dates that generate a Period of time. - hasumedic

1 Answers

0
votes

You should use the following way to define your * projection:

override def * = (id, aString, optional) <> ((constructExample _).tupled, extractExample)

constructExample _ will convert your private method into a function, and .tupled would convert your function with 3 arguments into a function with one Tuple3 argument.