多对多与 ECTO 和 put_assoc/4
Many-to-Many with ECTO and put_assoc/4
我尝试将 2 个现有的多对多记录与 ECTO 和 put_assoc/4 相关联,但在尝试更新时不会删除元素。
基本上我有项目和用户。为了管理用户对项目的访问,我有 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
当我使用项目列表创建用户时,所有内容都已创建,但是当尝试更新用户项目时,删除对一个项目的访问权限时没有任何反应...
例如:
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
返回值:
---------
1 - length:1
---------
inserted project_id : 1
2 - length:2
---------
inserted project_id : 1
inserted project_id : 2
为什么?为什么 put_assoc/4 只添加新元素而不删除?
我认为您需要在关联上定义 on_replace
选项:
https://hexdocs.pm/ecto/Ecto.Changeset.html#module-the-on_replace-option
@IvanYurov 是对的。您需要定义 on_replace 选项,但还需要更新用户的预加载(在每个请求中)。基本上为了使您的测试正确,您需要在 Management.upsert_user_projects(user, ...).
之前执行 user=get_user (预加载项目)
def get_user(id) do
Repo.get(User, id)
|> Repo.preload(:projects)
end
我尝试将 2 个现有的多对多记录与 ECTO 和 put_assoc/4 相关联,但在尝试更新时不会删除元素。
基本上我有项目和用户。为了管理用户对项目的访问,我有 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
当我使用项目列表创建用户时,所有内容都已创建,但是当尝试更新用户项目时,删除对一个项目的访问权限时没有任何反应...
例如:
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
返回值:
---------
1 - length:1
---------
inserted project_id : 1
2 - length:2
---------
inserted project_id : 1
inserted project_id : 2
为什么?为什么 put_assoc/4 只添加新元素而不删除?
我认为您需要在关联上定义 on_replace
选项:
https://hexdocs.pm/ecto/Ecto.Changeset.html#module-the-on_replace-option
@IvanYurov 是对的。您需要定义 on_replace 选项,但还需要更新用户的预加载(在每个请求中)。基本上为了使您的测试正确,您需要在 Management.upsert_user_projects(user, ...).
之前执行 user=get_user (预加载项目)def get_user(id) do
Repo.get(User, id)
|> Repo.preload(:projects)
end