10

I'm fairly new to Scala and I have a question about the best way to copy a case class while preserving data that comes from traits. For example, let's say I have the following:

trait Auditing {

  var createTime: Timestamp = new Timestamp(System.currentTimeMillis)
}

case class User(val userName: String, val email: String) extends Auditing

val user = User("Joe", "joe@blah.com")

Then I want to make a new copy with one parameter changed:

val user2 = user.copy(email = "joe@newemail.com")

Now, in the example above, the property createTime does not get copied over because it is not defined in the constructor of the User case class. So my question is: assuming that moving createTime into the constructor is not an option, what is the best way for getting a copy of the User object that includes the value from the trait?

I'm using Scala 2.9.1

Thanks in advance! Joe

Joe
  • 1,723
  • 2
  • 14
  • 16
  • There's not much options ahead of you: either you manually implement such a method that will produce the copy you want in the declaration of the `User` case class or you use Scala 2.10 macros feature to automate that. The second option definitely won't be an easy task for a beginner. – Nikita Volkov Nov 02 '12 at 02:24
  • 1
    @NikitaVolkov If you were willing to provide me/us with an example of how you would do it with macros, that would be more than great. – Malte Schwerhoff Nov 02 '12 at 08:51
  • 2
    @mhs I join the club. I'm a newbee in macros, that's why I didn't post it as answer. But [here's how](http://stackoverflow.com/a/10397595/485115) I solved a very similar task with `Toolbox` api. The accepted answer there is based on macros, but I think it doesn't support the latest Scala version. – Nikita Volkov Nov 02 '12 at 13:55
  • 1
    @Joe You can post it as another question specifically about macros. I know for sure that there are some experts on macros monitoring this site. I'll gladly upvote that kind of a question. – Nikita Volkov Nov 02 '12 at 17:22

3 Answers3

6

You can override the copy method with that behavior.

case class User(val userName: String, val email: String) extends Auditing
{
  def copy(userName = this.userName, email = this.email) {
   val copiedUser = User(userName, email)
   copiedUser.createTime = createTime
   copiedUser      
  }
}
Reuben Doetsch
  • 616
  • 4
  • 10
3

While I see no other solution than Reuben's, I don't understand the requirement to leave the constructor args untouched. This would be the most natural solution:

case class User(userName: String, email: String, 
   override val createTime:Timestamp = new Timestamp(System.currentTimeMillis)) 
      extends Auditing

If you don't want the user to be able to overwrite createTime, you can still use:

case class User private (userName: String, email: String, 
   override val createTime:Timestamp) extends Auditing {
   def this(userName: String, email: String) =
     this(userName, email, new Timestamp(System.currentTimeMillis))
}

The only drawback is that you need to write new User("Joe", "joe@blah.com"), as the primary constructor is now private.

Landei
  • 54,104
  • 13
  • 100
  • 195
0

You might be better of not using a case class. You can easily implement the functionality you need yourself. The below code implements the copy method you wanted, a constructor without new, hides the original constructor, and creates an extractor so that you can use User in case statements.

class User private(val userName: String,
                   val email: String,
                   val timeStamp: Timestamp =
                   new Timestamp(System.currentTimeMillis)) {

    def copy(uName: String = userName,
             eMail: String = email) =
    new User(uName, eMail, timeStamp)
}

object User {
  def apply(userName: String, email: String) =
    new User(userName, email)

  def unapply(u: User) = Some((u.userName, u.email, u.timeStamp))
}