自动案例分类映射 [英] Automatic case class mapping

查看:96
本文介绍了自动案例分类映射的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm building a web-application using Play and Slick, and find myself in a situation where the user-facing forms are similar, but not exactly the same as the database model.

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

Hence I have two very similar case classes, and need to map from one to another (e.g. while filling the form for rendering an "update" view).

在我感兴趣的情况下,数据库模型案例类是形式为case-class的超集,即两者之间的唯一区别是数据库模型具有两个以上的字段(基本上是两个标识符)

In the case I'm interested in, the database model case class is a super-set of the form case-class, i.e. the only difference between both is that the database model has two more fields (two identifiers, basically).

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

What I'm now wondering about is whether there'd be a way to build a small library (e.g. macro-driven) to automatically populate the form case class from the database case class based on the member names. I've seen that it may be possible to access this kind of information via reflection using Paranamer, but I'd rather not venture into this.

推荐答案

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

Here is a solution using Dynamic because I wanted to try it out. A macro would decide statically whether to emit an apply of a source value method, the default value method, or just to supply a literal. The syntax could look something like newFrom[C](k). (Update: see below for the macro.)

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

更新:这是宏版本,更多的是娱乐而非盈利:

Update: Here is the macro version, more for fun than for profit:

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

这篇关于自动案例分类映射的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆