Как написать сопоставление классов и таблиц для slick2 вместо использования класса case?

Раньше я использовал класс case для преобразования объекта класса в данные для slick2, но в настоящее время я использую другой игровой плагин, объект плагина использует класс case, мой класс унаследован от этого класса case. Таким образом, я не могу использовать класс case, поскольку язык scala запрещает использовать класс case для класса case, присущего ему.

до:

case class User()

class UserTable(tag: Tag) extends Table[User](tag, "User") {
  ...
  def * = (...)<>(User.tupled,User.unapply)
}

оно работает. Но теперь мне нужно изменить выше на ниже:

case class BasicProfile()

class User(...) extends BasicProfile(...){
  ...
  def unapply(i:User):Tuple12[...]= Tuple12(...)
}
class UserTable(tag: Tag) extends Table[User](tag, "User") {
  ...
  def * = (...)<>(User.tupled,User.unapply)
}

Я не знаю, как написать метод tupled и unapply (я не правильно пишу или нет), такой как автоматически сгенерированный шаблон класса case. Или вы можете использовать другой способ сопоставления класса с таблицей с помощью slick2.

Кто-нибудь может привести мне пример?


person user504909    schedule 05.08.2014    source источник
comment
Я полагаю, что это невозможно, чтобы класс был таблицей, он должен расширять гладкий абстрактный класс Table.   -  person Ende Neu    schedule 05.08.2014
comment
@EndeNeu У меня есть класс таблицы, на самом деле мне нужно сделать это и отменить переписывание метода, так как нет шаблона для класса, записывающего какую-то функцию по умолчанию.   -  person user504909    schedule 05.08.2014


Ответы (1)


Во-первых, этот case-класс — плохая идея:

case class BasicProfile()

Классы Case сравниваются по значениям членов, у этого их нет. Также название не очень удачное, потому что у нас такое же имя в Slick. Может вызвать путаницу.

Что касается вашего класса

class User(...) extends BasicProfile(...){
  ...
  def unapply(i:User):Tuple12[...]= Tuple12(...)
}

Можно самостоятельно эмулировать case-классы. Вы делаете это из-за ограничения в 22 поля? К вашему сведению: Scala 2.11 поддерживает более крупные классы case. Мы делаем то же, что и вы, в Sport195, но есть несколько аспектов, о которых нужно позаботиться.

apply и unapply должны быть членами object User (сопутствующего объекта class User). .tupled не является реальным методом, он автоматически генерируется компилятором Scala. он превращает такой метод, как .apply, который принимает список аргументов, в функцию, которая принимает один кортеж этих аргументов. Поскольку кортежи ограничены 22 столбцами, то же самое и с .tupled. Но вы, конечно, можете автоматически сгенерировать его самостоятельно, возможно, придется дать ему другое имя.

Мы используем генератор кода Slick в сочетании с механизмом шаблонов twirl (использует @ для вставки выражений. $ вставляются как бы в сгенерированный код Scala и оцениваются при компиляции/запуске сгенерированного кода). Вот несколько фрагментов, которые могут вам помочь:

Создать метод применения

  /** Factory for @{name} objects
    @{indentN(2,entityColumns.map(c => "* @param "+c.name+" "+c.doc).mkString("\n"))}
    */
  final def apply(
    @{indentN(2,
        entityColumns.map(c =>
          colWithTypeAndDefault(c)
        ).mkString(",\n")
    )}
  ) = new @{name}(@{columnsCSV})

Создайте метод неприменения:

@{if(entityColumns.size <= 22)
  s"""
  /** Extractor for ${name} objects */
  final def unapply(o: ${name}) = Some((${entityColumns.map(c => "o."+c.name).mkString(", ")}))
  """.trim
  else
  ""}

Признак, который можно смешать с User, чтобы сделать его продуктом Scala:

trait UserBase with Product{
  // Product interface
  def canEqual(that: Any): Boolean = that.isInstanceOf[@name]
  def productArity: Int = @{entityColumns.size}
  def productElement(n: Int): Any = Seq(@{columnsCSV})(n)

  override def toString = @{name}+s"(${productIterator.toSeq.mkString(",")})"
...

case-класс, такой как метод .copy

  final def copy(
    @{indentN(2,columnsCopy)}
  ): @{name} = @{name}(@{columnsCSV})

Чтобы использовать эти классы со Slick, у вас есть несколько вариантов. Все они несколько новее и не документированы (хорошо). Обычный оператор <> Slick проходит через кортежи, но это не вариант для > 22 столбцов. Одним из вариантов являются новые преобразователи fastpath. Другой вариант — отображение через Slick HList. Ни для того, ни для другого примеров не существует. Другой вариант — через пользовательскую форму, что мы и делаем. Это потребует от вас определения пользовательской формы для вашего класса User и другого класса, определенного с использованием типов Column, для отражения пользователя в запросах. Вот так: http://slick.typesafe.com/doc/2.1.0/api/#scala.slick.lifted.ProductClassShape Слишком многословно, чтобы писать вручную. Для этого мы используем следующий код шаблона:

/** class for holding the columns corresponding to @{name}
 * used to identify this entity in a Slick query and map
 */
class @{name}Columns(
  @{indent(
    entityColumns
      .map(c => s"val ${c.name}: Column[${c.exposedType}]")
      .mkString(", ")
  )}
) extends Product{
  def canEqual(that: Any): Boolean = that.isInstanceOf[@name]
  def productArity: Int = @{entityColumns.size}
  def productElement(n: Int): Any = Seq(@{columnsCSV})(n)
}

/** shape for mapping @{name}Columns to @{name} */
object @{name}Implicits{
  implicit object @{name}Shape extends ClassShape(
    Seq(@{
      entityColumns
        .map(_.exposedType)
        .map(t => s"implicitly[Shape[ShapeLevel.Flat, Column[$t], $t, Column[$t]]]")
        .mkString(", ")
    }),
    vs => @{name}(@{
      entityColumns
        .map(_.exposedType)
        .zipWithIndex
        .map{ case (t,i) => s"vs($i).asInstanceOf[$t]" }
        .mkString(", ")
    }),
    vs => new @{name}Columns(@{
      entityColumns
        .map(_.exposedType)
        .zipWithIndex
        .map{ case (t,i) => s"vs($i).asInstanceOf[Column[$t]]" }
        .mkString(", ")
    })
  )
}
import @{name}Implicits.@{name}Shape

Несколько хелперов, которые мы добавили в генератор кода Slick:

  val columnsCSV = entityColumns.map(_.name).mkString(", ")
  val columnsCopy = entityColumns.map(c => colWithType(c)+" = "+c.name).mkString(", ")
  val columnNames = entityColumns.map(_.name.toString)


  def colWithType(c: Column) = s"${c.name}: ${c.exposedType}"

  def colWithTypeAndDefault(c: Column) =
    colWithType(c) + colDefault(c).map(" = "+_).getOrElse("")

  def indentN(n:Int,code: String): String = code.split("\n").mkString("\n"+List.fill(n)(" ").mkString(""))

Я знаю, что это может быть немного сложно воспроизвести, особенно если вы новичок в Scala. Я надеюсь, что когда-нибудь найду время добавить его в официальный генератор кода Slick.

person cvogt    schedule 05.08.2014
comment
Извините, я внимательно прочитал этот ответ, но все еще был озадачен. На самом деле я не знаю slick.typesafe. com/doc/2.1.0/api/ объясняет страницу. У вас есть какой-нибудь документ или пример для объяснения класса Shape и Product. - person user504909; 06.08.2014
comment
На данный момент нет ни документов, ни примеров, кроме самого кода Slick. Формы Слика используются как неявные для вычислений на уровне типов. Они преобразуют типы запросов, используя типы столбцов и т. д., и типы результатов. Форма сообщает Slick, как использовать определенный тип в запросе и как сопоставить его с соответствующим типом результата. Код или формы Slick находятся здесь: github.com/slick/slick/blob/2.1.0/src/main/scala/scala/slick/ Но это определенно сложный материал для понимания. - person cvogt; 06.08.2014
comment
Вы пытаетесь создать свои собственные классы случаев из-за ограничения в 22 столбца, как я предполагал, или есть другая причина? - person cvogt; 06.08.2014