zimt28
Ecto: Shared schema definition/ model alias
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 ![]()
First Post!
zimt28
I’m still not sure how I want to do it, but no. 3 – defining structs – is also possible:
defmodule App.Entities.TypeA/B do
@entity App.Entities.Entity.__struct__()
defstruct @entity
|> Map.keys()
|> Enum.reduce([], fn key, acc ->
val = Map.get(@entity, key)
Keyword.put_new(acc, key, val)
end)
def __schema__(key) do
App.Entities.Entity.__schema__(key)
end
def __schema__(key, arg) do
App.Entities.Entity.__schema__(key, arg)
end
def __changeset__ do
App.Entities.Entity.__changeset__()
end
end
I’ve moved this into a separate module so I can just do
defmodule App.Entities.TypeA/B do
use App.Alias, for: App.Entities.Entity
end
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance









