Как обрабатывать ассоциацию один к одному с уникальными ограничениями в обеих моделях в Phoenix

У меня есть две модели, User и Profile, и форма для нового пользователя, у которой есть inputs_for для поля имени пользователя профиля.

Вот код для них:

пользователь ->

defmodule MyApp.User do
  use MyApp.Web, :model

  schema "users" do
    field :email, :string
    field :password, :string, virtual: true
    field :crypted_password, :string    

    has_one :profile, MyApp.Profile

    timestamps
  end

  def changeset(model, params \\ :empty) do
    model
    |> cast(params, ~w(email password), ~w())
    |> validate_length(:email, min: 5, max: 240)
    |> validate_format(:email, ~r/\w+\@\w+\.\w+/)
    |> unique_constraint(:email)    
    |> validate_confirmation(:password)
    |> validate_length(:password, min: 8, max: 240)    
  end
end

профиль ->

defmodule MyApp.Profile do
  use MyApp.Web, :model

  schema "profiles" do
    field :username, :string    

    belongs_to :user, MyApp.User, foreign_key: :user_id

    timestamps
  end

  def changeset(model, params \\ :empty) do
    model
    |> cast(params, ~w(username), ~w())
    |> validate_length(:username, min: 4, max: 240)
    |> unique_constraint(:username)      
  end

end

код метода создания контроллера ->

  def create(conn, %{"user" => user_params}) do    
    profile_changeset = Profile.changeset(%Profile{}, user_params["profile"])
    user_changeset = User.changeset(%User{profile: profile_changeset}, user_params)
    case Repo.insert(user_changeset) do
      {:ok, _changeset} ->        
        redirect conn, to: main_page_path(conn, :index)
      {:error, changeset} ->
        render conn, "new.html", user_changeset: changeset
    end
  end

Может ли кто-нибудь помочь мне с кодом для контроллера, который создает user и profile в транзакции и, если проверка не проходит, снова отображает форму с ошибками? Основная проблема для меня - показать ошибки, которые я получаю из-за ограничений уникальности, которые добавляются только во время вставки Repo, и поэтому метод valid? их не добавляет...

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


person NoDisplayName    schedule 20.08.2015    source источник
comment
Это короткий ответ, потому что у меня мало времени. Вам не нужно беспокоиться о транзакциях, если вы используете inputs_for и наборы изменений. Внутри inputs_for все ошибки будут в profile_form.errors (той, что передана функции inputs_for). Вы можете перечислить ошибки профиля точно так же, как вы бы перечислили их в пользовательской модели. Прочтите эту запись в блоге для получения дополнительной информации: блог .plataformatec.com.br/2015/08/   -  person José Valim    schedule 20.08.2015
comment
@ Хосе Валим, спасибо за ответ. Я добавил код, который у меня есть в моем контроллере, к вопросу, и он не работает должным образом. Я пытался обернуть его в транзакцию, я пытался использовать метод build для профиля, и я пытался работать с методом valid? перед вставкой и другими вещами, но я не могу заставить его работать правильно. Я прочитал эту статью раз 100, но так и не нашел правильного выхода.   -  person NoDisplayName    schedule 20.08.2015
comment
@José Valim, прошу прощения, что был неточен, отображение ошибок в форме работает отлично, проблема с добавлением этих ошибок ...   -  person NoDisplayName    schedule 20.08.2015
comment
Прочтите сообщение в блоге, код, который вы определили, должен просто работать. Имейте в виду, что уникальные_ограничения требуют, чтобы вы добавили create unique_index(...) к своим миграциям. Пожалуйста, проверьте документы.   -  person José Valim    schedule 20.08.2015
comment
@José Valim, Что ж, если я использую код для контроллера выше, то он работает следующим образом: 1) Если я отправляю пустую форму, она выполняет все проверки, кроме уникальных ограничений, добавляет ошибки в набор изменений и снова отображает форму с ошибками. . 2) Когда я исправляю все обычные ошибки (которые, вероятно, проверяются с помощью valid?), он проверяет уникальные ограничения и, если, например, электронная почта уже занята, снова отображает форму с ошибкой для поля электронной почты. 3) Когда я исправляю ошибки уникальных ограничений, он запускает транзакцию и сохраняет ТОЛЬКО user's информацию, без profille. И у меня есть индекс, спасибо   -  person NoDisplayName    schedule 21.08.2015
comment
Это действительно странно, это определенно должно спасти обоих. Ваше приложение на github? Если нет, можете ли вы разместить приложение, которое воспроизводит проблему, на github?   -  person José Valim    schedule 21.08.2015
comment
@JoséValim там -› github.com/JustMikey/phoenix-test-app , если это просто какая-то глупая ошибка с моей стороны, тогда прошу прощения, что отнял у вас время   -  person NoDisplayName    schedule 21.08.2015
comment
Пожалуйста, попробуйте эти изменения и обновите репозиторий: gist.github.com/josevalim/3efdc77c440e76b6513f, если они не работает, я могу запустить код завтра. :)   -  person José Valim    schedule 22.08.2015
comment
@JoséValim Это сработало :) Как я уже сказал ранее, я извиняюсь за то, что отнял у вас столько времени, большое спасибо :) Надеюсь, это будет полезно не только мне. Вы можете опубликовать это как ответ, и я приму его, если хотите.   -  person NoDisplayName    schedule 22.08.2015
comment
@JoséValim Еще один вопрос :) Я думаю, что он автоматически использует метод changeset модели profile, поэтому мне интересно, есть ли способ изменить набор изменений, который я хочу использовать для модели profile? Хотите использовать my_changeset вместо changeset или что-то в этом роде?   -  person NoDisplayName    schedule 22.08.2015
comment
Да, сообщение в блоге, на которое я ссылался выше, точно говорит, как это сделать. Вы читали его? Существует конфигурация on_cast, которую вы можете использовать при объявлении ассоциации.   -  person José Valim    schedule 22.08.2015


Ответы (1)


Ошибка в вашем действии контроллера:

profile_changeset = Profile.changeset(%Profile{}, user_params["profile"])
user_changeset = User.changeset(%User{profile: profile_changeset}, user_params)

Вместо того, чтобы создавать набор изменений профиля самостоятельно и устанавливать его непосредственно в пользователе (что неправильно, пользователь должен хранить профиль, а не наборы изменений профиля), вы должны просто передать все:

user_changeset = User.changeset(%User{}, user_params)

А затем скажите пользователю автоматически изменить набор изменений профиля для вас, объявив «профиль» в качестве обязательного поля:

|> cast(params, ~w(email password profile), ~w())

Мы отправим исправление в мастер, чтобы в этих случаях мы по крайней мере вызывали ошибку.

person José Valim    schedule 22.08.2015
comment
Помещение profile в список обязательных полей гарантирует, что оно всегда присутствует? Будет ли добавлена ​​ошибка, если user_params не содержит вложенных параметров profile? И если нет, должен ли я проверить наличие profile, как здесь stackoverflow.com/questions/31073275/ или есть лучший способ? - person NoDisplayName; 09.09.2015
comment
Да на первые два вопроса. Которые делают недействительным последний вопрос. :) - person José Valim; 09.09.2015