所以这是我的宏
package com.fullfacing.ticketing.macros
import com.mongodb.DBObject
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context
/**
* Project: com.fullfacing.ticketing.macros
* Created on 2015/05/26.
* ryno aka lemonxah -
* https://github.com/lemonxah
* http://stackoverflow.com/users/2919672/lemon-xah
*/
trait Mappable[A,B] {
def toDBType(a: A): B
def fromDBType(b: B): A
}
object MongoMappable {
implicit def materializeMappable[A]: Mappable[A, DBObject] = macro materializeMappableImpl[A]
def materializeMappableImpl[A: c.WeakTypeTag](c: Context): c.Expr[Mappable[A, DBObject]] = {
import c.universe._
val tpe = weakTypeOf[A]
val companion = tpe.typeSymbol.companion
val fields = tpe.decls.collectFirst {
case m: MethodSymbol if m.isPrimaryConstructor ⇒ m
}.get.paramLists.head
val (toDBObject, fromDBObject) = fields.map { field ⇒
val name = field.name.toTermName
val decoded = name.decodedName.toString
val returnType = tpe.decl(name).typeSignature
(q"$decoded → t.$name", q"dbo.as[$returnType]($decoded)")
}.unzip
c.Expr[Mappable[A,DBObject]] { q"""
new Mappable[$tpe] {
def toMap(t: $tpe): DBObject = MongoDBObject(..$toDBObject)
def fromMap(dbo: DBObject): $tpe = $companion(..$fromDBObject)
}
""" }
}
}
这就是我使用它的方式
package com.fullfacing.ticketing.common.dao
import com.fullfacing.ticketing.common.model._
import com.fullfacing.ticketing.macros.{MongoMappable, Mappable}
import com.fullfacing.ticketing.macros.MongoMappable._
import com.mongodb.DBObject
import com.mongodb.casbah.commons.MongoDBObject
import com.mongodb.casbah.Imports._
import com.mongodb.casbah.query.Imports
/**
* Project: com.fullfacing.liono.common.data.dao
* Created on 2015/05/11.
* ryno aka lemonxah -
* https://github.com/lemonxah
* http://stackoverflow.com/users/2919672/lemon-xah
*/
class MongoDAO[A <: Model](coll: MongoCollection) extends DAO[A, DBObject] {
import MongoDAO._
def interpreter(q: Query): Option[DBObject] = {
def loop(q: Query, acc: MongoDBObject): Imports.DBObject = q match {
case Or(left, right) => acc ++ $or(loop(left, MongoDBObject()), loop(right, MongoDBObject()))
case And(left, right) => acc ++ $and(loop(left, MongoDBObject()), loop(right, MongoDBObject()))
case Equals(f, v: Int) => f.name $eq v
case Equals(f, v: String) => f.name $eq v
case GreaterOrEqual(f, v: Int) => f.name $gte v
case GreaterThan(f, v: Int) => f.name $gt v
case LessOrEqual(f, v: Int) => f.name $lte v
case LessThan(f, v: Int) => f.name $lt v
case NotEquals(f, v: Int) => f.name $ne v
case NotEquals(f, v: String) => f.name $ne v
case _ => throw new UnsupportedOperationException()
}
try { Some(loop(q,MongoDBObject())) } catch {
case e: UnsupportedOperationException => None
}
}
override def list: Vector[A] = {
coll.find().toVector.map(mapf)
}
override def filter(query: Query): Vector[A] = {
interpreter(query) match {
case Some(q) => coll.find(q).toVector.map(mapf)
case None => Vector()
}
}
override def headOption(query: Query): Option[A] = {
interpreter(query) match {
case Some(q) => coll.find(q).toVector.map(mapf).headOption
case None => None
}
}
def insert(a: A) {
coll.insert(MongoDAO.mapt[A](a))
}
def update(a: A): Unit = {
// val q = MongoDBObject("id" -> a.id.id)
// val u = $set(toMap(a).toList: _*)
// coll.update(q,u)
}
override def delete(a: A): Unit = ???
override def delete(query: Query): Unit = {
interpreter(query) match {
case Some(q) => coll.findAndRemove(q)
case None =>
}
}
}
object MongoDAO {
def apply[A <: Model](coll: MongoCollection): MongoDAO[A] = new MongoDAO[A](coll)
def mapt[A: Mappable](a: A) = implicitly[Mappable[A, DBObject]].toDBType(a)
def mapf[A: Mappable](b: DBObject) = implicitly[Mappable[A, DBObject]].fromDBType(b)
}
这就是我想要使用它的方式
这是我的DAO特征和解释器的查询AST
package com.fullfacing.ticketing.common.dao
import com.fullfacing.ticketing.common.model.Model
/**
* Project: com.fullfacing.liono.common.data.dao
* Created on 2015/05/19.
* ryno aka lemonxah -
* https://github.com/lemonxah
* http://stackoverflow.com/users/2919672/lemon-xah
*/
trait DAO[A <: Model, B] {
def interpreter(q: Query): Option[B]
def +=(a: A) = insert(a)
def insert(a: A)
def list: Vector[A]
def filter(query: Query): Vector[A]
def headOption(query: Query): Option[A]
def update(a: A)
def delete(a: A)
def delete(query: Query)
}
case class Field[A](name: String) {
def ===(value: A): Query = Equals(this, value)
def !==(value: A): Query = NotEquals(this, value)
def <(value: A): Query = LessThan(this, value)
def >(value: A): Query = GreaterThan(this, value)
def >=(value: A): Query = GreaterOrEqual(this, value)
def <=(value: A): Query = LessOrEqual(this, value)
}
sealed trait Query { self =>
def &&(t: Query): Query = and(t)
def and(t: Query): Query = And(self, t)
def ||(t: Query): Query = or(t)
def or(t: Query): Query = Or(self, t)
}
sealed trait Operator extends Query { def left: Query; def right: Query}
case class Or(left: Query, right: Query) extends Operator
case class And(left: Query, right: Query) extends Operator
sealed trait Operand[+A] extends Query { def field: Field[_]; def value: A }
case class GreaterOrEqual[A](field: Field[A], value: A) extends Operand[A]
case class GreaterThan[A](field: Field[A], value: A) extends Operand[A]
case class LessOrEqual[A](field: Field[A], value: A) extends Operand[A]
case class LessThan[A](field: Field[A], value: A) extends Operand[A]
case class Equals[A](field: Field[A], value: A) extends Operand[A]
case class NotEquals[A](field: Field[A], value: A) extends Operand[A]
所有这一切的想法是这样我可以像MongoDAO一样创建新的DAO并且我不必更改大量代码只需添加数据库实现并且所有顶级代码保持不变,可能更改导入或使用不同的数据库上下文,它应该工作。
但在这样做的时候我遇到了这个问题
Error:(81, 50) not enough arguments for method implicitly: (implicit e: com.fullfacing.ticketing.macros.Mappable[A,com.mongodb.DBObject])com.fullfacing.ticketing.macros.Mappable[A,com.mongodb.DBObject].
Unspecified value parameter e.
def mapf[A: Mappable](b: DBObject) = implicitly[Mappable[A, DBObject]].fromDBType(b)
^
我不确定我想做的事情是否可能这样......但我想不要手动制作像下面这样的地图制作者。
object DeviceMap extends DAOMap[Device, DBObject] {
def mapt(device: Device): DBObject = {
MongoDBObject(
"uuid" -> device.uuid,
"created" -> device.created,
"id" -> device.id.id
)
}
def mapf(o: DBObject): Device = {
Device(
uuid = o.as[String]("uuid"),
created = o.as[Long]("created"),
id = Id(o.as[UUID]("id"))
)
}
}
因为对于将在此工具的最终版本中实现的所有数据对象执行此操作非常繁琐