In my application I want to have a single entities
table, which stores type a
and type b
entities. type a
and type b
share the exact same fields, I just set a type
field can be "type_a"
or "type_b"
.
For some reasons I don’t want to work with %Entity{}
structs, but %TypeA{}
and %TypeB{}
structs, and I’m now wondering what might be the best way to do this.
In the end I’m looking for a way to create “aliases” for my %Entity{}
struct. Here’s what I’ve tried so far:
1. Just define the schema multiple times
defmodule App.Entities.Entity do
# ...
schema "entities" do
field :title, :string
field :type, :string
has_one :address, Address
end
@doc false
def changeset(%Entity{} = entity, attrs) do
entity
|> cast(attrs, [:title, :type])
|> validate_required([:title, :type])
end
end
defmodule App.Entities.TypeA/B do
# ...
schema "entities" do
field :title, :string
field :type, :string
has_one :address, Address, foreign_key: :entity_id
end
@doc false
def changeset(%TypeA/B{} = entity, attrs) do
entity
|> cast(attrs, [:title, :type])
|> validate_required([:title, :type])
|> put_change(:type, "type_a/b")
end
end
This works fine, but I don’t like the repetitiveness of this approach.
2. Use a separate schema module
defmodule App.Entities.EntitySchema do
defmacro __using__(_) do
quote do
# ...
schema "entities" do
field :title, :string
field :type, :string
has_one :address, Address, foreign_key: :entity_id
end
end
end
end
defmodule App.Entities.Entity do
use App.Entities.EntitySchema
@doc false
def changeset(%Entity{} = entity, attrs) do
entity
|> cast(attrs, [:title, :type])
|> validate_required([:title, :type])
end
end
defmodule App.Entities.TypeA/B do
use App.Entities.EntitySchema
@doc false
def changeset(%TypeA/B{} = entity, attrs) do
entity
|> Map.put(:__struct__, Entity)
|> Entity.changeset(attrs)
|> put_change(:type, "type_a/b")
end
end
I prefer this approach, but it still seems to be a bit overkill for a “simple alias”.
3. Define structs
I have not tried this so far but it might be possible to use regular Elixir structs:
defmodule App.Entities.TypeA/B do
defstruct [:id, :title, :type, :address, :created_at, :updated_at]
end
defmodule App.Entities.Entity do
# ...
schema "entities" do
field :title, :string
field :type, :string
has_one :address, Address
end
@doc false
def changeset(%TypeA/B{} = entity, attrs) do
entity
|> Map.put(:__struct__, Entity)
|> changeset(attrs)
|> put_change(:type, "type_a/b")
end
def changeset(%Entity{} = entity, attrs) do
entity
|> cast(attrs, [:title, :type])
|> validate_required([:title, :type])
end
end
This seems to be a clean approach as my %TypeA/B{}
structs are pure data containers,
but I’m not sure if I would lose some Ecto functionality, say preloads etc.?
Alternatives, Feedback?
How would you solve this problem and why? Maybe there’s a better approach that I haven’t thought of yet