微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

HOWTO 为多对多关联的连接表记录设置列值 cast_assoc 由于 JOIN TABLE 字段上的 NOT NULL CONSTRAINT 而失败embedded_schemas 而不是嵌套表单

如何解决HOWTO 为多对多关联的连接表记录设置列值 cast_assoc 由于 JOIN TABLE 字段上的 NOT NULL CONSTRAINT 而失败embedded_schemas 而不是嵌套表单

我正在尝试充分利用 Phoenix/Ecto 框架的便利性/自动功能,尽可能地处理数据库插入。但是,我发现使用 many_to_many 关联有点困难。

详细说明:

  • 我有三个构成 many_to_many 关联的模式:UserOrganisationMembership
  • 简而言之,用户通过成员资格拥有许多组织,组织通过成员资格拥有许多用户
  • 会员记录还有一个 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 设置为特定值(例如,creator)。换句话说,有没有办法在 user_role 上设置 attrs,Phoenix/Ecto 会提取并存储在 Membership 记录中?

如果没有,我该怎么做?我是否需要使用三个单独的 INSERT(包装在事务中)来执行此操作:

  1. 插入用户
  2. 插入组织
  3. 使用 user_role 插入成员身份

提前感谢您的帮助!

解决方法

我想分享我是如何解决这个问题的,这并不像我预期的那么简单。值得庆幸的是,Discord 上的 Elixir 社区能够指导我完成很多工作,因此非常感谢他们的所有帮助!谢谢!

有些事情我在原帖中没有提到,这让事情变得复杂。他们是:

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

总的来说,关键是远离Phoenix框架的便利,采取更“循序渐进”的方式。总之,该方法是:

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

通过查看下面的多事务可以看到此解决方案的鸟瞰图:

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 由于 JOIN TABLE 字段上的 NOT NULL CONSTRAINT 而失败

最初,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 :users,App.Accounts.User,join_through: App.Organisations.Membership

如前所述,错误是由数据库抛出的,这是因为 cast_assoc 自动为 JOIN TABLE 创建了 INSERT。但是,由于 user_role 具有 NOT NULL 约束,因此由于未设置 user_role 而引发错误。据我所知,使用 cast_assoc 时无法设置。因此需要单独的 INSERT

我首先尝试通过从数据库中的 NOT NULL 字段中删除 user_role 来放宽约束,并使用了与上面相同的多事务,我不喜欢这样做,因为它有数据完整性的风险。然而,最终发生的是一个组织的两条记录,并且正在创建一个成员资格——多事务中的 cast_assocINSERT 各有一个记录。

因此,它清楚地告诉我,如果我将操作分解为多个 cast_assoc,则根本不需要(也不应该使用)INSERT。它还迫使我寻找另一种方式(见下文),并使我能够(高兴地)恢复 NOT NULL 约束。

embedded_schemas 而不是嵌套表单

这不是要敲打 Phoenix Framework 提供的通过嵌套表单使我们更方便的功能,而是演示何时应该使用它。当我说嵌套表单时,我指的是这份名为 Polymorphic associations with many to many 的指南。在本指南中,它详细介绍了一个简单的待办事项列表示例。很简单,因为 JOIN TABLE 没有要设置的其他字段,更不用说对这些字段之一的 NOT NULL 约束。在我的特殊情况下,嵌套表单根本不起作用。

同样,有一种观点认为,以这种方式建模表单与我们的数据库模式耦合得太紧密,这可能会导致其他并发症。所以,总的来说,我想是解耦的!

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

干杯!

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。