我应该如何管理插入相关表格

时间:2018-05-30 23:37:05

标签: elixir graphql phoenix-framework ecto absinthe

我有一个链接到电子邮件表的帐户表,大致如下:

enter image description here

目前,我的帐户变更集使用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")

我需要:

  1. 重新格式化我的输入,以便将其传递到我的变更集ecto(将其嵌入电子邮件中)
  2. 展开我的变更集错误以返回验证消息
  3. 有没有办法重构这个,或修改我的变更集来取消嵌套字段?我是elixir和ecto的新手。

2 个答案:

答案 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污染你的正常用户变更集。

最后一个想法:您可以在注册特定的解析器功能中执行此操作,而不是让您的客户关心嵌套吗?