2

我正在使用 Play 和 Slick 构建一个 Web 应用程序,发现自己处于面向用户的表单相似但与数据库模型不完全相同的情况。

因此,我有两个非常相似的案例类,并且需要从一个映射到另一个(例如,在填写表单以呈现“更新”视图时)。

在我感兴趣的案例中,数据库模型案例类是表单案例类的超集,即两者之间的唯一区别是数据库模型还有两个字段(基本上是两个标识符)。

我现在想知道的是是否有一种方法可以构建一个小型库(例如宏驱动),以根据成员名称从数据库案例类中自动填充表单案例类。我已经看到可以使用 Paranamer 通过反射访问此类信息,但我不想冒险这样做。

4

1 回答 1

2

这是一个使用的解决方案,Dynamic因为我想尝试一下。宏将静态决定是否发出源值方法的应用、默认值方法或仅提供文字。语法可能类似于newFrom[C](k). (更新:宏见下文。)

import scala.language.dynamics
trait Invocable extends Dynamic {
  import scala.reflect.runtime.currentMirror
  import scala.reflect.runtime.universe._

  def applyDynamic(method: String)(source: Any) = {
    require(method endsWith "From")
    def caseMethod(s: Symbol) = s.asTerm.isCaseAccessor && s.asTerm.isMethod
    val sm = currentMirror reflect source
    val ms = sm.symbol.asClass.typeSignature.members filter caseMethod map (_.asMethod)
    val values = ms map (m => (m.name, (sm reflectMethod m)()))
    val im = currentMirror reflect this
    invokeWith(im, method dropRight 4, values.toMap)
  }

  def invokeWith(im: InstanceMirror, name: String, values: Map[Name, Any]): Any = {
    val at = TermName(name)
    val ts = im.symbol.typeSignature
    val method = (ts member at).asMethod

    // supplied value or defarg or default val for type of p
    def valueFor(p: Symbol, i: Int): Any = {
      if (values contains p.name) values(p.name)
      else ts member TermName(s"$name$$default$$${i+1}") match {
        case NoSymbol =>
          if (p.typeSignature.typeSymbol.asClass.isPrimitive) {
            if (p.typeSignature <:< typeOf[Int]) 0
            else if (p.typeSignature <:< typeOf[Double]) 0.0
            else ???
          } else null
        case defarg   => (im reflectMethod defarg.asMethod)()
      }
    }
    val args = (for (ps <- method.paramss; p <- ps) yield p).zipWithIndex map (p => valueFor(p._1,p._2))
    (im reflectMethod method)(args: _*)
  }
}
case class C(a: String, b: Int, c: Double = 2.0, d: Double)
case class K(b: Int, e: String, a: String)
object C extends Invocable
object Test extends App {
  val res = C applyFrom K(8, "oh", "kay")
  Console println res      // C(kay,8,2.0,0.0)
}

更新:这是宏版本,更多的是为了好玩而不是为了利润:

import scala.language.experimental.macros
import scala.reflect.macros._
import scala.collection.mutable.ListBuffer

def newFrom[A, B](source: A): B = macro newFrom_[A, B]

def newFrom_[A: c.WeakTypeTag, B: c.WeakTypeTag](c: Context)(source: c.Expr[A]): c.Expr[B] = { 
  import c.{ literal, literalNull } 
  import c.universe._
  import treeBuild._
  import nme.{ CONSTRUCTOR => Ctor } 

  def caseMethod(s: Symbol) = s.asTerm.isCaseAccessor && s.asTerm.isMethod
  def defaulter(name: Name, i: Int): String = s"${name.encoded}$$default$$${i+1}"
  val noargs = List[c.Tree]()

  // side effects: first evaluate the arg
  val side = ListBuffer[c.Tree]()
  val src = TermName(c freshName "src$")
  side += ValDef(Modifiers(), src, TypeTree(source.tree.tpe), source.tree)

  // take the arg as instance of a case class and use the case members
  val a = implicitly[c.WeakTypeTag[A]].tpe
  val srcs = (a.members filter caseMethod map (m => (m.name, m.asMethod))).toMap

  // construct the target, using src fields, defaults (from the companion), or zero
  val b = implicitly[c.WeakTypeTag[B]].tpe
  val bm = b.typeSymbol.asClass.companionSymbol.asModule
  val bc = bm.moduleClass.asClass.typeSignature
  val ps = (b declaration Ctor).asMethod.paramss.flatten.zipWithIndex
  val args: List[c.Tree] = ps map { case (p, i) =>
    if (srcs contains p.name)
      Select(Ident(src), p.name)
    else bc member TermName(defaulter(Ctor, i)) match { 
      case NoSymbol =>
        if (p.typeSignature.typeSymbol.asClass.isPrimitive) { 
          if (p.typeSignature <:< typeOf[Int]) literal(0).tree
          else if (p.typeSignature <:< typeOf[Double]) literal(0.0).tree
          else ???
        } else literalNull.tree
      case defarg   => Select(mkAttributedRef(bm), defarg.name)
    } 
  } 
  c.Expr(Block(side.toList, Apply(Select(New(mkAttributedIdent(b.typeSymbol)), Ctor), args)))
} 

随着用法:

case class C(a: String, b: Int, c: Double = 2.0, d: Double)
case class K(b: Int, e: String, a: String) { def i() = b }
val res = newFrom[K, C](K(8, "oh", "kay"))
于 2013-07-09T23:00:40.357 回答