I’m setting up my schemas and migrations in a Phoenix app. I’m trying to create a many-to-many relation from a table called instances
to itself, but I’m getting an error – I think this error shows that I don’t quite understand many-to-many associations in Ecto. Maybe someone here can help?
This is the schema in question:
defmodule Backend.Instance do
use Ecto.Schema
import Ecto.Changeset
schema "instances" do
field :domain, :string
many_to_many :peers, Backend.Instance,
join_through: Backend.InstancePeer,
join_keys: [source_domain: :domain, target_domain: :domain]
has_many :instance_peers, Backend.InstancePeer, foreign_key: :source_domain
timestamps()
end
end
defmodule Backend.InstancePeer do
use Ecto.Schema
import Ecto.Changeset
schema "instance_peers" do
belongs_to :source_domain, Backend.Instance, references: :domain, type: :string
belongs_to :target_domain, Backend.Instance, references: :domain, type: :string
timestamps()
end
end
In the instances
schema, the instance_peers
field is there because I want to bulk insert associations without necessarily knowing the IDs of the instances – only the domain
fields.
My migrations look like so:
def change do
create table(:instances) do
add :domain, :string, null: false
timestamps()
end
create unique_index(:instances, [:domain])
create table(:instance_peers) do
add :source_domain, references(:instances, column: :domain, type: :string)
add :target_domain, references(:instances, column: :domain, type: :string)
timestamps()
end
create unique_index(:instance_peers, [:source_domain, :target_domain])
end
Now, I am trying to bulk-insert a list of instance_peer
. peers
below is a list of strings.
instance_peers =
Enum.map(
peers,
&%InstancePeer{
source_domain: curr_domain,
target_domain: &1,
inserted_at: now,
updated_at: now
}
)
instance
|> Repo.preload(:instance_peers)
|> Ecto.Changeset.change()
|> Ecto.Changeset.put_assoc(:instance_peers, instance_peers)
|> Repo.update!()
I am getting an error that I do not understand. The call to Repo.preload/2
throws the following error:
** (Ecto.QueryError) /home/backend/deps/ecto/lib/ecto/association.ex:622: field `source_domain` in `where` is a virtual field in schema Backend.InstancePeer in query:
from i0 in Backend.InstancePeer,
where: i0.source_domain == ^1,
order_by: [asc: i0.source_domain],
select: {i0.source_domain, i0}
(elixir) lib/enum.ex:1940: Enum."-reduce/3-lists^foldl/2-0-"/3
(elixir) lib/enum.ex:1431: Enum."-map_reduce/3-lists^mapfoldl/2-0-"/3
(elixir) lib/enum.ex:1940: Enum."-reduce/3-lists^foldl/2-0-"/3
(ecto) lib/ecto/repo/queryable.ex:138: Ecto.Repo.Queryable.execute/4
(ecto) lib/ecto/repo/queryable.ex:18: Ecto.Repo.Queryable.all/3
(elixir) lib/enum.ex:1327: Enum."-map/2-lists^map/1-0-"/2
But source_domain
is not a virtual field! As far as I understand it’s a real field in the database as defined in the migration. Have I misunderstood what virtual fields are?