Skip to content

Instantly share code, notes, and snippets.

@poetix
Created January 15, 2013 12:26
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save poetix/4538252 to your computer and use it in GitHub Desktop.
Save poetix/4538252 to your computer and use it in GitHub Desktop.
Dynamic / Monadic object builder round 2
package com.youdevise.lofty
import org.specs2.mutable._
import scala.language.dynamics
import scala.language.reflectiveCalls
import scalaz._
import Scalaz._
import scala.reflect.runtime.universe._
trait Builder[T] {
def buildFrom(properties: Map[String, _]):T
}
case class RecordedCall(target:BuildTarget[_], methodName:String, parameters:Seq[Any]) {
lazy val property = methodName -> parameters.head
}
case class Recording(val calls:Seq[RecordedCall])
object Recording {
val emptyRecording = Recording(Seq.empty[RecordedCall])
def builder[T](implicit builder:Builder[T]) = state[Recording, BuildTarget[T]]((recording:Recording) => (recording, new BuildTarget[T](builder)))
def buildFrom[T](state: State[Recording, BuildTarget[T]]):T = {
val (recording, target) = state(emptyRecording)
val transcripts = recording.calls.groupBy(_.target)
target.buildFrom(transcripts)
}
}
import Recording._
class BuildTarget[T](val builder:Builder[T]) extends Dynamic {
private def record(name:String, args:Any*) = {
val target = this
state[Recording, BuildTarget[T]] { (recording:Recording) => (
new Recording(recording.calls :+ RecordedCall(target, name, args.toSeq)),
target)
}
}
def applyDynamic(name:String)(args:Any*) = record(name, args:_*)
def buildFrom(transcripts: Map[BuildTarget[_], Seq[RecordedCall]]):T = {
val myTranscript = transcripts(this)
val properties = myTranscript.map(_.property).toMap
val reifiedProperties = properties.mapValues {
case target: BuildTarget[_] => target.buildFrom(transcripts)
case value: Any => value
}
builder.buildFrom(reifiedProperties)
}
}
class RecorderSpec extends Specification {
case class Address(lines:Seq[String], postcode:String)
case class Person(name:String, age: Option[Int], address:Address)
implicit object AddressBuilder extends Builder[Address] {
def buildFrom(properties: Map[String, _]) = Address(properties("lines").asInstanceOf[Seq[String]],
properties("postcode").asInstanceOf[String])
}
implicit object PersonBuilder extends Builder[Person] {
def buildFrom(properties: Map[String, _]) = Person(properties("name").asInstanceOf[String],
properties.get("age").asInstanceOf[Option[Int]],
properties("address").asInstanceOf[Address])
}
"""A recorder""" should {
"""Capture all method calls within the recorder monad""" in {
val withoutAge = for {
address <- builder[Address]
_ <- address lines Seq("12 Maudlin Street", "Manchester")
_ <- address postcode("VB6 5UX")
person <- builder[Person]
_ <- person name "Stephen Patrick"
_ <- person address address
} yield person
val person = buildFrom(withoutAge)
person must beEqualTo(Person("Stephen Patrick", None, Address(Seq("12 Maudlin Street", "Manchester"), "VB6 5UX")))
val withAge = for {
person <- withoutAge
_ <- person age 63
} yield person
val personWithAge = buildFrom(withAge)
personWithAge must beEqualTo(Person("Stephen Patrick", Some(63), Address(Seq("12 Maudlin Street", "Manchester"), "VB6 5UX")))
val movedToDulwich = for {
newAddress <- builder[Address]
_ <- newAddress lines Seq("42 Penguin Ave", "Dulwich")
_ <- newAddress postcode("E17 4TW")
person <- withoutAge
_ <- person address newAddress
} yield person
buildFrom(movedToDulwich) must beEqualTo(Person("Stephen Patrick", None, Address(Seq("42 Penguin Ave", "Dulwich"), "E17 4TW")))
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment