Abstract model / Polymorphism?

Although I don’t get exactly, I think what you want to do is Table Inheriting.
The most easiest way to go is Single Table Inheritance, where you need posts table which has all the columns VideoPost and ImagePost will have. Imitating from your example:

Migration will look like

create table(posts) do
  add :concrete_table_type, :string # , null: false <- if Post not directly used
  add :common_field1, :string
  add :common_field2, :string
  add :foo, :string  # cannot be null: false
  add :bar, :string  # cannot be null: false
  add :baz, :string  # cannot be null: false

  timestamps()
end

Schemas will be

defmodule Post do
  schema "posts" do
    field :common_field1
    field :common_field2
  end

  # abstract logic here
  def abstract_logic(%Post{} = post), do: ...

  defmodule ConcretePostBehaviour do
    @callback table_type() :: String.t
  end
end
defmodule VideoPost do
  @behaviour Post.ConcreatePostBehvaiour

  schema "posts" do       # Use the same table as above
    field :common_field1
    field :common_field2
    field :foo
  end

  @impl Post.ConcretePostBehaviour
  def table_type, do: "VideoPost"

  def changeset(post, attrs) do
    post
    |> cast([:common_field1, :common_field2, :foo])
    |> put_change(:concrete_table_type, table_type())
      # NOT NULL of foo cannot be defined in STI so define here
    |> validate_required([:foo, :concrete_table_type])
  end
end

With this you can

def list_posts, do: %Post{} |> Repo.all  # Gets all the abstract data
def list_video_posts do
  %VideoPost{}
  |> where(:concrete_table_type, "VideoPost")
  |> Repo.all
end

Because it is not objects and inheritance, you cannot do Post.abstract_logic() to %VideoPost{}, but you can always create a Protocol for the concrete types, or create a macro inside the abstract module and require it.
Well I mean you can do video_post |> Post.abstract_logic() if you don’t do guarding on that function, but it is always safer to define a protocol for that :slight_smile:

3 Likes