I'm updating my application from Specs2 2.3.12 to 3.6.1 and am having trouble updating the class which wraps our unit tests in a transaction.
The 2.3.12 class:
class DbTx extends AroundOutside[Session] {
var session: Option[Session] = None
def around[T : AsResult](t: => T) = {
Db.withTransaction { implicit s =>
session = Some(s)
val result = AsResult(t)
s.rollback()
result
}
}
def outside: Session = session.get
}
its usage:
"my unit test" in (new DbTx).apply { implicit session: Session =>
...
}
What I've tried in 3.6.1
class DbTx extends ForEach[Session] {
var session: Option[Session] = None
def foreach[T : AsResult](t: Session => T) = {
Db.withTransaction { implicit s =>
session = Some(s)
val result = AsResult(t)
s.rollback()
result
}
}
}
its usage:
"my unit test" in (new DbTx).foreach { implicit session: Session =>
...
}
but this seemed to produce an infinite loop between lines 6 & 4 of that block.
I also tried
class DbTx extends Around {
def around[T: AsResult](t: => T): Result = {
super.around {
Db.withTransaction { implicit s: Session =>
val result = AsResult(t)
s.rollback()
result
}
}
}
}
its usage:
"my unit test" in (new DbTx).around { implicit session: Session =>
...
}
but that results in
could not find implicit value for evidence parameter of type AsResult[Session => MatchResult[ ... ]]
I also tried
class DbTx extends Fixture[Session] {
def apply[T: AsResult](t: Session => T): Result = {
Db.withTransaction { implicit s: Session =>
val result = AsResult(t)
s.rollback()
result
}
}
}
its usage:
"my unit test" in (new DbTx) { implicit session: Session =>
...
}
which results in
could not find implicit value for evidence parameter of type AsResult[Session => T]
Edit
I'm also getting an infinite loop with this code:
import org.specs2.execute.AsResult
import org.specs2.mutable.Specification
import org.specs2.specification.ForEach
class DbTxSpec extends Specification with ForEach[Session] {
def foreach[T: AsResult](t: Session => T) = {
Db.withTransaction { implicit s => // infinite loop between here
try AsResult(t) // and here
finally s.rollback()
}
}
"my unit test" in { implicit session: Session =>
true must beTrue
}
}
If you want to pass in a Session you need to use have your specification extend the ForEach trait, not a special object. Something like:
class DbTxSpec extends Specification with ForEach[Session] {
var session: Option[Session] = None
def foreach[T : AsResult](t: Session => T) = {
Db.withTransaction { implicit s =>
session = Some(s)
try AsResult(t(session))
finally s.rollback()
}
}
"my unit test" in { implicit session: Session =>
...
}
}
Related
I have multiple actors managing data models that are written to a mongo db.
object LevelManager {
val collectionName = "levels"
}
#Singleton
class LevelManager #Inject()(
val reactiveMongoApi: ReactiveMongoApi) extends Actor with ActorLogging with InjectedActorSupport {
def collection: Future[JSONCollection] = reactiveMongoApi.database.map(_.collection[JSONCollection](LevelManager.collectionName))
override def receive: Receive = {
case msg:GetById =>
var level = collection.flatMap(c => c.find(Json.obj("_id" -> msg.id), Option.empty[JsObject]).one[LevelModel].map {
result =>
logger.info( result )
}
}
}
This works fine, but this db code is used in every actor and i did not manage to have it only once. I'm not sure if this is even a clever way, too. It derived from older scala times without dependency injection, where everything was put in an object trait.
So i'm looking for a trait or something, with basic db io handling
Edit: Before dependency injection i was able to use a trait like this:
trait BaseModel[T] {
val collectionName: String
val db = ReactiveMongoPlugin.db
def load(id: Long)(implicit fmt: Format[T]) = {
val coll = db.collection[JSONCollection](collectionName)
coll.find(Json.obj("_id" -> id)).one[T]
}
def loadAll()(implicit fmt: Format[T]) = {
val coll = db.collection[JSONCollection](collectionName)
coll.find(Json.obj()).cursor[T].collect[Vector]()
}
def save(id: Long, model: T)(implicit fmt: Format[T]) = {
val coll = db.collection[JSONCollection](collectionName)
val doc = Json.toJson(model).as[JsObject] + ("_id" -> Json.toJson(id))
coll.save(doc).map { lastError =>
if (!lastError.ok) Logger.error(lastError.message)
lastError.ok
}
}
I ended in creating a trait with def collection: Future[JSONCollection] and i'm now able to access the db my favorite db functions. This was my goal and makes life so much better. But i'm unsettled from the recent feedback here, if this has any disadvantages.
trait DBStuff[T] {
def collection: Future[JSONCollection]
def log: LoggingAdapter
def save(id: String, model: T)(implicit fmt: Format[T]) = {
val doc:JsObject = Json.toJson(model).as[JsObject] + ("_id" -> Json.toJson(id))
collection.flatMap(_.update.one(Json.obj("_id" -> id), doc, true)).map(lastError => if (!lastError.ok) log.warning(s"Mongo LastError: %s".format(lastError)))
}
def loadOne(id: String)(implicit fmt: Format[T]): Future[Option[T]] = loadOne( Json.obj("_id" -> id) )
def loadOne(obj: JsObject, projection:Option[JsObject] = None )(implicit fmt: Format[T]): Future[Option[T]] = {
collection.flatMap(_.find( obj, projection).one[T].map {
result =>
result
}.recover {
case err => log.error(s"DB Loading Error: $err")
None
})
}
def loadAll()(implicit fmt: Format[T]):Future[Vector[T]] = {
loadAll(Json.obj(), None )
}
def loadAll( obj: JsObject, projection:Option[JsObject] = None)(implicit fmt: Format[T]):Future[Vector[T]] = {
collection.flatMap(_.find(obj, projection ).cursor[T]().collect[Vector](Int.MaxValue, Cursor.FailOnError()).map {
result => result
}.recover {
case err =>
log.error(s"DB Loading Error: $err")
Vector.empty
})
}
...
}
#Singleton
class TaskManager #Inject()(
val reactiveMongoApi: ReactiveMongoApi
) extends Actor with ActorLogging with InjectedActorSupport with DBStuff[TaskModel] {
def collection: Future[JSONCollection] = reactiveMongoApi.database.map(_.collection[JSONCollection](TaskManager.collectionName))
override def preStart() = {
loadAll() map {
result =>
//What ever
}
}
Cannot compile even if a function that handles function arguments is passed to macro.
A sample is shown below.
trait Generated[Z] {
def deserialize[A](t: A): Z
}
def from[A, Z](apl: A => Z): Generated[Z] = macro GeneratorMacro.from[A, Z]
class GeneratorMacro(val c: blackbox.Context) {
import c.universe._
def from[A: c.WeakTypeTag, Z: c.WeakTypeTag](apl: c.Expr[A => Z]): c.Expr[Generated[Z]] = {
reify {
new Generated[Z] {
def deserialize[A](t: A): Z = {
apl.splice.apply(t)
}
}
}
}
}
object Generation {
def apply(input: String): Generated[Int] = {
Generator.from[Option[String], Int] {
case Some(i) => input.toInt + i.toInt // compilation failed
case None => 0
}
}
}
An error occurs at this time.
Error: scalac: Error while emitting Generation.scala
value input
Isn't the class recompiled with the macro expanded?
If recompiled with inline expansion, no compilation error should occur.
object Generation {
def apply(input: String): Generated[Int] = {
new Generated[Int] {
def deserialize(t: String): Int = {
{
case Some(i) => input.toInt + i.toInt // input should be visible
case None => 0
}.apply(t)
}
}
}
}
What is going on and how to avoid it.
This seems to be impossible, AFAICS. The compiler creates an anonymous class for your code, but it will not capture the lexical context of the macro call inside it.
The code looks a bit like this after the lambdalift phase:
def apply(input: String): Generated[Int] = ({
new <$anon: Generated>()
}: Generated);
...
final class $anon extends Object with Generated {
def deserialize(t: Option): Int = {
... // <- your code is here !!
};
}
Of course, the code has no access to the input variable at this place.
This might be a bug in the Scala compiler...
The first error I have is
Warning:scalac: {
final class $anon extends App.this.Generated[Int] {
def <init>() = {
super.<init>();
()
};
def deserialize(t: Option[String]): Int = ((x0$1: Option[String]) => x0$1 match {
case (value: String)Some[String]((i # _)) => scala.Predef.augmentString(input).toInt.+(scala.Predef.augmentString(i).toInt)
case scala.None => 0
}).apply(t)
};
new $anon()
}
Error:(6, 43) object creation impossible. Missing implementation for:
def deserialize[A](t: A): Int // inherited from trait Generated
Generator.from[Option[String], Int] {
Obviously this is because you define
reify {
new Generated[Z] {
def deserialize(t: A): Z = {
...
instead of def deserialize[A](t: A): Z (#OlegPyzhcov pointed this out in the comments).
Regarding your error Error while emitting ... the thing is that
{
case Some(i: String) => input.toInt + i.toInt
case None => 0
}
has type not ... => ... i.e. Function1[..., ...] but actually PartialFunction[..., ...].
Try either
object Generator {
def from[Z](apl: PartialFunction[Any, Z]): Generated[Z] = macro GeneratorMacro.from[Z]
}
class GeneratorMacro(val c: blackbox.Context) {
import c.universe._
def from[Z: c.WeakTypeTag](apl: c.Expr[Any => Z]): c.Expr[Generated[Z]] = {
reify {
new Generated[Z] {
def deserialize[A](t: A): Z = {
apl.splice.apply(t)
}
}
}
}
}
Generator.from[Int]({
case Some(i: String) => input.toInt + i.toInt
case None => 0
})
or
object Generator {
def from[Z](apl: Any => Z): Generated[Z] = macro GeneratorMacro.from[Z]
}
class GeneratorMacro(val c: blackbox.Context) {
import c.universe._
def from[Z: c.WeakTypeTag](apl: c.Expr[Any => Z]): c.Expr[Generated[Z]] = {
reify {
new Generated[Z] {
def deserialize[A](t: A): Z = {
apl.splice.apply(t)
}
}
}
}
}
Generator.from[Int](new (Any => Int) {
override def apply(x: Any): Int = x match {
case Some(i: String) => input.toInt + i.toInt
case None => 0
}
})
In my Play application, I service my requests usings cats-effect's IO, instead of Future in the controller, like this (super-simplified):
def handleServiceResult(serviceResult: ServiceResult): Result = ...
def serviceMyRequest(request: Request): IO[ServiceResult] = ...
def myAction = Action { request =>
handleServiceResult(
serviceMyRequest(request).unsafeRunSync()
)
}
Requests are then processed (asynchronously) on Play's default thread pool. Now, I want to implement multiple thread pools to handle different sorts of requests. Were I using Futures, I could do this:
val myCustomExecutionContext: ExecutionContext = ...
def serviceMyRequest(request: Request): Future[ServiceResult] = ...
def myAction = Action.async { request =>
Future(serviceMyRequest(request))(myCustomExecutionContext)
.map(handleServiceResult)(defaultExecutionContext)
}
But I'm not using Futures, I'm using IO, and I'm not sure about the right way to go about implementing it. This looks promising, but seems a bit clunky:
def serviceMyRequest(request: Request): IO[ServiceResult] = ...
def myAction = Action { request =>
val ioServiceResult = for {
_ <- IO.shift(myCustomExecutionContext)
serviceResult <- serviceMyRequest(request)
_ <- IO.shift(defaultExecutionContext)
} yield {
serviceResult
}
handleServiceResult(ioServiceResult.unsafeRunSync())
}
Is this the right way to implement it? Is there a best practice here? Am I screwing up badly? Thanks.
Ok, so since this doesn't seem to be well-trodden ground, this is what I ended up implementing:
trait PlayIO { self: BaseControllerHelpers =>
implicit class IOActionBuilder[A](actionBuilder: ActionBuilder[Request, A]) {
def io(block: Request[A] => IO[Result]): Action[A] = {
actionBuilder.apply(block.andThen(_.unsafeRunSync()))
}
def io(executionContext: ExecutionContext)(block: Request[A] => IO[Result]): Action[A] = {
val shiftedBlock = block.andThen(IO.shift(executionContext) *> _ <* IO.shift(defaultExecutionContext))
actionBuilder.apply(shiftedBlock.andThen(_.unsafeRunSync()))
}
}
}
Then (using the framework from the question) if I mix PlayIO into the controller, I can do this,
val myCustomExecutionContext: ExecutionContext = ...
def handleServiceResult(serviceResult: ServiceResult): Result = ...
def serviceMyRequest(request: Request): IO[ServiceResult] = ...
def myAction = Action.io(myCustomExecutionContext) { request =>
serviceMyRequest(request).map(handleServiceResult)
}
such that I execute the action's code block on myCustomExecutionContext and then, once complete, thread-shift back to Play's default execution context.
Update:
This is a bit more flexible:
trait PlayIO { self: BaseControllerHelpers =>
implicit class IOActionBuilder[R[_], A](actionBuilder: ActionBuilder[R, A]) {
def io(block: R[A] => IO[Result]): Action[A] = {
actionBuilder.apply(block.andThen(_.unsafeRunSync()))
}
def io(executionContext: ExecutionContext)(block: R[A] => IO[Result]): Action[A] = {
if (executionContext == defaultExecutionContext) io(block) else {
val shiftedBlock = block.andThen(IO.shift(executionContext) *> _ <* IO.shift(defaultExecutionContext))
io(shiftedBlock)
}
}
}
}
Update2:
Per the comment above, this will ensure we always shift back to the default thread pool:
trait PlayIO { self: BaseControllerHelpers =>
implicit class IOActionBuilder[R[_], A](actionBuilder: ActionBuilder[R, A]) {
def io(block: R[A] => IO[Result]): Action[A] = {
actionBuilder.apply(block.andThen(_.unsafeRunSync()))
}
def io(executionContext: ExecutionContext)(block: R[A] => IO[Result]): Action[A] = {
if (executionContext == defaultExecutionContext) io(block) else {
val shiftedBlock = block.andThen { ioResult =>
IO.shift(executionContext).bracket(_ => ioResult)(_ => IO.shift(defaultExecutionContext))
}
io(shiftedBlock)
}
}
}
}
I want TestHttp class to be able to receive http requests or messages from other actors. How can I do it?
Code:
object Main extends App with SimpleRoutingApp {
implicit val system = ActorSystem("system")
import system.dispatcher
implicit val timeout = Timeout(240.seconds)
startServer(interface = "localhost", port = 3000) {
get {
path("register" / IntNumber) { n =>
respondWithMediaType(MediaTypes.`application/json`) { ctx =>
val future = IO(Http) ? Bind(system.actorOf(Props[TestHttp]), interface = "localhost", port = 3000 + n)
future onSuccess {
case Http.Bound(msg) => ctx.complete(s"Ok:"+msg)
case _ => ctx.complete("...")
}
}
} // : Route == RequestContext => Unit
} // : Route
}
}
trait TestHttpService extends HttpService {
val oneRoute = {
path("test") {
complete("test")
}
}
}
class TestHttp extends Actor with TestHttpService {
def actorRefFactory = context
val sealedRoute = sealRoute(oneRoute)
def receive = {
// case HttpRequest(GET, Uri.Path("/ping"), _, _, _) => //not working
// sender ! HttpResponse(entity = "PONG")
case ctx: RequestContext => sealedRoute(ctx) //not working
}
// def receive = runRoute(oneRoute) //it works
}
Actor.Receive is a partial function that takes Any value and returns Unit (PartialFunction[Any, Unit]), so you can do it by regular PF composition.
HttpService.runRoute returns Actor.Receive (see https://github.com/spray/spray/blob/master/spray-routing/src/main/scala/spray/routing/HttpService.scala#L31)
So, your solution would be:
class TestHttp extends Actor with TestHttpService {
def actorRefFactory = context
val sealedRoute = sealRoute(oneRoute)
def receive = {
case s: String => println(s"Just got string $s")
} orElse runRoute(oneRoute)
}
Take a database transaction:
def create(model: Model, orderNum: String) = {
db.handle withSession { implicit ss: Session=>
ss.withTransaction { // auto commit now set to false
val result = for {
uid <- repo.user.create(model)
mid <- repo.membership.create(uid)
oid <- repo.orders.create(model, uid, orderNum)
} yield uid
result fold(
e=> { ss.rollback; Left(e) },
s=> { Cache.remove("member.directory"); Right(s) }
)
}
}
}
If the repository user create implementation takes an implicit Session, is it the same Session as the withTransaction enabled Session above, or is implicit value "is a" rather than "is the" identity?
def create[T <: User](t: T)(implicit ss: Session) = // what Session is this?
for {
uid <- either( Users.insert( Users(t) ), i18n("user not created") )
ur <- either( UserRoles.insert( UserRole(uid, t.role) ), i18n("user role not created") )
} yield uid
I could pass in the Session explicitly repo.user.create(model)(ss) and have create take an explicit Session, but am curious to know if more concise/convenient implicit approach provides the same outcome, the transaction enabled session.
If I correctly understood you, you are using ScalaQuery and you want to have your method working also when the user provide the session from outside.
def withSession
[T] (f: ⇒ T): T Run the supplied thunk with a new session and
automatically close the session at the end.
def withSession [T] (f:
(Session) ⇒ T): T Run the supplied function with a new session and
automatically close the session at the end.
Both of these are creating a new transaction, so the way I would go is to use an Optional[Session] as implicit and default it to None
def onProvidedOrCreatedSession[K](f: Session => K)(session:Option[Session]) = {
session match {
case Some(s) => f(s)
case None => db.withSession { f }
}
}
def create(model: Model, orderNum: String)(implicit session:Option[Session]=None){
onProvidedOrCreatedSession(
implicit s => s.withTransaction { val x = 10 }
)(session)
}
Not sure if the following is a faithful abstraction of what you are trying to do, but I hope it helps:
class Store(var x: Int) {
def flip() { x = -x }
}
object M1 {
implicit val store2 = new Store(2)
def create(s: String) = {
implicit val store3 = new Store(3)
{ implicit store: Store =>
println("[M1.create] store.x = " + store.x)
store.flip()
M2.create("tummy")
println("[M1.create] store.x = " + store.x)
}
}
}
object M2 {
implicit val store4 = new Store(4)
def create(s: String)(implicit store: Store) = {
println("[M2.create] store.x = " + store.x)
store.flip()
}
}
M1.create("dummy")(new Store(1))
The output is:
[M1.create] store.x = 1
[M2.create] store.x = -1
[M1.create] store.x = 1
The new Store(1) that is explicitly passed to M1.create is forwarded to M2.create
The additional implicit stores store2, store3, store4 are apparently ignored by the compiler.