I try associate 2 existing Many-to-Many records with ECTO and put_assoc/4 but won't remove elements when try update.
Basically i have projects and users . for manage the access of users to projects i have the table "user_project".
def Project do
schema "project" do
...
# users (if user_type is :admin)
many_to_many(
:users,
User,
join_through: "user_project"
)
...
end
end
def User do
schema "user" do
...
# users (if user_type is :admin)
many_to_many(
:projects,
User,
join_through: "user_project"
)
...
end
...
def changeset_insert_not_active(%User{} = user, attrs) do
user
|> cast(attrs, @required_fields)
|> put_assoc(:projects, attrs.projects)
|> validate_required(@required_fields)
|> validate_user_type()
|> validate_format(:email, ~r/@/)
|> unique_constraint(:email)
end
...
def changeset_update_projects(%User{} = user, projects) do
user
|> cast(%{}, @required_fields)
# associate projects to the user
|> put_assoc(:projects, projects)
end
...
end
def Management do
...
def create_user(attrs \\ %{}, project_ids \\ []) when is_list(project_ids) do
projects =
Project
|> where([project], project.id in ^project_ids)
|> Repo.all()
%User{}
|> User.changeset_insert(attrs
|> Map.put(:projects, projects))
|> Repo.insert()
end
...
def upsert_user_projects(user, project_ids) when is_list(project_ids) do
projects =
Project
|> where([project], project.id in ^project_ids)
|> Repo.all()
user
|> User.changeset_update_projects(projects)
|> Repo.update()
end
...
end
When i create user with list of projects is everything created but when try update the user projects removing the access to one project nothing happens ...
For example:
test "xxx" do
Management.upsert_user_projects(user, [1])
l = Management.list_user_project_by_user(user.id)
IO.puts("---------")
IO.puts("1 - length:#{length(l)}")
IO.puts("---------")
Enum.each(l, fn project ->
IO.inspect(project.project_id, label: "inserted project_id ")
end)
Management.upsert_user_projects(user, [2])
l = Management.list_user_project_by_user(user.id)
IO.puts("---------")
IO.puts("2 - length:#{length(l)}")
IO.puts("---------")
Enum.each(l, fn project ->
IO.inspect(project.project_id, label: "inserted project_id ")
end)
end
Returned values:
---------
1 - length:1
---------
inserted project_id : 1
2 - length:2
---------
inserted project_id : 1
inserted project_id : 2
Why? Why put_assoc/4 just add new elements don't remove?
I think you need to define
on_replace
option on association:https://hexdocs.pm/ecto/Ecto.Changeset.html#module-the-on_replace-option
@IvanYurov are right . You need to define on_replace option but also need the preloads of user updated (in every request). Basiclly for your test to be correct you need to do a user=get_user (with the preload of the projects) before Management.upsert_user_projects(user, ...).