With the following schema
defmodule Grid do
use Ecto.Schema
import Ecto.Changeset
schema "grids" do
has_many(:xs, Grid.X)
has_many(:ys, Grid.Y)
has_many(:points, Grid.Point)
end
def changeset(model, params) do
model
|> cast(params, [:uuid])
|> cast_assoc(:points)
|> cast_assoc(:xs)
|> cast_assoc(:ys)
end
end
defmodule Grid.X do
use Ecto.Schema
import Ecto.Changeset
schema "grid_xs" do
has_many(:points, Grid.Point)
belongs_to(:grid, MeasureTable)
end
def changeset(model, params) do
model
|> cast(params, [:uuid, :grid_id, :value])
|> validate_required([:grid_id])
|> foreign_key_constraint(:grid_id)
end
end
defmodule Grid.Y do
use Ecto.Schema
import Ecto.Changeset
schema "grid_ys" do
has_many(:points, Grid.Point)
belongs_to(:grid, MeasureTable)
end
def changeset(model, params) do
model
|> cast(params, [:uuid, :grid_id, :value])
|> validate_required([:grid_id])
|> foreign_key_constraint(:grid_id)
end
end
defmodule Grid.Point do
use Ecto.Schema
import Ecto.Changeset
schema "grid_points" do
field(:value, :float)
belongs_to(:x, Grid.X)
belongs_to(:y, Grid.Y)
belongs_to(:grid, MeasureTable)
end
def changeset(model, params) do
model
|> cast(params, [:uuid, :x_id, :y_id, :grid_id, :value])
|> validate_required([:x_id, :y_id, :grid_id])
|> foreign_key_constraint(:x_id)
|> foreign_key_constraint(:y_id)
|> foreign_key_constraint(:grid_id)
end
end
I’m able to create the whole grid
structure using a single changeset
, defined at Grid.changeset/2
. Under the hood I can see Ecto doing the each insertion in sequence, starting at grids
, then trying to insert points
relation. At first this wouldn’t work, since the IDs are not yet defined. But I can generate it in code and populate the data structure with the IDs of each relation.
However since points
has a foreign_key constraint with x
and y
, and Ecto starts the insertion with it, x's
and y's
are not yet defined, and the insertion fails.
But if I re-order the Grid
schema definition, putting the points
relation first, like this:
schema "grids" do
has_many(:points, Grid.Point)
has_many(:xs, Grid.X)
has_many(:ys, Grid.Y)
# has_many(:points, Grid.Point)
end
Since Ecto starts from bottom to top, the insertion succeeds since the entries are present in the database.
My proposal is to make this type of behavior explicitly controlled by the developer. It could be done by adding a depends
field to cast_assoc
, so a explicit order of insertion can be defined.