title: Scala Macro to make generic mappers for my DAO
body: <p>so this is my macro</p>
  
  <pre><code>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)
        }
      """ }
    }
  }
  </code></pre>
  
  <p>and this is how i am using it</p>
  
  <pre><code>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 &lt;: 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) =&gt; acc ++ $or(loop(left, MongoDBObject()), loop(right, MongoDBObject()))
        case And(left, right) =&gt; acc ++ $and(loop(left, MongoDBObject()), loop(right, MongoDBObject()))
        case Equals(f, v: Int) =&gt; f.name $eq v
        case Equals(f, v: String) =&gt; f.name $eq v
        case GreaterOrEqual(f, v: Int) =&gt; f.name $gte v
        case GreaterThan(f, v: Int) =&gt; f.name $gt v
        case LessOrEqual(f, v: Int) =&gt; f.name $lte v
        case LessThan(f, v: Int) =&gt; f.name $lt v
        case NotEquals(f, v: Int) =&gt; f.name $ne v
        case NotEquals(f, v: String) =&gt; f.name $ne v
        case _ =&gt; throw new UnsupportedOperationException()
      }
      try { Some(loop(q,MongoDBObject())) } catch {
        case e: UnsupportedOperationException =&gt; None
      }
    }
  
    override def list: Vector[A] = {
      coll.find().toVector.map(mapf)
    }
  
    override def filter(query: Query): Vector[A] = {
      interpreter(query) match {
        case Some(q) =&gt; coll.find(q).toVector.map(mapf)
        case None =&gt; Vector()
      }
    }
  
    override def headOption(query: Query): Option[A] = {
      interpreter(query) match {
        case Some(q) =&gt; coll.find(q).toVector.map(mapf).headOption
        case None =&gt; None
      }
    }
  
    def insert(a: A) {
      coll.insert(MongoDAO.mapt[A](a))
    }
  
    def update(a: A): Unit = {
  //    val q = MongoDBObject("id" -&gt; 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) =&gt; coll.findAndRemove(q)
        case None =&gt;
      }
    }
  }
  
  object MongoDAO {
    def apply[A &lt;: 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)
  }
  </code></pre>
  
  <p>well that is how i want to be using it</p>
  
  <p>this is my DAO trait and the query AST for the interpreter</p>
  
  <pre><code>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 &lt;: 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 &lt;(value: A): Query = LessThan(this, value)
    def &gt;(value: A): Query = GreaterThan(this, value)
    def &gt;=(value: A): Query = GreaterOrEqual(this, value)
    def &lt;=(value: A): Query = LessOrEqual(this, value)
  }
  sealed trait Query { self =&gt;
    def &amp;&amp;(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]
  </code></pre>
  
  <p>the idea of all of this is so that i can make new DAOs like the MongoDAO and i dont have to change a lot of code just add a db implementation and all the top level code stays the same, maybe change a import or use a different db context and it should just work. </p>
  
  <p>but while doing this i am getting this issue </p>
  
  <pre><code>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)
                                               ^
  </code></pre>
  
  <p>i am not sure if what i want to do is possible like this .. but i want to refrain from having make mappers like the following manually.</p>
  
  <pre><code>  object DeviceMap extends DAOMap[Device, DBObject] {
      def mapt(device: Device): DBObject = {
        MongoDBObject(
          "uuid" -&gt; device.uuid,
          "created" -&gt; device.created,
          "id" -&gt; 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"))
        )
      }
    }
  </code></pre>
  
  <p>because that would be very tedious to do it for all the data objects that will be implemented in the final version of this tool</p>