我有一个链接到电子邮件表的帐户表,大致如下:
目前,我的帐户变更集使用cast_assoc
来提取电子邮件:
|> cast_assoc(:emails, required: true, with: &Email.changeset(&1, &2))
但这意味着我需要提供如下数据:
%{
username: "test",
password: "secret",
emails: %{email: "test@123.com"} //<- nested
}
我使用GraphQL,并且为了支持&#34;注册&#34;形式的变异:
register(username:"test", password:"secret", email: "test@test.com")
我需要:
有没有办法重构这个,或修改我的变更集来取消嵌套字段?我是elixir和ecto的新手。
答案 0 :(得分:3)
您的问题涉及应用程序中的不同点,因此我假设您使用Phoenix&gt; = 1.3以及Absinthe。这样,我们就可以讨论您的上下文和解析器的外观。
处理传入的GraphQL请求涉及在到达域模块中的变更集函数之前经历两个抽象级别:首先,解析器;然后,上下文模块。一个重要的好习惯是你的解析器应该只调用上下文函数。我们的想法是将解析器与您的Ecto架构所在的底层域模块分开。
然后,您可以使用解析器按摩您的输入,使其适合您的上下文功能所需的任何内容。假设您的上下文名为Accounts
,您的解析器可能看起来像这样:
def register(_root, %{username: username, password: password, email: email}, _info) do
args = %{username: username, password: password, emails: [%{email: email}]}
case Accounts.create_account(args) do
{:ok, %Account{} = account} ->
{:ok, account}
{:error, changeset} ->
{:error, message: "Could not register account", details: error_details(changeset)}
end
end
然后调用这个依赖traverse_errors/2
的简单辅助函数来返回所有验证消息:
defp error_details(changeset) do
changeset
|> Ecto.Changeset.traverse_errors(fn {msg, _} -> msg end)
end
答案 1 :(得分:1)
我在类似的船上(使用GraphQL),我选择尽可能远离cast_assoc
。由于“更新”方案,因此“创建”方案更少,因此更少。
github,你会看到它说......
- 如果参数不包含ID,则参数数据将通过新结构传递给changeset / 2并成为插入操作
- 如果参数包含ID且没有具有此ID的关联子项,则参数数据将通过新结构传递给changeset / 2并成为插入操作
- 如果参数包含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污染你的正常用户变更集。
最后一个想法:您可以在注册特定的解析器功能中执行此操作,而不是让您的客户关心嵌套吗?