Felix
Felix

Reputation: 5619

Mapping Model to Model.type in scala

I'am stuck on a (rather) simple problem, I think. I've got a Model like this

 case class MyModel(
      id: Option[Int],
      title: String,
      backStepId: Option[Int] = None,
      backStep: Option[MyModel] = None,
      ...
 )

So I've got to backStep properties on my model. The first backStepId I use to persist the data and map it to another instance of MyModel. I'm using backStep to validate incoming requests. My trait looks like this:

trait MyClassComponent extends AnotherClassComponent {
  self: HasDatabaseConfigProvider[JdbcProfile] =>
  import profile.api._
  class MyClass(tag: Tag) extends Table[MyModel](tag, "MyClass") {
    def id: Rep[Int] = column[Int]("id", O.PrimaryKey, O.AutoInc)
    def title: Rep[String] = column[String]("title")
    def backStepId: Rep[Option[Int]] = column[Option[Int]]("backStepId")
    def * : ProvenShape[MyModel] = (id.?, title, backStepId) <> ( {
      tuple: (Option[Int], String, Option[Int]) => MyModel(tuple._1, tuple._2, tuple._3, Some(<What do I need to fill in here?>))
    }, {
      x: MyModel=> Some((x.id, x.title, x.backStepId))
    })
  }
  val myModels: TableQuery[MyClass] = TableQuery[MyClass] // Query Object
}

When filling in MyModel it says: Type mismatch, expected: Option[MyModel], actual: Some[MyModel.type]. However I use this kind of method for other use cases as well. For instance Some(Seq()), whenever I'm validating a incoming Array/Seq of some Model. What am I doing wrong here? Thanks in advance!

Upvotes: 0

Views: 63

Answers (1)

Mateusz Kubuszok
Mateusz Kubuszok

Reputation: 27535

First let's try to sum up, what you want to achieve here:

  • I guess the errors comes from Some(MyModel) which puts companion object instance inside an Option (MyModel.type is a type of that companion object)
  • you want to have MyModel with recursive definition (as it references itself),
  • you want to fetch it from database and have it already populated,
  • apparently you want to have an uniform model.

I would say it would be pretty hard (and dangerous) to do it this way. If your backStep would create e.g. circular dependency in database, you would end up with infinite loop on fetch, as there is no place inside ProvenShape for logic more sophisticated, than mapping tuples to some other representation back and forth (and resolving another record is more sophisticated logic).

Instead I would suggest, that you split your model into 2 parts:

case class MyModelRow(
    id: Option[Int],
    title: String,
    backStepId: Option[Int] = None,
    ...
)

case class MyModel(
    id: Int,
    title: String,
    backStep: Option[MyModel] = None,
      ...
)

This way, you could fetch MyModelRowand then translate it into MyModel.

As Slick (as far as I know) do not support recursive queries you would need to use SQL to define your query.

With such query you would obtain Seq[MyModelRow]. With that seq you could take Row without dependencies translate it to Model, then take Row referencing it and translate it to Model referencing newly build model and so on.

Thing is, this is NOT a simple problem. Think about such case:

Model(id:234).backStep -> Model(id:6456).backStep -> Model(id:56756).backStep -> null

What you modeled with unapply is that if you fetch Model(id:234) it should have Model(id:6456). Except to hold the assumptions that in turn would have to hold Model(id:56756). You might want it. You might not. Surely I would be careful to make it a default behavior.

Instead you might consider not fetching deps by default. Fetch just backStepId and decide what to do with it once it arrives. For those particular cases when you really need all the dependencies design a separate query - this query could use raw SQL to fetch all rows at once, then you could chain them manually.

If performance is of no concern, and instead you want to have nice (?) Hibernate-like experience, you might create extension methods, that would add backStep methods using Slick to fetch dependency as Future[Option[MyModel]].

Other option you might want to check, is to simply let the unapply always return None for backStep, and add separate service for "enriching it".

So, depending on trade-offs and priorities, you might want to go with a different route. You know you use cases best, so I suggest creating a prototype branch and experimenting which approach (perhaps none of what I came up with) will be most useful. Personally, I would prefer complexity of recursive queries explicit to remind me that I cannot just spam database with calls without any penalty.

Upvotes: 1

Related Questions