1

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)
          )
       }
    }
1
  • Using a String to represent type seems like a bad idea. Why not have several types implement a common Post interface, like so? trait Post { def id: Long def name: String } case class FilePost(id:Long, name:String, info: FileInfo) extends Post { } Commented Dec 18, 2012 at 1:39

3 Answers 3

1

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 ...

Sign up to request clarification or add additional context in comments.

Comments

1

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.

Comments

0
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)
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.