Проекция на карту с сопутствующим объектом в SLICK

Я вложил классы / объекты и хочу сохранить (и получить) их в базе данных с помощью SLICK. Я понимаю, что с SLICK картографическая проекция была бы ключевой. Кроме того, я использую сопутствующий объект для отображения между вложенными объектами и плоской структурой (для хранения в таблице БД). Я хочу сделать что-то вроде этого (упрощенный пример):

case class Foo(id: Int, myBar: Bar)

case class Bar(myInt: Int, myString: String)

object Foo {
  def apply(id: Int, myInt: Int, myString: String): Foo = Foo(id, Bar(myInt, myString))

  override def unapply(f: Foo) = (f.id, f.myBar.myInt, f.myBar.myString)
}

object TTable extends Table[Foo]("FOO") {
    def id = column[Int]("id",  O.PrimaryKey)
    def myInt = column[Int]("myInt", O NotNull)
    def myString = column[String]("myString", O NotNull)

    def * = id ~ myInt ~ myString <> (Foo.apply _, Foo.unapply _)

    def query(db: Database, id: Int): Option[Foo] = db withSession { //s: Session =>
        (for { b <- TTable if b.id is id} yield b).firstOption
    }
}

Но компиляция заканчивается неудачей с несколькими ошибками: "метод unapply определяется дважды", "неоднозначная ссылка на перегруженное определение, оба метода apply [...] соответствуют ожидаемому типу?" и "перегруженное значение метода <> с альтернативами"

Я нашел это превосходное объяснение отображаемой проекции " метод scala slick, который я до сих пор не могу понять" и " Сопоставленная проекция с <> классу дел с сопутствующим объектом в Slick", но ни одно из предложенных решений не работает для меня.

1 ответ

Вместо unapply а также applyВы можете просто передать лямбды, которые делают то, что вы хотите:

  def * = id ~ myInt ~ myString <> (
    (id,myInt,myString) => Foo(id, Bar(myInt, myString)),    /* from a row to a Foo */
    (f:Foo) => Some((f.id, f.myBar.myInt, f.myBar.myString)) /* and back */)

Таким образом, отображение строк таблицы в классы прецедентов остается в определении таблицы, а классы прецедентов остаются простыми классами прецедентов, что не так уж и плохо.

Другим способом было бы не использовать case-класс для Foo, но вместо этого обычный класс, который дает вам свободу определять свои собственные apply а также unapply в объекте-компаньоне, вот так:

// untested code
class Foo private (val id: Int, val myBar: Bar) 
case class Bar(myInt: Int, myString: String)
object Foo {
  def apply(id: Int, myInt: Int, myString: String): Foo = new Foo(id, Bar(myInt, myString))
  def unapply(f: Foo) = Some((f.id, f.myBar.myInt, f.myBar.myString))
}

Если вы хотите сделать def * = id ~ myInt ~ myString <> (Foo.apply _, Foo.unapply _)

Вы будете в некоторой степени использовать класс, похожий на случай, но вы можете пропустить другие приятные вещи, такие как equals а также toString бесплатно, как и в реальных классах. Я предпочел бы сохранить классы дел (и их по умолчанию applyunapply) поэтому они могут рассматриваться как алгебраические типы данных в обычном соглашении.

Настоящая проблема здесь заключается в том, что у тематических классов есть свои unapply поэтому вы не можете (насколько я знаю) иметь аналогичный метод (с тем же именем и теми же аргументами) в вашем классе компаньона. Вы можете просто использовать другое имя метода. В конце концов, то, что вы хотите сделать, семантически не эквивалентно unapply тем не мение:

object Foo {
  def fromRow(id: Int, myInt: Int, myString: String): Foo = Foo(id, Bar(myInt, myString))
  def toRow(f: Foo) = Some((f.id, f.myBar.myInt, f.myBar.myString))
}

Тогда в вашей схеме таблицы:

def * = id ~ myInt ~ myString <> (Foo.fromRow _, Foo.toRow _)
Другие вопросы по тегам