我在一条类似的船上(使用 GraphQL),我选择尽可能远离cast_assoc
。这不是因为“创建”场景,而是因为“更新”场景。
查看 的文档cast_assoc
,您会看到它说...
- 如果参数不包含ID,则参数数据将通过新结构传递到changeset/2,成为插入操作
- 如果参数包含一个 ID 并且没有与该 ID 关联的子节点,则参数数据将通过新结构传递到 changeset/2 并成为插入操作
- 如果参数包含一个 ID 并且有一个关联的子 ID 与该 ID,参数数据将通过现有结构传递到 changeset/2 并成为更新操作
- 如果有一个带有 ID 的关联子节点并且其 ID 未作为参数给出,则将调用该关联的 :on_replace 回调(请参阅模块文档中的“替换时”部分)
场景 1 是您的标准创建,这意味着您的数据需要看起来类似于上面的嵌套输入。(它实际上需要是电子邮件密钥的映射列表。
假设一个人添加了第二封电子邮件(您在上面指出这是一对多)。如果您的输入如下所示:
%{
id: 12345,
username: "test",
emails: [
%{email: "test_2@123.com"}
}
}
...这会触发场景 1(新参数,无 ID)和场景 4(未给出 ID 的孩子)有效地删除所有先前的电子邮件。这意味着您的更新参数实际上需要如下所示:
%{
id: 12345,
username: "test",
emails: [
%{id: 1, email: "test@123.com"},
%{email: "test_2@123.com}
]
}
...对我来说,这意味着在请求中排队很多额外的数据。对于像电子邮件这样的东西——用户不太可能很少——成本很低。对于创建更丰富的关联,这是一种痛苦。
与其总是放入cast_assoc
您的User.changeset
.
defmodule MyApp.UserRegistration do
[...schema, regular changeset...]
def registration_changeset(params) do
%MyApp.User{}
|> MyApp.Repo.preload(:emails)
|> changeset(params)
|> cast_assoc(:emails, required: true, with: &MyApp.Email.changeset(&1, &2))
end
end
你仍然需要emails
在你的输入中提供一个嵌套字段,这可能是一个无赖,但至少你不会用 cast_assoc 污染你的普通用户变更集。
最后一个想法:与其让您的客户关心嵌套,您可以在特定于注册的解析器函数中这样做吗?