Polymorphism in Ecto schemas

I have a messaging app where users can send multiple types of messages (text, video, image, etc). I would like to model these with different schemas, but I need to be able to query them and get back one list of all types of messages. What would be the best way to go about modeling this?

My initial thought is to have a table that contains all the columns for every type of message, and then to have multiple schemas that act on the same table, for example TextMessage, VideoMessage, ImageMessage, etc.

It would be really nice if I could then return a list of all messages in the form of:

[
  %TextMessage{},
  %VideoMessage{},
  %ImageMessage{},
  %TextMessage{},
  %TextMessage{}
]

That way, I could easily pattern match to render different views for each type.

3 Likes

bump! having exact same issue

I suppose you could just have a generic %Message{} struct with a type field.

In that case you could pattern match like this

case message do
  %Message{type: "text"} -> ...
  %Message{type: "video"} -> ...
2 Likes

Wouldn’t you still end up with one big “god” schema for all types of messages?

1 Like

Depending on the data stored, a solution would be to simply embed the message-type specific data in JSON

2 Likes

Hi what solution did you finally use?

2 Likes

At this point I’m still using a one large schema that looks a bit like this:

schema "messages" do
  field :type, DB.MessageType, default: :text

  field :text, :string
  field :video_url, :string
  field :image_url, :string

  ...
end

The DB.MessageType is a custom Ecto type that basically translates :text, :video, and :image in the struct to "text", “video”, and “image” in the database, and only lets messages with those values be inserted.

Definitely open to feedback on this.

1 Like

We’ve done this at my company with really bad results. Here’s how I would have done it differently.

In your first phase, design your app with plain ole’ Structs. This is nice because you can focus on the problem without all the DB issues sneaking in. Write it to solve the application problem you have. You can make a Protocol (or behaviour) to handle all the polymorphism you want.

Once your app is going, now think about how you want to persist those structs. Put in a layer which takes those structs in and read/writes them to a database structure that is hidden from the caller. As much as possible, don’t let Ecto concepts leak out of that layer.

In other words, use Ecto for saving and loading data, but don’t pass Structs generated by Ecto.Schema through the core of your application. Make your own Structs in your application core and pass those around.

6 Likes

Wow, thanks for the advice. I’m running into this problem right now myself – focusing too much on how I should represent my data in ecto when I should really be focusing on how to represent it in the application itself.

@pdilyard: I recommend to write Ecto extension for it (for all validation things). You can find all my ideas for Ecto extensions here:

I have to wonder whether this is more of a process issue. Structs are great logical representations of the various shapes within your data but they lack the convenience of ChangeSet that many developers seem to crave. Maybe what is really needed is that ChangeSet and it’s schema facility are separated from Ecto and a workflow that emphasizes application schema first (rather than DB schema) is established. I suspect that consequently application schema to DB schema generation/mapping may become a little more complex.

1 Like

I know this is an old thread but Im wondering if someone found a good solution for it.

1 Like