如何在加入 Table 记录时为多对多关联设置列值

HOWTO Set Column Value on Join Table Record for Many-to-Many Association

我正在尝试充分利用 Phoenix/Ecto 框架中的 conveniences/automagic 来尽可能处理数据库 INSERT。但是,我发现在使用 many_to_many 关联的情况下有点困难。

详细说明:

%{
   "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(包装在事务中)来执行此操作:

  1. 插入用户
  2. 插入组织
  3. INSERT 会员资格 user_role

提前感谢您的帮助!

我想分享我是如何解决这个问题的,这并不像我预期的那么微不足道。值得庆幸的是,Discord 上的 Elixir 社区能够指导我完成很多这一切,所以非常感谢他们提供的所有帮助!谢谢!

有几件事我在原文中没有提到 post 让事情变得复杂。他们是:

  1. 依靠 Phoenix 的嵌套表单来处理 many-to-many 关联
  2. User.changeset中使用cast_assoc来处理嵌套数据
  3. NOT NULL CONSTRAINT user_role 字段 membership table – JOIN table

一般来说,关键是摆脱 Phoenix 框架的便利,采取更“step-by-step”的方法。总之,方法是:

  1. 分别为每个 table 细分 INSERT
  2. 使用 Ecto.MultiEcto.Repo.transaction()INSERT 作为单个事务执行
  3. 在任何 changeset 函数中删除或不使用 cast_assoc
  4. 手动设置 user_role 成员
  5. 创建一个 embedded_schema 来表示表单,为每个 table 提取相关值并使用相关 changeset 函数
  6. 适当地转换这些值

这个解决方案的 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 时无法设置。因此需要单独的 INSERTs.

我首先尝试通过从数据库中的 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 约束了。在我的特殊情况下,嵌套表单根本行不通。

同样,有一种观点认为,以这种方式建模表单会使它与我们的数据库架构过于紧密地耦合在一起,这可能会导致其他复杂情况。所以,总的来说,我想解耦!

我可以写更多,但我认为这足以涵盖主要领域。如果您有任何问题,请随时发表评论。

干杯!