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