Cast_assoc with scope as 3rd parameter on changeset function

with Phoenix 1.8 generated changeset functions, with a third parameter being user_scope, how do you call cast_assoc ?

  def changeset(q, attrs, user_scope) do
    q
    |> cast(attrs, [:name])
    |> validate_required([:name])
    |> put_change(:user_id, user_scope.user.id)
    |> cast_assoc(:parts, user_scope) <<-- this does not work
  end

in parts.ex

  def changeset(part, attrs, user_scope) do
    part
    |> cast(attrs, [:name])
    |> validate_required([:name])
    |> put_change(:user_id, user_scope.user.id)
  end

do I need to implement an arity 2 changeset function ?
then tweak the param map to incorporate the user_id in it ?

like


  def embedded_changeset(part, attrs) do
    part
    |> cast(attrs, [:name,  :user_id])
    |> validate_required([:name :user_id])
  end

I faced the same issue and fixed it using the :with option:

 def changeset(q, attrs, user_scope) do
    q
    |> cast(attrs, [:name])
    |> validate_required([:name])
    |> put_change(:user_id, user_scope.user.id)
    |> cast_assoc(:parts, with: &Part.changeset(&1, &2, user_scope))
  end

thank you

there is still a hitch

normally cast_assoc will pass the id of the newly created parent to the children changesets
but that does not happen with a user defined “with”

so that it works only for update but not for an entire new parent + children changeset
where the parent id is not known

is ecto.multi the way to go then ?

The default for the :with option literally just calls module.changeset/2 so I don’t see how the behavior could diverge. Of course I could be missing something.

2 Likes

I thought that if you passed a changeset of q having many parts
with neither q and parts having ids set, this creates everything with ecto in the db,
in particular the id of q is passed to the parts to set the q_id field.

but this does not work with the three params changeset (with scope new param) for some reason

maybe I am missing something.

so instead I used ecto multi to insert q then the parts
at least you explicitly set what’s needed, but again I thought this was not needed to use a transaction

I can’t find the id behavior you describe in the docs but I have always avoided cast_assoc and what you describe makes sense. Can anyone else point to either some documentation or some code which sets the foreign key of the child assocs?

Again it could also be me missing something, but passing in a :with function doesn’t seem to change anything in the code as far as I can tell, so this behavior is surprising to me.

Can you try to reproduce this on the same schemas with and without the :with option to see if it’s actually something else causing the problem?