3

I have an existing Scala application and it uses case classes which are then persisted in MongoDB. I need to introduce a new field to a case class but the value of it is derived from existing field.

For example, there is phone number and I want to add normalised phone number while keeping the original phone number. I'll update the existing records in MongoDB but I would need to add this normalisation feature to existing save and update code.

So, is there any nice shortcut in Scala to add a "hook" to a certain field of a case class? For example, in Java one could modify setter of the phone number.

Edit:

The solution in Christian's answer works fine alone but in my case I have defaults for fields (I think because of Salat...)

case class Person(name: String = "a", phone: Option[String] = None, normalizedPhone: Option[String] = None)

object Person {
  def apply(name: String, phone: Option[String]): Person = Person(name, phone, Some("xxx" + phone.getOrElse("")))
}

And if use something like: Person(phone = Some("s"))

I'll get: Person = Person(a,Some(s),None)

Petteri H
  • 11,779
  • 12
  • 64
  • 94

3 Answers3

9

You can define an apply method in the companion object:

case class Person(name: String, phone: String, normalizedPhone: String)

object Person {
  def apply(name: String, phone: String): Person =  Person(name, phone, "xxx" + phone)
}

Then, in the repl:

scala> Person("name", "phone")
res3: Person = Person(name,phone,xxxphone)
Christian
  • 4,543
  • 1
  • 22
  • 31
  • This answers my original question. I ended up going totally different solution by normalising the field in place and not to introduce new one at all. – Petteri H Jun 17 '14 at 08:51
3

You could add methods to the case class that would contain the transforming logic from existing fields. For example:

case class Person(name: String, phone: String) {
  def normalizedPhone = "+40" + phone
}

Then you can use the method just as if it was a field:

val p1 = new Person("Joe", "7234")
println(p1.normalizedPhone) // +407234
dzs
  • 729
  • 6
  • 11
0

I think this comes close to what you need. Since you can't override the generated mutator, prefix the existing field with an underscore, make it private, and then write the accessor and mutator methods for the original field name. After that, you only need an extra line in the constructor to accommodate for constructor-based initialization of the field.

case class Test(private var _phoneNumber: String, var formatted: String = "") {
  phoneNumber_=(_phoneNumber)

  def phoneNumber = _phoneNumber

  def phoneNumber_=(phoneNumber: String) {
    _phoneNumber = phoneNumber
    formatted = "formatted" + phoneNumber
  }
}

object Test {
  def main(args: Array[String]) {
    var t = Test("09048751234")

    println(t.phoneNumber)
    println(t.formatted)

    t.phoneNumber = "08068745963"

    println(t.phoneNumber)
    println(t.formatted)
  }
}
Robby Cornelissen
  • 91,784
  • 22
  • 134
  • 156