1
votes

I have a case class representing my domain:

case class MyModel(rawValue: String, transformedValue: String)

rawValue maps to a value in the database and is properly parsed and bound. What I am trying to do is add transformedValue to my model: this value is just some arbitrary transformation that I perform on the rawValue. It does not map to any data in the database / query.

I have a parser (prior to adding transformedValue) that looks like this:

val parser = {
  get[String]("rawValue") map {
    case rawValue => MyModel(rawValue)
  }
}

Since MyModel is immutible and I can't insert transformedValue into it after its been created, what and where is the best way to do and add this transformation (e.g. adding ad-hoc values to the Model) preferably without using vars?

Coming from Java, I would probably just have added a getTransformedValue getter to the domain class that performs this transformation on the rawValue attribute.

3

3 Answers

2
votes

Since transformedValue seems to be a derived property, and not something that would be supplied in the constructor, you can define it as an attribute in the body of the function, possibly using the lazy qualifier so that it will only be computed on-demand (and just once for instance):

case class MyModel(rawValue: String) {
  lazy val transformedValue: String = {
    // Transformation logic, e.g.
    rawValue.toLowerCase()
  }
}

val is evaluated when defined; def is evaluated when called. A lazy val is evaluated when it is accessed the first time.

It may be appropriate to use lazy in the case of an expensive computation that is rarely needed, or when logic requires it. But for most cases a regular val should be used. Quoting:

lazy val is not free (or even cheap). Use it only if you absolutely need laziness for correctness, not for optimization.

1
votes

I don't see why you wouldn't just do the transformation in the parser itself:

def transformation(rawValue: String): String = ...

val parser = {
    get[String]("rawValue") map {
        case rawValue => MyModel(rawValue, transformation(rawValue))
    }
}

Or if you don't want to do it there for some reason, you can use copy to create a new copy of MyModel with a new value:

val model = MyModel("value", ..)

val modelWithTransform = model.copy(transformedValue = transformation(model.rawValue))

You could also overload apply to automatically apply the transformation within the companion object:

case class MyModel(rawValue: String, transformedValue: String)

object MyModel {

    def apply(value: String): MyModel = MyModel(rawValue, transformation(rawValue))

    val parser = {
        get[String]("rawValue") map {
            case rawValue => MyModel(rawValue)
        }
    }
}

MyModel may be immutable, but you can always create another copy of it with some values changed.

0
votes

Turns out it was easier than I thought and the solution did look like what I said I would have done in java:

I just add a function to MyModel that does this transformation:

case class MyModel(rawValue: String) {
  def transformedValue = {
    // do the transformation here and return it
  }
}