The recommended way to handle polymorphic associations in Phoenix seems to be adding an intermediate schema that contains the references to the other schemas:
So if I would like to create schemas with different kinds of animals, I would do:
defmodule Animal do
use Ecto.Model
schema "animals" do
belongs_to(:dog, Dog)
belongs_to(:cat, Cat)
belongs_to(:owner, PetOwner)
end
end
defmodule Dog do
use Ecto.Model
schema "dogs" do
end
end
defmodule Cat do
use Ecto.Model
schema "cats" do
end
end
defmodule PetOwner do
use Ecto.Model
schema "pet_owners" do
has_one(:pet, Animal)
end
end
But I could also have the PetOwner
schemas containing a binary field and the type:
defmodule Dog do
use Ecto.Model
schema "dogs" do
end
end
defmodule Cat do
use Ecto.Model
schema "cats" do
end
end
defmodule PetOwner do
use Ecto.Model
schema "pet_owners" do
field(:pet, :binary)
field(:pet_type, :integer)
end
end
Or even just having a nullable reference to all the animals in the owner schema:
defmodule Dog do
use Ecto.Model
schema "dogs" do
belongs_to(:owner, PetOwner)
end
end
defmodule Cat do
use Ecto.Model
schema "cats" do
belongs_to(:owner, PetOwner)
end
end
defmodule PetOwner do
use Ecto.Model
schema "pet_owners" do
has_one(:cat, Cat)
has_one(:dog, Dog)
end
end
The first method seems to add complexity to the schemas. What are the pros and cons of the different methods?
EDIT: Let's assume that a pet owner can own only one pet, if the schema allows multiple pet, the verification is done in the changeset.
I spent quite a lot of time reading blog posts and answers to similar questions. I also asked this question on Elixir's discourse: https://elixirforum.com/t/how-to-handle-schemas-polymorphism-in-phoenix/13269/24 and received great answers.
A reccurent point is that this problem is more a SQL problem than a Phoenix or Ecto problem. It is true that Ecto offers some ways to solve this problem, but the resolution of this question should start with: "How to handle polymorphic association in a relational database".
Note that if you are here looking for a solution to solve "belong_to" polymorphic association (that is, if a concrete table belongs to two or more polymorphic table), there is an entire section for this in Ecto's documentation. This answer is for "has_many" polymorphic association.
Here is comparision of the different approaches wrote by ndac_todoroki in answer to my post on Elixir's forum, all credits goes to him:
This is when you have a big table (Animal) and each concrete animal tables are a sub-division of that table. You won’t have multiple tables.
Pros
Cons
This is when you have a base table (= Animal), and each concrete animal tables have a reference to the base table plus their unique fields. (Animal {id: 1, birth: "20180101", vaccinated: true} Animal {id: 2, birth: "20111225", vaccinated: false / Cat {animal_id: 1, colour: "brown"} Snake {animal_id: 2, length: 150})
Pros
Cons
No tables for Animals will be created. Each concrete animal tables will have all basic informations + their unique informations. This will be good if you want to assure that all animals are animals, but you don’t work with Animals directly. (I’d rather create a protocol named Animal rather that doing this)
Pros
Cons
This is described in the Ecto docs. (Sample repo) Using only this seems not very helpful on your usage, but if you are looking at STI or CTI, it may help the implementation.
When you create a Class Table Inheritance, each table refers to an Animal base table. Using abstract tables, you can split that into multiple base tables for each concrete animal tables, for example a cat table will refer tocat_base and snake to snake_base, where cat_base and snake_base will have same columns. Then we will create an abstract table animal, and when you cat |> Animal.add_base_animal_info() it will create a cat_base.
Pros
Cons
I think this is somewhere between Class Table Inheritance and Concrete Table Inheritance.
Embedding can be done in Postgres and MongoDB etc. You can have a single animal table with a field that accepts a map (field :details, :map). Then you define many concrete animal modules which its schema refers to that animal table, having a embeds_one :details, CatDetails where you define a embed_schema for CatDetails. (This example is STI with Embeds)
Pros
Cons
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With