Please why am I loosing child association data when ecto changeset insert fails?

Hello !
So I have a Client schema that belongs to a User schema, and has one Company schema (optional).

Here is the Client changeset I’m using with a registration form :

@doc false
  def create_changeset(%Client{} = client, attrs) do
    |> cast(attrs, [:qualification, :phone])
    |> cast_assoc(:user, with: &User.create_changeset/2)
    |> cast_assoc(:company)
    |> validate_required([:qualification])
    |> unique_constraint(:user_id)

And now here is a Context function that creates a User, Client and Company all at once.

def create_client(attrs \\ %{}) do
    |> Client.create_changeset(attrs)
    |> Repo.insert()

When all required data and their validation are ok on the registration form submit, the insertion succeeds and everything goes as I hope. Even when form validation fails before insertion attempt, the registration form is rendered back with all errors and populated with previously sent data…

But when insertion fails on a User database constraint (for example an already taken email) , the form is rendered back with all data and errors only for User and Client schema. All Company data are lost. This will force the client to re-enter all his Company information.
Is this behaviour unavoidable?

In case it could be useful, I’m using Phauxth library for user authentication.

I’m not really sure I understand what’s going on with the above behaviour…
Maybe Company changeset is lost because of the type of association between Client and Company ? Or maybe because I’m trying to insert parent (User) and child (Company) of the same schema (Client) at the same time ?

But anyway all insertions succeed perfectly as intended if form data are ok… ^^
And finally someone suggested me, when insertion failed because of DB constraint, I could always retrieve company params in the returned changeset using “changeset.params” even if Company changeset vanished from the changes… :slight_smile:

So with the following code I restore the lost changeset and the registration form is populated correctly.

{:error, %Ecto.Changeset{} = changeset} ->
  |> Changeset.put_assoc(:company, Company.changeset(%Company{},
  changeset.params["company"] || %{}))
  render(conn, "new.html", changeset: changeset)

Well I have what I wanted, so I guess I won’t complain. ^^

1 Like