Reputation: 508
I have a class which represents posts in our system. Where a post might represent a question, document, image, etc. There are about 7 different types of objects the Post class can represent. Each of the 7 different types of objects we have has it's own metadata class to store additional object specific information.
Currently my Post class has 7 optional attributes, one of which gets filled depending on the type of object it is. But since the Post class will only ever have one of these 7 attributes filled, is there a way to consolidate these into a single attribute with an arbitrary type? Then I could use a match case statement to generate the correct metadata object at runtime. Or this impossible with Scala given the strongly typed nature of the language.
Code is below:
case class Post (
id : Long,
typ : String,
name : String,
fileInfo : Option[FileInfo],
imageInfo : Option[FileImageInfo],
videoInfo : Option[FileVideoInfo],
audioInfo : Option[FileAudioInfo],
eventInfo: Option[EventInfo],
lectureInfo: Option[LectureInfo],
drawingInfo: Option[DrawingInfo]
)
object Post {
val simple = {
get[Long]("object_view.id") ~
get[String]("object_view.type") ~
get[String]("object_view.name") map {
case id~typ~name =>
Post(
id,
typ,
name,
FileInfo.getById(id),
FileImageInfo.getById(id),
FileVideoInfo.getById(id),
FileAudioInfo.getById(id),
EventInfo.getFirst(id),
LectureInfo.getById(id),
DrawingInfo.getById(id)
)
}
}
Upvotes: 1
Views: 897
Reputation: 81
Well, here in 2022, what I would do is very similar to what Reuben did, but I'd move the metadata into a separate ADT. Like this
sealed trait PostMetadata
object PostMetadata {
final case class File(val info: FileInfo) extends PostMetadata
final case class Image(val info: FileImageInfo) extends PostMetadata
final case class Video(val info: FileVideoInfo) extends PostMetadata
final case class Audio(val info: FileAudioInfo) extends PostMetadata
final case class Event(val info: EventInfo) extends PostMetadata
final case class Lecture(val info: LectureInfo) extends PostMetadata
final case class Drawing(val info: DrawingInfo) extends PostMetadata
}
final case class Post(
val id: Long,
val name: String,
val metadata: PostMetadata
)
object Post {
val simple =
get[Long]("object_view.id") ~
get[String]("object_view.type") ~
get[String]("object_view.name") map {
case id ~ typ ~ name =>
Post(
id,
name,
typ match {
case "file" => PostMetadata.File(FileInfo.getById(id).get)
case "image" => PostMetadata.Image(FileImageInfo.getById(id).get)
case _ => // and so on
}
)
}
}
This way you can match against post.metadata
and actually get a resolved metadata value on info
instead of having to check for an Option
.
If you want to able to export type
back you may want to add a def typ: String
on the trait and implement on each of the case classes, or just adding typ
and matching self against each of the case classes. Also, you could have Map[String, Long => PostMetadata]
to simplify that match down there.
Upvotes: 1
Reputation: 165
Why not make Post abstract, then implement a subclass for each different type of post ? Something like:
abstract class Post { val id:Long; val typ:String; val name:String; }
case class FilePost(
id : Long,
typ : String,
name : String,
fileInfo : Option[FileInfo
);
case class ImagePost(
id : Long,
typ : String,
name : String,
imageInfo : FileImageInfo
);
...
def doSomething( post:Post ):Unit = post match {
case fp:FilePost => ...
}
Doh! - looks like earlier response said the same thing ...
Upvotes: 1
Reputation: 1703
class FileInfo(val name: String)
abstract trait CanGet[T] { val value: Option[T]; def get = value.get }
case class PostFileInfo(val id: Long, val typ: String, val name: String) extends
{ val value = Some(new FileInfo(name)) } with CanGet[FileInfo]
...
(1L, "FileInfo", "FileName") match {
case (id, typ @ "FileInfo", name) => new PostFileInfo(1, typ, name)
}
Upvotes: 0