Created
January 15, 2013 12:26
-
-
Save poetix/4538252 to your computer and use it in GitHub Desktop.
Dynamic / Monadic object builder round 2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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