Converting one case class to another that is similar with additional parameter in Scala - scala

So, the problem is in the title, but here are the details.
I have two case classes:
case class JourneyGroup(id: Option[Int] = None,
key: UUID,
name: String,
data: Option[JsValue],
accountId: Int,
createdAt: DateTime = DateTime.now,
createdById: Int,
updatedAt: Option[DateTime] = None,
updatedById: Option[Int] = None,
deletedAt: Option[DateTime] = None,
deletedById: Option[Int] = None)
and
case class JourneyGroupApi(id: Option[Int] = None,
key: UUID,
name: String,
data: Option[JsValue],
accountId: Int,
createdAt: DateTime = DateTime.now,
createdById: Int,
updatedAt: Option[DateTime] = None,
updatedById: Option[Int] = None,
deletedAt: Option[DateTime] = None,
deletedById: Option[Int] = None,
parties: Seq[Party] = Seq.empty[Party])
Background: the reason for having these two separate classes is the fact that slick does not support collections, and I do need collections of related objects that I build manually. Bottom line, I could not make it work with a single class.
What I need is an easy way to convert from one to another.
At this point, to unblock myself, I created a manual conversion:
def toJourneyGroupApi(parties: Seq[Party]): JourneyGroupApi = JourneyGroupApi(
id = id,
key = key,
name = name,
data = data,
accountId = accountId,
createdAt = createdAt,
createdById = createdById,
updatedAt = updatedAt,
updatedById = updatedById,
deletedAt = deletedAt,
deletedById = deletedById,
parties = parties
)
Which is working, but extremely ugly and requires a lot of maintenance.
One thing that I tried doing is:
convert the source object to tuple
Add an element to that tuple using shapeless
and build a target object from resulting tuple
import shapeless._
import syntax.std.tuple._
val groupApi = (JourneyGroup.unapply(group).get :+ Seq.empty[Party])(JourneyGroupApi.tupled)
But, this thing is claiming, that the result of :+ is not tuple, even though in console:
Party.unapply(p).get :+ Seq.empty[Participant]
res0: (Option[Int], model.Parties.Type.Value, Int, Int, org.joda.time.DateTime, Int, Option[org.joda.time.DateTime], Option[Int], Option[org.joda.time.DateTime], Option[Int], Seq[model.Participant]) = (None,client,123,234,2016-11-12T03:55:24.006-08:00,987,None,None,None,None,List())
What am I doing wrong? Maybe there is another way of achieving this.

Could you consider Composition?
case class JourneyGroup(
...
)
case class JourneyGroupApi(
journeyGroup: JourneyGroup=JourneyGroup(),
parties: Seq[Party] = Seq()
)
Converting a journeyGroup would just be something like JourneyGroupApi(journeyGroup,parties) and "converting" a journeyGroupApi would be a matter of accessing journeyGroupApi.journeyGroup. You could perhaps come up with names that worked better for this case. Not sure if this approach would fit the rest of your code. In particular referencing journeyGroup attributes in a journeyGroupApi will be one extra level, e.g. journeyGroupApi.journeyGroup.accountId. (This could potentially be mitigated by "shortcut" definitions on journeyGroupApi like lazy val accountId = journeyGroup.accountId.)
Inheritance might also be an approach to consider with a base case class of JourneyGroup then a normal class (not case class) that extends it with parties as the extra attribute. This option is discussed further in this SO thread.

Related

Scala Option and Some mismatch

I want to parse province to case class, it throws mismatch
scala.MatchError: Some(USA) (of class scala.Some)
val result = EntityUtils.toString(entity,"UTF-8")
val address = JsonParser.parse(result).extract[Address]
val value.province = Option(address.province)
val value.city = Option(address.city)
case class Access(
device: String,
deviceType: String,
os: String,
event: String,
net: String,
channel: String,
uid: String,
nu: Int,
ip: String,
time: Long,
version: String,
province: Option[String],
city: Option[String],
product: Option[Product]
)
This:
val value.province = Option(address.province)
val value.city = Option(address.city)
doesn't do what you think it does. It tries to treat value.province and value.city as extractors (which don't match the type, thus scala.MatchError exception). It doesn't mutate value as I believe you intended (because value apparently doesn't have such setters).
Since value is (apparently) Access case class, it is immutable and you can only obtain an updated copy:
val value2 = value.copy(
province = Option(address.province),
city = Option(address.city)
)
Assuming the starting point:
val province: Option[String] = ???
You can get the string with simple pattern matching:
province match {
case Some(stringValue) => JsonParser.parse(stringValue).extract[Province] //use parser to go from string to a case class
case None => .. //maybe provide a default value, depends on your context
}
Note: Without knowing what extract[T] returns it's hard to recommend a follow-up

Fetch all entities from case class and convert them to string

I have a case class like below:
case class Class1(field1: String,
field2: Option[String] = None,
var var1: Option[String] = None,
var var2: Option[Boolean] = None,
var var3: Option[Double] = None
)
The list of variables is a bit longer. Now I want to convert all variables, which are inside the class, into a string. Say Option[] must be omitted and also Boolean, Double and Number must be converted to string type. My first approach was:
def anyOptionalToString(class1Dataset: Dataset[Class1]): DataFrame = {
val ds1 = class1Dataset.map { class1 =>
(
class1.field1,
class1.field2.getOrElse(""),
class1.var1.getOrElse(""),
class1.var2.getOrElse(false),
class1.var3.getOrElse(-1.0)
)
}
Is there a way to cast them without calling every field?
Speak in a kind of loop or something similar?
What I would do, is creating a new Seq containing the defaults you want to have. Let's say:
val defaults = Seq("", "", "", false, -1)
Now, we can use the productIterator to iterate over the existing elements, and choose whether we want to use the existing value, or the default:
val c1 = Class1("f1", Some("f2"), None, Some(true), Some(3))
c1.productIterator.zip(defaults.iterator).map {
case (None, default) => default
case (Some(value), _) => value
case (value, _) => value
}.map(_.toString)
The resulting type of the code above is Iterator[String]. Code run can be found at Scastie.

Difference between Option[String] = None and Option[String] in scala

I have started learning Scala and going through the code (case classes and other stuff) in my project.
I see a case class defined like this:
case class Test(firstName: Option[String] = None, lastName: Option[String])
I have few questions on the above case class:
What is the difference between Option[String] = None and Option[String]?
What is the correct usage, when should we use Option[String] = None and Option[String]?
The "regex" for a parameter is something like this: name: type (= defaultValue) - Note that the default value part is optional.
Thus firstName: Option[String] = None means the parameter is named firstName and it is of type Option[String] and its default value is a None (also remember the Option type is used to explain that a value may exist or not. And that None means it doesn't exist).
Both are correct depending on the context, in this case, this class can be used like:
Test(lastName = None)
// res: Test = Test(None,None) - A person without names.
Test(lastName = Some("Mejia"))
// res: Test = Test(None,Some(Mejia)) - A person with just its last name, which it is "Mejia".
Test(firstName = Some("Luis"), lastName = Some("Mejia"))
// res: Test = Test(Some(Luis),Some(Mejia)) - A person whose first name is "Luis" and its last name is "Mejia".
Test(firstName = Some("Luis"), lastName = None)
// res: Test = Test(Some(Luis),None) - A person with just its first name, which it is "Luis".
Note that I always have to specify its last name because it doesn't have a default value.

scala assign value to object

I have the following code:
def getContentComponents: Action[AnyContent] = Action.async {
contentComponentDTO.list().map(contentComponentsFuture =>
contentComponentsFuture.foreach(contentComponentFuture =>
contentComponentFuture.typeOf match {
case 5 =>
contentComponentDTO.getContentComponentText(contentComponentFuture.id.get).map(
text => contentComponentFuture.text = text.text
)
}
)
Ok(Json.toJson(contentComponentsFuture))
)
and get this error message while assigning a value:
Is there a way to solve this issue?
I thought about creating a copy but that would mean that I have do lots of other stuff later. It would be much easier for me if I could reassign the value.
thanks
Unfortunately this is not possible as contentComponentFuture.text is an immutable property so you cannot change its value like that.
The only way to "change" the values of the job object is to actually create a totally new instance and discard the old one. This is standard practice when dealing with immutable objects.
Sorry for the bad news.
Just found the solution ...
In model I have to define the Attributes as var
case class ContentComponentModel(
id: Option[Int] = None,
typeOf: Int,
name: String,
version: String,
reusable: Option[Boolean],
createdat: Date,
updatedat: Date,
deleted: Boolean,
processStepId: Int,
var text: Option[String],

Scala case class equality when reading from Database

I'm having trouble with checking for equality on a case class that I've mapped to an apache tinkerpop graph, but I want to be able to check for equality after I query the graph.
#label("apartment")
case class Apartment(#id id: Option[Int], address: String, zip: Zip, rooms: Rooms, size: Size, price: Price, link: String, active: Boolean = true, created: Date = new Date()) {}
val ApartmentAddress = Key[String]("address")
Await.result(result, Duration.Inf).foreach(apt => {
val dbResult = graph.V.hasLabel[Apartment].has(ApartmentAddress, apt.address).head().toCC[Apartment]
println(dbResult == apt) //always false :(
})
My problem is that when I've created the object it has no id, and the time stamp on it is obviously different. I read that if you add a second parameter list, it is excluded from equals, so I changed it:
#label("apartment")
case class Apartment(address: String, zip: Zip, rooms: Rooms, size: Size, price: Price, link: String, active: Boolean = true)(#id implicit val id: Option[Int] = None, implicit val created: Date = new Date()) {}
val ApartmentAddress = Key[String]("address")
Await.result(result, Duration.Inf).foreach(apt => {
val dbResult = graph.V.hasLabel[Apartment].has(ApartmentAddress, apt.address).head().toCC[Apartment]
println(dbResult == apt) //true! yay! :D
})
I can now check for equality using ==, but the value from the database loses its id, and the "created" value gets reset. And, one other frustrating thing, they always need to be created with extra parenthesis at the end:
Apartment(address, zip, rooms, size, price, link)()
Is there a way to achieve this functionality without overloading equals? Or make the value from the database maintain the original values using this approach?
It seems in your case, you just need it only for one time comparison, so I would not play with equals and just modified value on comparison
case class Apartment(
#id id: Option[Int] = None,
address: String,
zip: Zip,
rooms: Rooms,
size: Size,
price: Price,
link: String,
active: Boolean = true,
created: Date = new Date(0)) {
}
println(dbResult.copy(id = None, created = new Date(0)) == apt) //true! yay! :D
or add extra function to the class
case class Apartment(
#id id: Option[Int] = None,
address: String,
zip: Zip,
rooms: Rooms,
size: Size,
price: Price,
link: String,
active: Boolean = true,
created: Date = new Date(0)) {
def equalsIgnoreIdAndCreated(other: Apartment) = {
this.equals(other.copy(id = id, created = created))
}
}
println(dbResult.equalsIgnoreIdAndCreated(apt))
You can look at good explanation for case classes in
http://www.alessandrolacava.com/blog/scala-case-classes-in-depth/ and reasons why you should not override equals from automatically generated, otherwise just override equals.