如何在 Phoenix 的两个模型中处理具有唯一约束的一对一关联

How to handle one-to-one association with unique constraints in both models in Phoenix

我有两个模型,UserProfile 以及一个新用户表单,其配置文件的用户名字段为 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

有人可以帮我处理在事务中创建 userprofile 的控制器代码吗?如果验证失败,再次呈现错误的表单?对我来说主要的困难是显示我从唯一性约束中得到的错误,这些错误仅在 Repo 插入期间添加,因此 valid? 方法不会添加它们 ...

我花了几个小时试图让它工作,但我不能正确地完成它,当某些事情有效时,其他事情却不...

错误在于您的控制器操作:

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)

然后通过将 "profile" 声明为必填字段来告诉用户自动为您转换配置文件变更集:

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

我们将向 master 推送一个修复程序,这样我们至少会在这些情况下引发错误。