I have Ecto schemas representing a directed graph -- there is a Node
schema with a self-referential many-to-many relation through an intermediate NodeEdge
schema. i.e.
schema "nodes" do
field :name, :string
many_to_many :edges, MyApp.Node,
join_through: MyApp.NodeEdge,
join_keys: [source: :id, target: :id]
timestamps()
end
schema "node_edges" do
belongs_to :source, MyApp.Node
belongs_to :target, MyApp.Node
timestamps()
end
with the following initial migration:
create table(:nodes) do
add :name, :string, null: false
timestamps()
end
create table(:node_edges) do
add :source, references(:nodes)
add :target, references(:nodes)
timestamps()
end
I'm trying to bulk insert edges like so:
now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
new_edges =
Enum.map(
result.edges,
&%NodeEdge{
source: curr_name,
target: &1,
inserted_at: now,
updated_at: now
}
)
node
|> Repo.preload(:edges)
|> Ecto.Changeset.change()
|> Ecto.Changeset.put_assoc(:edges, my_edges)
|> Repo.update!()
As far as I can tell, this is pretty close to the documentation for put_assoc/4
. However, I'm getting an error:
** (ArgumentError) expected changeset data to be a Elixir.MyApp.Node struct, got: %MyApp.NodeEdge{__meta__: #Ecto.Schema.Metadata<:built, "node_edges">, id: nil, inserted_at: ~N[2019-06-25 23:18:35], source: "nodeA", source_id: nil, target: "nodeB", target_id: nil, updated_at: ~N[2019-06-25 23:18:35]}
Is the error perhaps that I don't know the IDs of the nodes in advance? Or can anyone see another problem?
Ab error is pretty self-explained: you are creating a list of NodeEdge
, and then tries to apply this list to the association edges
, which is Node
type.
Bunch of solutions:
Build Node
list, instead of NodeEdge
list. Put this list with put_assoc
Add relation to the node edges
schema "nodes" do
field :name, :string
many_to_many :edges, MyApp.Node,
join_through: MyApp.NodeEdge,
join_keys: [source: :id, target: :id]
has_many :node_edges, MyApp.NodeEdge, foreign_key: source
timestamps()
end
Now, put your NodeEdge
list here.
Simply create this list - it's absolutely self-explained. Because NodeEdge
has relations - use them to build associations:
%NodeEdge{
source: put_assoc(:source, curr_name),
target: put_assoc(:target, &1),
inserted_at: now,
updated_at: now
}
Build this list and then save it.