Пожалуйста, почему я теряю данные об ассоциациях детей при сбое вставки ecto changeset?

Таким образом, у меня есть схема клиента, которая принадлежит схеме пользователя и имеет одну схему компании (необязательно).

Вот набор изменений клиента, который я использую с формой регистрации:

  @doc false
  def create_changeset(%Client{} = client, attrs) do
    client
    |> cast(attrs, [:qualification, :phone])
    |> cast_assoc(:user, with: &User.create_changeset/2)
    |> cast_assoc(:company)
    |> validate_required([:qualification])
    |> unique_constraint(:user_id)
  end

А теперь вот контекстная функция, которая создает пользователя, клиента и компанию одновременно.

def create_client(attrs \\ %{}) do
  %Client{}
  |> Client.create_changeset(attrs)
  |> Repo.insert()
end

Когда все необходимые данные и их проверка в форме регистрации в порядке, вставка завершается успешно, и все идет, как я надеюсь. Даже если проверка формы завершается неудачно перед попыткой вставки, форма регистрации возвращается со всеми ошибками и заполняется ранее отправленными данными...

Но когда вставка завершается неудачей в ограничении базы данных пользователя (например, уже принятом электронном письме), форма возвращается со всеми данными и ошибками только для схемы пользователя и клиента. Все данные компании утеряны. Это заставит клиента повторно ввести всю информацию о своей компании. Это поведение неизбежно?

В случае, если это может быть полезно, я использую библиотеку Phauxth для аутентификации пользователя.

2 ответа

Решение

Если вы хотите создать компанию независимо от клиента, рассмотрите возможность использования альтернатив для cast_assoc.

Я не знаю точно ваш вариант использования, но похоже, что вы хотите создать компанию, если с данными клиента все в порядке и независимо от пользовательских данных. Может быть, вы можете позвонить каждому changeset для каждой схемы явно, не приводя ее в Client.changeset,

Кроме того, ваш клиент не должен отправлять вам снова attr, как у вас уже есть - даже если Repo.insert не удается, у вас есть доступ к changeset.params, так что вы можете использовать их с некоторой дополнительной логикой.

Вместо того, чтобы добавлять каждый параметр вручную, вы можете объединить свои ошибки набора изменений с набором изменений до выполнения операции с базой данных:

      def create(conn, %{"client" => client}) do
  changes = Client.changeset(%Client{}, client)
  case Repo.insert(changes) do
    {:ok, _} -> ...
    {:error, errors} ->
      changes = Ecto.Changeset.merge(changes, errors)
      render(conn, "new.html", changes: changes)
  end
end

Обратите внимание, что порядок изменений имеет значение. Второй набор изменений имеет приоритет над первым 1, поэтому ошибки переопределят все существующие поля.


1 https://hexdocs.pm/ecto/Ecto.Changeset.html#merge/2
Другие вопросы по тегам