VictorGaiva

VictorGaiva

Implicit cast_assoc insertion order causes unpredictable constraint error

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.

Where Next?

Popular in Discussions Top

Fl4m3Ph03n1x
Background A few days ago I was listening to The future of Elixir from Elixir Talks, with Dave Thomas (@pragdave ) and Brian Mitchell. I...
New
praveenperera
How We Replaced React with Phoenix By: Thought Bot
New
pillaiindu
I want to convert a Phoenix LiveView CRUD website to a CRUD mobile app. What do you think is the easiest way to do so?
New
ricklove
I was just introduced to Elixir and Phoenix. I was told about the 2 million websocket test that was done 2 years ago. From my research, t...
New
AlexMcConnell
The reason that Rails is as popular as it is is because it’s very easy for relatively inexperienced developers to get a lot of work done....
588 19568 166
New
CharlesO
Erlang :list.nth simple, but 1 - based nth(1, [H|_]) -> H; nth(N, [_|T]) when N > 1 -> nth(N - 1, T). Elixir Enum.at … coo...
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New
Qqwy
I would like to spark a discussion about the static access operator: .. For whom does not know: it is used in Elixir to access fields of...
New
Markusxmr
Since Drab has been developed for a while in the open, introducing the Liveview functionality in a way it happend appears to undermine th...
New
slashdotdash
Phoenix Live View is now publicly available on GitHub. Here’s Chris McCord’s tweet announcing making it public.
New

Other popular topics Top

sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 53690 245
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 39297 209
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New

We're in Beta

About us Mission Statement