Как я должен управлять вставкой в ​​связанные таблицы - PullRequest
0 голосов
/ 31 мая 2018

У меня есть таблица учетных записей, которая ссылается на таблицу адресов электронной почты, примерно следующим образом:

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 и для поддержки "регистра""Мутация формы:

register(username:"test", password:"secret", email: "test@test.com")

Мне нужно:

  1. Переформатировать мой ввод, чтобы передать его в мой набор изменений для экто (вложите его в электронные письма)
  2. Сгладить ошибки моей ревизии для возврата сообщений проверки

Есть ли способ реорганизовать это или изменить мою ревизию, чтобы отменить вложение поля?Я новичок в эликсире и экто.

Ответы [ 2 ]

0 голосов
/ 31 мая 2018

Ваш вопрос касается различных точек приложения, поэтому я предполагаю, что вы используете Phoenix> = 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
0 голосов
/ 31 мая 2018

Я нахожусь в аналогичной лодке (использую GraphQL) и решил остаться как можно дальше от cast_assoc.Это меньше из-за сценария «создания» и больше из-за сценария «обновления».

Глядя на документацию для cast_assoc, вы увидите, что там написано ...

  • Если параметр не содержит идентификатора, данные параметра будут переданы в changeset / 2 с новой структурой и станут операцией вставки
  • Если параметр содержитс идентификатором, и с таким идентификатором нет связанного дочернего элемента, данные параметров будут переданы в changeset / 2 с новой структурой и станут операцией вставки
  • Если параметр содержит идентификатор и существует связанный дочерний элемент сПри таком идентификаторе данные параметров будут переданы в changeset / 2 с существующей структурой и станут операцией обновления
  • Если есть связанный дочерний элемент с идентификатором, и его идентификатор не задан в качестве параметра, обратный вызов: on_replaceдля этой связи будет вызываться (см. раздел «О замене» в документации к модулю)

Сценарий 1 является вашим стандартнымчто означает, что ваши данные должны выглядеть как ваш вложенный ввод выше.(фактически это должен быть список из карт для ключа электронной почты.

Допустим, кто-то добавляет второе электронное письмо (вы указали, что это одно-много выше). Если ваш ввод выглядит как:

%{
  id: 12345,
  username: "test",
  emails: [
    %{email: "test_2@123.com"}
  }
}

... это вызывает оба сценария 1 (новый параметр, без идентификатора) и сценарий 4 (дочерний элемент с идентификатором не является 't) эффективно удаляет все предыдущие электронные письма. Это означает, что ваши параметры обновления должны выглядеть примерно так:

%{
  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.

Еще одна мысль: вместо того, чтобы заботиться о вложении вашего клиента, вы могли бы сделать это в своей функции распознавания, связанной с регистрацией?

...