I have a Scala case class with the following declaration:
case class Student(name: String, firstCourse: String, secondCourse: String, thirdCourse: String, fourthCourse: String, fifthCourse: String, sixthCourse: String, seventhCourse: String, eighthCourse: String)
Before I create a new Student object, I have a variable holding the value for name and an array holding the values for all 8 courses. Is there a way to pass this array to the Student constructor? I want it to look cleaner than:
val firstStudent = Student(name, courses(0), courses(1), courses(2), courses(3), courses(4), courses(5), courses(6), courses(7))
You can always write your own factory methods on the Student companion object:
case class Student(
name: String, firstCourse: String, secondCourse: String,
thirdCourse: String, fourthCourse: String,
fifthCourse: String, sixthCourse: String,
seventhCourse: String, eighthCourse: String
)
object Student {
def apply(name: String, cs: Array[String]): Student = {
Student(name, cs(0), cs(1), cs(2), cs(3), cs(4), cs(5), cs(6), cs(7))
}
}
and then just call it like this:
val courses: Array[String] = ...
val student = Student("Bob Foobar", courses)
Why you need a case class with 8 similar fields is another question. Something with automatic mappings to a database of some kind?
Related
I have the following model:
case class ProcessStepTemplatesModel(
id: Option[Int],
title: String,
createdat: String,
updatedat: String,
deadline: Option[Date],
comment: Option[String],
stepType: Int,
deleted: Boolean,
processtemplate: Option[Int])
object ProcessStepTemplatesModel {
implicit val processStepFormat = Json.format[ProcessStepTemplatesModel]
}
I have an extra value derived. All data is sent via POST as JSON to my controller. When I validate the request with the model above this value is lost.
I need this value for the model to work with, but it shouldn't be persisted.
But if I add the value to the model I get an error from Scala slick.
Update:
From the top of my head, two options for you:
1. Decouple your frontend representation from your actual model
You could have a ProcessStepTemplatesClientModel, which has the extra field derived and is only used to validate the JSON input in the controller. After you are done with your business logic involving the derived field you convert the object to ProcessStepTemplatesModel and persist it in your database.
2. Handle the field in your *-projection of your Slick table
Include the derived field in your ProcessStepTemplatesModel class (assuming it is boolean, works with any other primitive):
case class ProcessStepTemplatesModel(
id: Option[Int],
title: String,
createdat: String,
updatedat: String,
deadline: Option[Date],
comment: Option[String],
stepType: Int,
deleted: Boolean,
processtemplate: Option[Int],
derived: Boolean)
And since you are using Slick as your database mapper you probably have a table representation for your ProcessStepTemplatesModel:
class ProcessStepTemplatesModelTable(tag: Tag) extends Table[ProcessTableTemplatesModel](tag, "PROCESS_TABLE_TEMPLATES_MODEL") {
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
...
def processtemplate = column[Option[Int]]("PROCESSTEMPLATE")
def * = (id, ..., processtemplate) <> ( {
tuple: (Int, ..., Option[Int]) => ProcessStepTemplatesModel(tuple._1, ..., tuple._9, derived = false)
}, {
ps: ProcessStepTemplatesModel => Some((ps.id, ..., ps.processtemplate))
})
}
Don't include the derived field in the table definition and handle that case within the *-projection by handing a static value to the case class constructor to create the object from the tuple and just leave it out when creating the tuple from the object.
EDIT
As a response to your comment, a more concrete implementation of the *-projection, based on the ProcessStepTemplatesModel including derived:
def * : ProvenShape[ProcessStepTemplatesModel] = (id.?, title, createdat, updatedat, deadline, comment, stepType, deleted, processtemplate) <> ( {
tuple: (Option[Int], String, String, String, Option[Data], Option[String], Int, Boolean, Option[Int]) => ProcessStepTemplatesModel(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, derived = false)
}, {
ps: ProcessStepTemplatesModel => Some((ps.id, ps.title, ps.createdat, ps.updatedat, ps.deadline, ps.comment, ps.stepType, ps.deleted, ps.processtemplate))
})
Suppose I have a Scala class with update method:
case class User (id: Integer, name: String, surname: String, address: String) {
def update (name: String, surname: String, address: String): Unit = {
copy (name, surname, address)
}
}
Is it possible with Scala to call update function with one or two parameters and delegate call to copy function? Like this:
val user: User = User (1, "name", "surname", "address")
user.update(name: "name") // Here it delegate to call copy (name)
Or do I have to copy and paste every update function for each case?
Consider
case class User (id: Integer, name: String, surname: String, address: String)
where member types follow the member names.
Then call copy directly like this,
val userA = User(1,"a","b","c")
userA: User(1,a,b,c)
val userA2 = a.copy(2)
userA2: User(2,a,b,c)
val userBB = a.copy(name = "bb")
userBB: User(1,bb,b,c)
In the book "Scala for the impatient" by Cay Horstmann, on chapter 5 exercise 8, there is this question:
//Make a class Car with read-only properties for manufacturer, model name,
//and model year, and a read-write property for the license plate. Supply four
// constructors. All require the manufacturer and model name. Optionally,
//model year and license plate can also be specified in the constructor. If not,
//the model year is set to -1 and the license plate to the empty string. Which
//constructor are you choosing as the primary constructor? Why?
So I coded the class:
case class Car(val manufacturer: String, val modelName: String,
val modelYear: Int, var licensePlate : String = "") {
def this(manufacturer: String, modelName: String, licensePlate: String) {
this(manufacturer, modelName, -1, licensePlate)
}
def this(manufacturer: String, modelName: String, modelYear: Int) {
specifically on this part:
this(manufacturer, modelName, modelYear)
}
def this(manufacturer: String, modelName: String) {
this(manufacturer, modelName, -1)
}
}
The compiler complains of the error:
<console>:14: error: called constructor's definition must precede calling constructor's definition
this(manufacturer, modelName, modelYear)
^
Why does it complain when I have provided a default value for the license Plate as an empty String in the primary constructor, and it sure is defined first??
The error goes away if I do this:
this(manufacturer, modelName, modelYear, "")
or if I make the class primary constructor not have a default value for the licensePlate (of course adjusting other auxilliary constructor calls in the process).
Note that the question specifically asked for 4 constructors, so instance creation can be called like:
new Car("Honda", "City", 2010, "ABC-123")
new Car("Honda", "City")
new Car("Honda", "City", "ABC-123")
new Car("Honda", "City", 2010)
Thanks in advance to those who can shed light on this issue.
====
Thanks to answers by Ende Neu and Kigyo, this looks like the perfect solution (remove the recursive constructor):
case class Car(val manufacturer: String, val modelName: String,
val modelYear: Int = -1, var licensePlate : String = "") {
def this(manufacturer: String, modelName: String, licensePlate: String) {
this(manufacturer, modelName, -1, licensePlate)
}
<< removed constructors here >>
}
Console println new Car("Honda", "City", 2010, "ABC-123")
Console println new Car("Honda", "City")
Console println new Car("Honda", "City", "ABC-123")
Console println new Car("Honda", "City", 2010)
I just want to make some things clear.
You can leave out the last parameter in this example. Here we call the primary constructor, that has 3 parameters, with only supplying two. (related to Ende Neu's example)
case class Test(a: String, b: String, c: String = "test") {
def this() = this("", "")
}
So why does it not work in your scenario? Have a close look!
def this(manufacturer: String, modelName: String, modelYear: Int) {
this(manufacturer, modelName, modelYear)
}
Here you also leave out the last parameter, but the new constructor you define takes exactly that amount of parameters and this makes it a recursive call. So you never call the primary constructor and therefore you get the error.
I would also remove the default value in your case. Maybe there is a way around it, but I'm too tired at the moment.
You get this error because the scala compiler is not able to see the default value you provided in your case class and you are declaring a constructor with three parameters instead of four, for example:
case class Test(a: String, b: String, c: String = "test") {
def this(d: String, e: String) = this(c, e)
}
This will throw the same error, what you can do is to specify default values in the constructor as you did with the empty string:
def this(d: String) = this(d, "empty")
def this(d: String, e: String) = this(d, e, "empty") // and this would return a new object
Having a case class with default value simply corresponds to a constructor where you don't have to serve all parameters, so for the exercise you don't need two constructors, if the year and the plate are not specified use some predifined value, else use the default constructor (which takes already 4 parameters):
case class Car(val manufacturer: String, val modelName: String, val modelYear: Int, var licensePlate : String) {
def this(manufacturer: String, modelName: String) =
this(manufacturer, modelName, -1, "")
}
}
And then call it like this:
new Car("Wolksvagen", "Polo")
Car("Ford", "Fiesta", 1984, "plate")
Note that you have to use the new keyword in the first declaration because it's not referring to the apply method in the companion object (which could also be overridden, see this SO question).
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)
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)
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
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)
}
}
Suppose, I have my domain object named "Office":
case class Office(
id: Long,
name: String,
phone: String,
address: String
) {
def this(name: String, phone: String, address: String) = this(
null.asInstanceOf[Long], name, phone, address
)
}
When I create new Office:
new Office("officeName","00000000000", "officeAddress")
I don't specify id field becouse I don't know it. When I save office (by Anorm) I now id and do that:
office.id = officeId
So. I know that using null is non-Scala way. How to avoid using null in my case?
UPDATE #1
Using Option.
Suppose, something like this:
case class Office(
id: Option[Long],
name: String,
phone: String,
address: String
) {
def this(name: String, phone: String, address: String) = this(
None, name, phone, address
)
}
And, after saving:
office.id = Option(officeId)
But what if I need to find something by office id?
SomeService.findSomethingByOfficeId(office.id.get)
Does it clear? office.id.get looks not so good)
UPDATE #2
Everyone thanks! I've got new ideas from your answers! Greate thanks!
Why not declare the id field as a Option? You should really avoid using null in Scala. Option is preferable since it is type-safe and plays nice with other constructs in the functional paradigm.
Something like (I haven't tested this code):
case class Office(
id: Option[Long],
name: String,
phone: String,
address: String
) {
def this(name: String, phone: String, address: String) = this(
None, name, phone, address
)
}
Just make the id field an Option[Long]; once you have that, you can use it like this:
office.id.map(SomeService.findSomethingByOfficeId)
This will do what you want and return Option[Something]. If office.id is None, map() won't even invoke the finder method and will immediately return None, which is what you want typically.
And if findSomethingByOfficeId returns Option[Something] (which it should) instead of just Something or null/exception, use:
office.id.flatMap(SomeService.findSomethingByOfficeId)
This way, if office.id is None, it will, again, immediately return None; however, if it's Some(123), it will pass that 123 into findSomethingByOfficeId; now if the finder returns a Some(something) it will return Some(something), if however the finder returns None, it will again return None.
if findSomethingByOfficeId can return null and you can't change its source code, wrap any calls to it with Option(...)—that will convert nulls to None and wrap any other values in Some(...); if it can throw an exception when it can't find the something, wrap calls to it with Try(...).toOption to get the same effect (although this will also convert any unrelated exceptions to None, which is probably undesirable, but which you can fix with recoverWith).
The general guideline is always avoid null and exceptions in Scala code (as you stated); always prefer Option[T] with either map or flatMap chaining, or using the monadic for syntactic sugar hiding the use of map and flatMap.
Runnable example:
object OptionDemo extends App {
case class Something(name: String)
case class Office(id: Option[Long])
def findSomethingByOfficeId(officeId: Long) = {
if (officeId == 123) Some(Something("London")) else None
}
val office1 = Office(id = None)
val office2 = Office(id = Some(456))
val office3 = Office(id = Some(123))
println(office1.id.flatMap(findSomethingByOfficeId))
println(office2.id.flatMap(findSomethingByOfficeId))
println(office3.id.flatMap(findSomethingByOfficeId))
}
Output:
None
None
Some(Something(London))
For a great introduction to Scala's rather useful Option[T] type, see http://danielwestheide.com/blog/2012/12/19/the-neophytes-guide-to-scala-part-5-the-option-type.html.
When using id: Option[Long] , extract the option value for instance with
if (office.id.isDefined) {
val Some(id) = office.id
SomeService.findSomethingByOfficeId(id)
}
or perhaps for instance
office.id match {
case None => Array()
case Some(id) => SomeService.findSomethingByOfficeId(id)
}
Also you can define case classes and objects as follows,
trait OId
case object NoId extends OId
case class Id(value: Long) extends OId
case class Office (
id: OId = NoId,
name: String,
phone: String,
address: String
)
Note that by defaulting id for example to NoId , there is no need to declare a call to this. Then
val office = Office (Id(123), "name","phone","addr")
val officeNoId = Office (name = "name",phone="phone",address="addr")
If the id member is defined last, then there is no need to name the member names,
val office = Office ("name","phone","addr")
office: Office = Office(name,phone,addr,NoId)
As of invoking (neatly) a method,
office.id match {
case NoId => Array()
case Id(value) => SomeService.findSomethingByOfficeId(value)
}
I prefer more strong restriction for object Id property:
trait Id[+T] {
class ObjectHasNoIdExcepthion extends Throwable
def id : T = throw new ObjectHasNoIdExcepthion
}
case class Office(
name: String,
phone: String,
address: String
) extends Id[Long]
object Office {
def apply(_id : Long, name: String, phone: String, address: String) =
new Office(name, phone, address) {
override def id : Long = _id
}
}
And if I try to get Id for object what is not stored in DB, I get exception and this mean that something wrong in program behavior.
val officeWithoutId =
Office("officeName","00000000000", "officeAddress")
officeWithoutId.id // Throw exception
// for-comprehension and Try monad can be used
// update office:
for {
id <- Try { officeWithoutId.id }
newOffice = officeWithoutId.copy(name = "newOffice")
} yield OfficeDAL.update(id, newOffice)
// find office by id:
for {
id <- Try { officeWithoutId.id }
} yield OfficeDAL.findById(id)
val officeWithId =
Office(1000L, "officeName","00000000000", "officeAddress")
officeWithId.id // Ok
Pros:
1) method apply with id parameter can be incapsulated in DAL logic
private[dal] def apply (_id : Long, name: String, ...
2) copy method always create new object with empty id (safty if you change data)
3) update method is safety (object not be overridden by default, id always need to be specified)
Cons:
1) Special serealization/deserealization logic needed for store id property (json for webservices, etc)
P.S.
this approach is good if you have immutable object (ADT) and store it to DB with id + object version instead object replace.