How to override binding in subling child submodules in scala play - scala

I have scala play multimodule application with 3 modules: common, moduleA and moduleB where moduleA and moduleB depend on common.
ModuleA and moduleB should bind different implementation for trait defined in common.
If I try to bind it this way:
class ModuleA extends AbstractModule with AkkaGuiceSupport {
override def configure(): Unit = {
bind(classOf[Foo]).to(classOf[ModuleAFooImpl])
}
}
class ModuleB extends AbstractModule with AkkaGuiceSupport {
override def configure(): Unit = {
bind(classOf[Foo]).to(classOf[ModuleBFooImpl])
}
}
I'm getting error:
1) A binding to Foo was already configured at ModuleB.configure(ModuleB.scala:11) (via modules: com.google.inject.util.Modules$OverrideModule -> ModuleB).
Foo is used in ActionBuilder in common submodule in this way:
class CustomAction #Inject()(foo: Foo) extends ActionBuilder[Request]{
override def invokeBlock {
...
case Left(_) => Future.successful(foo.onUnauthenticated())
...
}
}
I've tried to used named binding, like this:
bind(classOf[Foo]).annotatedWith(Names.named("ModuleAFoo")).to(classOf[ModuleBFooImpl])
but it didn't work.
Does anyone know if it's possible to bind different implementations for the same trait in two subling submodules?

Related

How to properly bind a trait to its impl when the later one has implicit parameters

[Please forgive me for the long question, I'm still learning to Scala.]
I'm trying to bind a generic trait to its generic impl who has implicit parameters. Here's the cleanup code:
trait PersistenceService[T <: SomeOtherClass] {
def persist(record: T): Future[Unit]
}
class MongoPersistenceService[T <: SomeOtherClass] #Inject()(implicit ec: ExecutionContext, tag: ClassTag[T]) extends PersistenceService[T] {
val collectionName: String = tag.runtimeClass.getSimpleName
val databaseName = "someDatabase"
def db: Future[DefaultDB] = MongoConnectionWrapper.getMongoConnection("mongodb://127.0.0.1", "27017")
.flatMap(_.database(databaseName))
def collection: Future[BSONCollection] = db.map(_.collection(collectionName))
def persist(record: T): Future[Unit] = {
val result = for {
col <- collection
writeResult <- col.insert(record)
} yield writeResult
result.recoverWith {
case WriteResult.Code(11000) => throw RecordAlreadyExistsException(record,
"")
}.map(_ => ())
}
def read(id: BSONObjectID): Future[T] = {
val query = BSONDocument("_id" -> id)
val readResult: Future[T] = for {
coll <- collection
record <- coll.find(query).requireOne[T]
} yield record
readResult.recoverWith {
case NoSuchResultException => throw RecordNotFoundException(id)
}
}
}
I'm using Play, ReactiveMongo and ScalaGuice (all latest versions). So here's my main Module class binding everything:
class Module(env: Environment, config: Configuration) extends AbstractModule with ScalaModule {
def configure(): Unit = {
bind[PersistenceService[_]].to[MongoPersistenceService[_]] // Also tried with specific class instead of _ but not working either
}
}
And let's say I have one of my controller with dependency on PersistenceService like this:
class PersistenceServiceController #Inject()(val PersistenceService: PersistenceService[Bar], cc ControllerComponents) extends AbstractController(cc) { ... }
And the model (as you can probably guess) with its implicits Reader/Writer:
case class Bar() extends SomeOtherClass() {}
object Bar {
implicit object BarReader extends BSONDocumentReader[Bar] {
def read(doc: BSONDocument): Bar = { ... }
}
implicit object BarWriter extends BSONDocumentWriter[Bar] {
def write(bar: Bar): BSONDocument = { ... }
}
}
With all this stuffs, I'm getting the following runtime exception:
com.google.inject.CreationException: Unable to create injector, see the following errors:
1) No implementation for reactivemongo.bson.BSONDocumentReader<Bar> was bound.
while locating reactivemongo.bson.BSONDocumentReader<Bar>
for the 2nd parameter of MongoPersistenceService.<init>(MongoPersistenceService.scala:15)
at Module.configure(Module.scala:14) (via modules: com.google.inject.util.Modules$OverrideModule -> Module)
2) No implementation for reactivemongo.bson.BSONDocumentWriter<Bar> was bound.
while locating reactivemongo.bson.BSONDocumentWriter<Bar>
for the 3rd parameter of persistence.MongoPersistenceService.<init>(MongoPersistenceService.scala:15)
at Module.configure(Module.scala:14) (via modules: com.google.inject.util.Modules$OverrideModule -> Module)
3) No implementation for scala.reflect.ClassTag<Bar> was bound.
while locating scala.reflect.ClassTag<Bar>
for the 5th parameter of MongoPersistenceService.<init>(MongoPersistenceService.scala:15)
at Module.configure(Module.scala:14) (via modules: com.google.inject.util.Modules$OverrideModule -> Module)
So clearly, the stuffs my class MongoPersistenceService should get in the execution context are missing some how. I understand that Play is kind of magically providing the execution context when you setup your stuffs properly with guice. But in that case, looks like it's not working.
How can I fix that?
Well I'm feeling bad about this one but the error message was pretty obvious to find the issue. To fix it, I had to manually bind an impl for BSONDocumentReader[Bar], BSONDocumentWriter[Bar] and ClassTag[Bar].
I since refactored my code to something much simpler.
But wanted to let know other people what was the issue.

Scala: Let trait depend on other trait

I want to write a family of traits whose methods should log something and a Logger trait that should be implemented in concrete Loggers and it should only be possible to mix in the above traits when a Logger is mixed in as well. I only know that a trait can depend on a class, i.e. it can only be mixed into classes who have this class as super type. What can I do?
It sounds like you need self types e.g.
trait Logger {
def log(msg: String): Unit
}
trait ConsoleLogger extends Logger {
def log(msg: String): Unit = { println(msg) }
}
trait NeedsLogger { self: Logger =>
def doSomething(): Unit = {
self.log("about to do something...")
self.log("it worked!")
}
}
object Test extends NeedsLogger with ConsoleLogger {
}
Test.doSomething()

Scala - Using guice multibinder on a generic interface fails

I have the following interface:
trait Subject[T] {
def fetch() :Future[Option[T]]
}
And class:
class ChildSubject#Inject()(dao:Dao) extends Subject[String]{
def fetch(): Future[Option[String]] ={
dao.find("10").map{ name => Some(name)
}
}
And a module:
class SubjectModule extends AbstractModule with ScalaModule{
override def configure(): Unit = {
val subMulti = ScalaMultibinder.newSetBinder[Subject](binder)
subMulti.addBinding.to[ChildSubject]
}
}
And I try to inject this Set:
#Singleton
class SomeClass #Inject()(subjects: Set[Subject]){
subjects.map{
//do somthing
}
}
And I get the following error:
play.sbt.PlayExceptions$CompilationException: Compilation error[kinds
of the type arguments (com.test.Subject) do not conform to the expected
kinds of the type parameters (type T).
com.test.Subject's type parameters do not match type T's expected
parameters:
trait Subject has one type parameter, but type T has none]
Any idea??
Thanks!
You need a new TypeLiteral<Subject<String>>() for binding - generically typed interface like Subject<T> in Guice from Java. It's very likely that Scala requires the same in some form.
Something like this may work:
class SubjectModule extends AbstractModule with ScalaModule{
override def configure(): Unit = {
val subMulti = ScalaMultibinder.newSetBinder[Subject[String]](binder)
subMulti.addBinding.to[ChildSubject]
}
}

How to use a function in two packages in Scala?

I have two packages in Scala: package foo and package bar. Both have a Utils object in them that has same methods doSomething() and doSomethingElse(). Code looks like this:
Inside package foo
object Utils{
def doSomething()
def doSomethingElse()
}
Inside package bar
object Utils{
def doSomething()
def doSomethingElse()
}
Is there a way I can provide a common ground from where both the packages can use the methods in Utils object? I want to avoid code duplication. What I have in mind is to write a package object in one of the packages and import that in the other package. But is there a better way to eliminate code duplication?
package objects can inherit from other traits, abstract classes and classes, just like any other object. Simply provide the methods in an appropriately named trait and include it in both:
trait SomethingHandler {
def doSomething() = ???
def doSomethingElse() = ???
}
package object foo extends SomethingHandler
package object bar extends SomethingHandler
Alternatively, you can use an object and import it into both package namespaces:
object SomethingHandler {
def doSomething() = ???
def doSomethingElse() = ???
}
package object foo {
import some.package.SomethingHandler._
}
package object bar {
import some.package.SomethingHandler._
}
Place the Utils object in a separate package util and import it where you use it?
Or else alias it in your package objects:
package object foo {
val Utils = util.Utils
}
If they don't have all their methods in common, factor out common methods in a trait:
trait Common {
def doSomething() = ???
def doSomethingElse() = ???
}
object Utils extends Common {
...
}

Scala self-typed trait and calling method on supertype

Lets say I have a class like this
class Job(args:String) {
def config:Map[String,String] = ...
}
I want to create a trait that can be mixed in with this class to add more configuration options. So I did something like this
trait ExtraConfig { self:Job =>
override def config = self.config ++ Map("extra","config")
}
unfortunately this doesnt compile as self.config becomes self recursive.
I tried doing
trait ExtraConfig extends Job {}
but job is taking a constructor param and I dont know how to thread that through.
What would be the best way to solve this?
You are looking for abstract override modifier, but to use it you need explicitly extend the Job class, then in you trait you can use members of your superclass:
class Job(args:String) {
def config:Map[String,String] = Map.empty
}
trait ExtraConfig extends Job {
abstract override def config =
super.config ++ Map("extra" -> "config")
}
val job = new Job("name") with ExtraConfig