Chris Beach Chris Beach - 2 months ago 15
Scala Question

Binding/Injecting a Scala function (not whole class) using Guice?

I'd like to inject the

byId
method of my
UserDao
object into the constructor of my
Authentication
object. I want to avoid injecting the whole class.

// Has def byId(id: UserId): Option[User]
bind(classOf[UserDao]).asEagerSingleton()

// Something like this
bindMethod(classOf[UserDao], _.byId)

// Constructor takes a (UserId) => Option[User] function
bind(classOf[Authentication]).asEagerSingleton()


I'm using Guice with the Play Framework. Any suggestions appreciated

Answer

You're right to ask this as it can greatly simplify testing. The trick is to use the @Provides annotation:

TL;DR

class MyController @Inject() (byId: (UserId) => Option[User]) extends Controller { ... }

@Provides
def userDao(aDependency: Any): UserDao = // return UserDao, note aDependency will be injected

@Provides
def byId: (UserId) => Option[User] = userDao.byId // note: this method will call the other @Provides method 'userDao'

Explanation

Ultimately byId: (UserId) => Option[User] translates to scala.Function1[UserId, Option[User]] however you can declare function dependencies using the syntactic sugar:

class MyController @Inject() (_addTwo: (Int, Int) => Int) extends Controller {

  def addTwo(a: Int, b: Int) = Action {
    Ok(_addTwo(a, b).toString) // call the injected function 
  }

}

Then in your Module.scala create a method which returns the function:

@Provides
def addTwo: (Int, Int) => Int = (a, b) => a + b

You can do all the usual Scala stuff in the @Provides method, e.g. returning a partially applied function:

@Provides
def addTwo: (Int, Int) => Int = addThree(0, _:Int, _:Int)

def addThree(a: Int, b: Int, c: Int): Int = a + b + c

To avoid conflicts you can also use the @Named annotation:

class MyController @Inject() (@Named("addTwo") _addTwo: (Int, Int) => Int, 
                              @Named("subtractTwo") _subTwo: (Int, Int) => Int) 
                              extends Controller { ... }

@Provides
@Named("addTwo")
def addTwo: (Int, Int) => Int = (a, b) => a + b

@Provides
@Named("subtractTwo")
def subTwo: (Int, Int) => Int = (a, b) => a - b