0

我之前使用案例类将类对象转换为slick2的数据,但目前我使用另一个播放插件,插件对象使用案例类,我的类是这个案例类所固有的。所以,我不能使用case类作为scala语言禁止用例类对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)
}

我不知道如何编写自动生成的案例类模板之类的元组和取消应用(我不是我的写作是否正确)方法。或者你可以用其他方式通过 slick2 将类映射到 talbe。

任何人都可以给我一个例子吗?

4

1 回答 1

0

First of all, this case class is a bad idea:

case class BasicProfile()

Case classes compare by their member values, this one doesn't have any. Also the name is not great, because we have the same name in Slick. May cause confusion.

Regarding your class

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

It is possible to emulate case classes yourself. Are you doing that because of the 22 field limit? FYI: Scala 2.11 supports larger case classes. We are doing what you are trying at Sport195, but there are several aspects to take care of.

apply and unapply need to be members of object User (the companion object of class User). .tupled is not a real method, but generated automatically by the Scala compiler. it turns a method like .apply that takes a list of arguments into a function that takes a single tuple of those arguments. As tuples are limited to 22 columns, so is .tupled. But you could of course auto-generated one yourself, may have to give it another name.

We are using the Slick code generator in combination with twirl template engine (uses @ to insert expressions. The $ are inserted as if into the generated Scala code and evaluated, when the generated code is compiled/run.). Here are a few snippets that may help you:

Generate apply method

  /** 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})

Generate unapply method:

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

Trait that can be mixed into User to make it a Scala Product:

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-class like .copy method

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

To use those classes with Slick you have several options. All are somewhat newer and not documented (well). The normal <> operator Slick goes via tuples, but that's not an option for > 22 columns. One option are the new fastpath converters. Another option is mapping via a Slick HList. No examples exist for either. Another option is going via a custom Shape, which is what we do. This will require you to define a custom shape for your User class and another class defined using Column types to mirror user within queries. Like this: http://slick.typesafe.com/doc/2.1.0/api/#scala.slick.lifted.ProductClassShape Too verbose to write by hand. We use the following template code for this:

/** 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

A few helpers we put into the Slick code generator:

  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(""))

I know this may a bit troublesome to replicate, especially if you are new to Scala. I hope to to find the time get it into the official Slick code generator at some point.

于 2014-08-05T15:56:19.543 回答