如何在加入 Table 记录时为多对多关联设置列值
HOWTO Set Column Value on Join Table Record for Many-to-Many Association
我正在尝试充分利用 Phoenix/Ecto 框架中的 conveniences/automagic 来尽可能处理数据库 INSERT。但是,我发现在使用 many_to_many
关联的情况下有点困难。
详细说明:
- 我有三个构成
many_to_many
关联的模式:User
、Organisation
和 Membership
- 简而言之,用户通过会员资格拥有许多组织,组织通过会员资格拥有许多用户
- 会员记录也有一个
user_role
列来指定用户相对于相关组织的角色(例如,创建者、管理员等)
- 当新用户注册时,注册表单还会提供一个名称字段,以便用户可以提供其组织的名称。
- 这部分是关键,因为它意味着
User
表单具有 Organisation
的嵌套输入
- 我也不想公开表单上的
user_role
字段。这是我希望系统在幕后管理的东西。
- 为了说明,表单返回的结构如下所示:
%{
"email" => "test@test.com",
"password" => "elixirrocks",
"organisations" => %{"0" => %{"name" => "ACME CO."}}
}
在相关 Ecto 模式中正确设置关联后,此结构将使用以下一组操作插入到数据库中:
%User{}
|> User.changeset(attrs)
|> Repo.insert()
但是,我想做的是以某种方式将 user_role
设置为特定值(例如,创建者),而不更改上面显示的操作集。换句话说,有没有办法以某种方式在 attrs
上设置 Phoenix/Ecto 将拾取并存储在 Membership
记录中的 user_role
?
如果没有,我该怎么做呢?我是否需要使用三个单独的 INSERT(包装在事务中)来执行此操作:
- 插入用户
- 插入组织
- INSERT 会员资格 user_role
提前感谢您的帮助!
我想分享我是如何解决这个问题的,这并不像我预期的那么微不足道。值得庆幸的是,Discord 上的 Elixir 社区能够指导我完成很多这一切,所以非常感谢他们提供的所有帮助!谢谢!
有几件事我在原文中没有提到 post 让事情变得复杂。他们是:
- 依靠 Phoenix 的嵌套表单来处理
many-to-many
关联
- 在
User.changeset
中使用cast_assoc
来处理嵌套数据
NOT NULL CONSTRAINT
user_role
字段 membership
table – JOIN
table
一般来说,关键是摆脱 Phoenix 框架的便利,采取更“step-by-step”的方法。总之,方法是:
- 分别为每个 table 细分
INSERT
- 使用
Ecto.Multi
和 Ecto.Repo.transaction()
将 INSERT
作为单个事务执行
- 在任何
changeset
函数中删除或不使用 cast_assoc
- 手动设置
user_role
成员
- 创建一个
embedded_schema
来表示表单,为每个 table 提取相关值并使用相关 changeset
函数 适当地转换这些值
这个解决方案的 birds-eye 视图可以通过查看下面的多事务来了解:
1 Ecto.Multi.new()
2 |> Ecto.Multi.insert(:user, User.registration_changeset(%User{}, attrs))
3 |> Ecto.Multi.insert(:org, Organisation.changeset(%Organisation{}, %{name: org_name}))
4 |> Ecto.Multi.insert(:membership, fn %{user: user, org: org} ->
5 Membership.membership_changeset(%Membership{}, user, org, %{user_role: "creator"})
6 end)
7 |> Repo.transaction(
为可能需要的人提供的一些重要说明...
cast_assoc
失败,因为 NOT NULL CONSTRAINT
在 JOIN TABLE 字段上
最初,User.registration_changeset
是这样的,因为我试图利用 Phoenix 的嵌套表单处理程序:
1 def registration_changeset(user, attrs, opts \ []) do
2 user
3 |> cast(attrs, [:first_name, :last_name, :email, :password])
4 |> cast_assoc(:organisations, required: false )
5 |> validate_email()
6 |> validate_password(opts)
7 end
当我提交表单时,数据库会抛出以下错误。
ERROR 23502 (not_null_violation) null value in column "user_role" violates not-null constraint
罪魁祸首是第 4 行,cast_assoc
,但深入挖掘,这是因为用户和组织之间的 many-to-many 关联由此定义:
many_to_many :users, App.Accounts.User,
join_through: App.Organisations.Membership
如前所述,错误是由数据库引发的,这是因为 cast_assoc
自动为 JOIN TABLE 创建了 INSERT
。但是,因为 user_role
有一个 NOT NULL
约束,所以抛出了一个错误,因为 user_role
没有被设置。据我了解,使用 cast_assoc
时无法设置。因此需要单独的 INSERT
s.
我首先尝试通过从数据库中的 user_role
字段中删除 NOT NULL
来放松约束,并使用上面相同的多事务处理,我不喜欢这样做,因为它有数据风险正直。然而,最终发生的是为一个组织和一个成员创建了两条记录——一条记录用于 cast_assoc
,一条记录用于多事务中的 INSERT
。
因此,它清楚地向我表明,如果我将操作分解为多个 INSERT
,则根本不需要(也不应该使用)cast_assoc
。这也迫使我寻找另一种方式(见下文),也使我能够(愉快地)恢复 NOT NULL
约束。
embedded_schemas 而不是嵌套形式
这不是敲Phoenix Framework提供的嵌套表单来方便我们使用,而是演示什么时候应该使用它。当我说嵌套表单时,我指的是标题为 Polymorphic associations with many to many 的指南。在本指南中,它详细介绍了一个简单的待办事项列表示例。从 JOIN TABLE 没有要设置的其他字段的意义上来说很简单,更不用说对其中一个字段的 NOT NULL
约束了。在我的特殊情况下,嵌套表单根本行不通。
同样,有一种观点认为,以这种方式建模表单会使它与我们的数据库架构过于紧密地耦合在一起,这可能会导致其他复杂情况。所以,总的来说,我想解耦!
我可以写更多,但我认为这足以涵盖主要领域。如果您有任何问题,请随时发表评论。
干杯!
我正在尝试充分利用 Phoenix/Ecto 框架中的 conveniences/automagic 来尽可能处理数据库 INSERT。但是,我发现在使用 many_to_many
关联的情况下有点困难。
详细说明:
- 我有三个构成
many_to_many
关联的模式:User
、Organisation
和Membership
- 简而言之,用户通过会员资格拥有许多组织,组织通过会员资格拥有许多用户
- 会员记录也有一个
user_role
列来指定用户相对于相关组织的角色(例如,创建者、管理员等) - 当新用户注册时,注册表单还会提供一个名称字段,以便用户可以提供其组织的名称。
- 这部分是关键,因为它意味着
User
表单具有Organisation
的嵌套输入
- 我也不想公开表单上的
user_role
字段。这是我希望系统在幕后管理的东西。 - 为了说明,表单返回的结构如下所示:
- 这部分是关键,因为它意味着
%{
"email" => "test@test.com",
"password" => "elixirrocks",
"organisations" => %{"0" => %{"name" => "ACME CO."}}
}
在相关 Ecto 模式中正确设置关联后,此结构将使用以下一组操作插入到数据库中:
%User{}
|> User.changeset(attrs)
|> Repo.insert()
但是,我想做的是以某种方式将 user_role
设置为特定值(例如,创建者),而不更改上面显示的操作集。换句话说,有没有办法以某种方式在 attrs
上设置 Phoenix/Ecto 将拾取并存储在 Membership
记录中的 user_role
?
如果没有,我该怎么做呢?我是否需要使用三个单独的 INSERT(包装在事务中)来执行此操作:
- 插入用户
- 插入组织
- INSERT 会员资格 user_role
提前感谢您的帮助!
我想分享我是如何解决这个问题的,这并不像我预期的那么微不足道。值得庆幸的是,Discord 上的 Elixir 社区能够指导我完成很多这一切,所以非常感谢他们提供的所有帮助!谢谢!
有几件事我在原文中没有提到 post 让事情变得复杂。他们是:
- 依靠 Phoenix 的嵌套表单来处理
many-to-many
关联 - 在
User.changeset
中使用cast_assoc
来处理嵌套数据 NOT NULL CONSTRAINT
user_role
字段membership
table –JOIN
table
一般来说,关键是摆脱 Phoenix 框架的便利,采取更“step-by-step”的方法。总之,方法是:
- 分别为每个 table 细分
INSERT
- 使用
Ecto.Multi
和Ecto.Repo.transaction()
将INSERT
作为单个事务执行 - 在任何
changeset
函数中删除或不使用cast_assoc
- 手动设置
user_role
成员 - 创建一个
embedded_schema
来表示表单,为每个 table 提取相关值并使用相关changeset
函数 适当地转换这些值
这个解决方案的 birds-eye 视图可以通过查看下面的多事务来了解:
1 Ecto.Multi.new()
2 |> Ecto.Multi.insert(:user, User.registration_changeset(%User{}, attrs))
3 |> Ecto.Multi.insert(:org, Organisation.changeset(%Organisation{}, %{name: org_name}))
4 |> Ecto.Multi.insert(:membership, fn %{user: user, org: org} ->
5 Membership.membership_changeset(%Membership{}, user, org, %{user_role: "creator"})
6 end)
7 |> Repo.transaction(
为可能需要的人提供的一些重要说明...
cast_assoc
失败,因为 NOT NULL CONSTRAINT
在 JOIN TABLE 字段上
最初,User.registration_changeset
是这样的,因为我试图利用 Phoenix 的嵌套表单处理程序:
1 def registration_changeset(user, attrs, opts \ []) do
2 user
3 |> cast(attrs, [:first_name, :last_name, :email, :password])
4 |> cast_assoc(:organisations, required: false )
5 |> validate_email()
6 |> validate_password(opts)
7 end
当我提交表单时,数据库会抛出以下错误。
ERROR 23502 (not_null_violation) null value in column "user_role" violates not-null constraint
罪魁祸首是第 4 行,cast_assoc
,但深入挖掘,这是因为用户和组织之间的 many-to-many 关联由此定义:
many_to_many :users, App.Accounts.User,
join_through: App.Organisations.Membership
如前所述,错误是由数据库引发的,这是因为 cast_assoc
自动为 JOIN TABLE 创建了 INSERT
。但是,因为 user_role
有一个 NOT NULL
约束,所以抛出了一个错误,因为 user_role
没有被设置。据我了解,使用 cast_assoc
时无法设置。因此需要单独的 INSERT
s.
我首先尝试通过从数据库中的 user_role
字段中删除 NOT NULL
来放松约束,并使用上面相同的多事务处理,我不喜欢这样做,因为它有数据风险正直。然而,最终发生的是为一个组织和一个成员创建了两条记录——一条记录用于 cast_assoc
,一条记录用于多事务中的 INSERT
。
因此,它清楚地向我表明,如果我将操作分解为多个 INSERT
,则根本不需要(也不应该使用)cast_assoc
。这也迫使我寻找另一种方式(见下文),也使我能够(愉快地)恢复 NOT NULL
约束。
embedded_schemas 而不是嵌套形式
这不是敲Phoenix Framework提供的嵌套表单来方便我们使用,而是演示什么时候应该使用它。当我说嵌套表单时,我指的是标题为 Polymorphic associations with many to many 的指南。在本指南中,它详细介绍了一个简单的待办事项列表示例。从 JOIN TABLE 没有要设置的其他字段的意义上来说很简单,更不用说对其中一个字段的 NOT NULL
约束了。在我的特殊情况下,嵌套表单根本行不通。
同样,有一种观点认为,以这种方式建模表单会使它与我们的数据库架构过于紧密地耦合在一起,这可能会导致其他复杂情况。所以,总的来说,我想解耦!
我可以写更多,但我认为这足以涵盖主要领域。如果您有任何问题,请随时发表评论。
干杯!