I have a simple case class Amount as below
case class Amount(value: Long, currency: Currency)
And an accompanying object to convert a string currency code into a Currency object
object Amount {
private val log = Logger(getClass)
def apply(value: Long, currencyCode: String) : Amount = {
try {
Amount(value, Currency.getInstance(currencyCode))
} catch {
case e: Exception =>
log.error(s"Invalid currency code [$currencyCode]")
throw new Exception(s"Invalid currency code [$currencyCode]")
}
}
}
Invocation :
val amount : Amount = Amount(1234, "USD")
When I read some data from the database, I have a custom parser such as
implicit val amountParser = Macro.parser[Amount]("value", "currencyCode")
However, the compiler complains
scala.ScalaReflectionException: value apply encapsulates multiple overloaded alternatives and cannot be treated as a method. Consider invoking `<offending symbol>.asTerm.alternatives` and manually picking the required method
[error] at scala.reflect.api.Symbols$SymbolApi$class.asMethod(Symbols.scala:228)
[error] at scala.reflect.internal.Symbols$SymbolContextApiImpl.asMethod(Symbols.scala:84)
[error] at anorm.Macro$.parserImpl(Macro.scala:70)
[error] at anorm.Macro$.namedParserImpl_(Macro.scala:25)
[error] implicit val amountParser = Macro.parser[Amount]("value", "currencyCode")
How do I make this work ?
UPDATE
After understanding the response from @MikeAllen, I decided to leave the case class Amount
and the object Amount
as is, instead I wrote a custom parser for the Amount as below
implicit private val amountParser = for {
value <- long("value")
currencyCode <- str("currency_code")
} yield {
Amount(value, currencyCode)
}
The Scala compiler will automatically generate an Amount.apply
factory method for creating case class
instances, which is why you're getting this error - because you have multiple Amount.apply
methods. One of these takes arguments of type (Long
, Currency
) and the other takes arguments of type (Long
, String
). The error message suggests that you need to select one of these from the overloaded alternatives reported through reflection.
Alternatively, your case class and companion might be reworked as follows:
final case class Amount(value: Long, currencyCode: String) {
/** Currency. Will create an exception on instantiation if code is invalid. */
val currency: Currency = {
try {
Currency.getInstance(currencyCode)
}
catch {
case e: Exception =>
Amount.log.error(s"Invalid currency code [$currencyCode]")
throw new Exception(s"Invalid currency code [$currencyCode]")
}
}
}
object Amount {
private val log = Logger(getClass)
}
This is not quite as elegant, admittedly, as you now have a field, currency
, that isn't one of the case class's parameters and that isn't available for pattern matching, while also carrying around the string form.
A better solution would be to keep your original case class
and convert the currency code field from a String
into a Currency
, before creating the Amount
instance, as part of the parser:
val amountMapping = {
get[Long]("value") ~ get[String]("currencyCode") map {
case value ~ currencyCode => {
val currency = {
try {
Currency.getInstance(currencyCode)
}
catch {
case e: Exception =>
Amount.log.error(s"Invalid currency code [$currencyCode]")
throw new Exception(s"Invalid currency code [$currencyCode]")
}
}
Amount(value, currency)
}
}
}
You can then use this to parse rows, for example with:
def amounts(): List[Amount] = SQL("select * from amounts").as(amountMapping *)