Thanks everyone for the responses!
Context / why I asked
While learning Ecto / Phoenix / LiveView, I ran into this modeling dilemma and was trying to understand the different layers at the same time.
Before the full mental model was really in place, I was jumping between different layers due to the learning process.
Why I didn’t immediately propose my own solution
I also explored the direction linked by LostKobrakai earlier (separate types, separate structures), but what held me back was:
- at the database level, I modeled this using composite-key / multi-field identity approaches
- in Ecto, this is not really supported in a native or idiomatic way
- so it wasn’t clear to me whether this approach is actually considered idiomatic in an Ecto context
Ecto vs data modeling
What I found interesting is that Ecto + LiveView changesets work really well together, especially for dynamic forms.
If I completely drop Ecto, I don’t just lose an ORM, but also a very useful validation + UI integration layer.
At the same time, it became increasingly clear to me that the database model and the Ecto schema are not the same thing.
At the database level, you can have a perfectly valid relational model (e.g. composite-key / subtype-style structure), which Ecto doesn’t necessarily map in a natural way.
Concrete example (simplified)
schema "items" do
field :type, Ecto.Enum, values: [:item_one, :item_two]
has_one :item_one
has_one :item_two
end
And the more specific schemas:
@primary_key {:id, :id, autogenerate: false}
schema "item_one" do
field :type, Ecto.Enum, values: [:item_one], default: :item_one
belongs_to :item, Item
end
@primary_key {:id, :id, autogenerate: false}
schema "item_two" do
field :type, Ecto.Enum, values: [:item_two], default: :item_two
belongs_to :item, Item
end
This is a really interesting approach — I find the idea of abstracting the association into a single “slot” quite compelling, especially as the number of possible underlying tables grows.
In my case the domain already tends toward multiple item types, so avoiding a growing number of empty or optional has_one associations is exactly the kind of problem I was running into.






















