Проекция на карту с сопутствующим объектом в 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
бесплатно, как и в реальных классах. Я предпочел бы сохранить классы дел (и их по умолчанию apply
unapply
) поэтому они могут рассматриваться как алгебраические типы данных в обычном соглашении.
Настоящая проблема здесь заключается в том, что у тематических классов есть свои 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 _)