Oh I think in this case, itās exact case noted in put_assoc
, where it is recommended you use something other than put_assoc
: https://hexdocs.pm/ecto/Ecto.Changeset.html#put_assoc/4-example-adding-a-comment-to-a-post
I was facing your exact problem - in my case adding new address for a user.
Eventually maybe Iāll add address checks to find out same address was already created - however for now, my plan is to always add new addresses.
So that follows your example - I have users, addresses, and users_addresses as tables.
After working many strategies, I think the best case would be to be more explicit than be implicit in this case, which follows along the examples listed in the link in ways to insert a single new entry. Note that the examples in the link is not using a many-to-many table.
In my case, I decided to explicitly do the actual DB operations wrapped in Ecto.Multi
. I think this is a case where cast_assoc
and put_assoc
canāt help, and I donāt think there are any helper functions that deal with adding extra columns in the many-to-many table. So, my strategy was if I at least be slightly lower level and explicitly drive the DB operations, at least it will be more maintainable later on.
Beauty of Elixir is thatā¦ well lemme show you the code first:
profile = Repo.one(Profile)
my_role = "some role"
result =
Ecto.Multi.new()
|> Ecto.Multi.insert(:user, User.changeset(%User{}, address))
|> Ecto.Multi.insert(:user_profile, fn %{user: user} ->
%UserProfile{user_id: user.id, profile_id: profile.id, role: my_role}
end)
|> Repo.transaction()
case result do
{:ok, %{user_profile: user_profile}} ->
{:ok, user_profile}
{:error, _failed_operation, failed_value, _changes_so_far} ->
{:error, failed_value}
end
These few lines chain insertions, and also do transaction rollback. Amazing!
Iām still learning Ecto so thereās probably better ways to do it - but I tend to rather be close to the DB (as Ecto is still new for me)