Phoenix-Framework : 在创建 child 模型时转换、关联和 nil 检查 Ecto parent 模型

Phoenix-Framework : transform, associate and nil check Ecto parent model when creating child model

我从 Rails 来到 Phoenix 框架。到目前为止,这是一个相当容易的过渡。不过,Phoenix 较新,我很难找到一些具体信息:

我正在使用我的 Phoenix 应用程序作为 API 服务层。
我希望我的 UI 表单(和传入的 curl 请求)使用 virtual field 来查找关联的 parent 模型,并使用 child 模型的 changeset 填充适当的属性。到目前为止,还不错:

在我的 child 模型中:

schema "child" do
    field :parent_name, :string, virtual: true
    belongs_to :parent, MyApp.Parent
end

...

before_insert :find_and_fill_parent

    def find_and_fill_parent(changeset) do
        parent = MyApp.Repo.get_by(
                  MyApp.Parent, 
                  parent_name: changeset.changes.parent_name
                 )

        changeset
        |> Ecto.Changeset.put_change(:parent_id, parent.id)
    end

这就是我卡住的地方。我不想在没有 parent 的情况下创建 child。我如何以及在哪里 nil 检查 parent 模型?尽管有条件语句,我尝试过的所有内容都已无条件地阻止或允许创建。

看来我需要在检查之前预加载 parent 模型,因为 Phoenix 旨在防止像我这样的人滥用延迟加载。不幸的是,我不知道一般正确的加载模式是什么,所以我不确定如何在这里应用它。 (我正在使用 MySQL,如果相关的话)

感谢有关查看位置和查看内容的提示和提示,以帮助我解决这个问题!谢谢!

---编辑---
根据@Gazler 的建议,我已确保我的 child 模型迁移具有参考:

create table(:child) do
    add :parent_id, references(:parent)
end

我还是有点迷茫 -- 我想通过 parent 字段 parent_name ("Jane Doe") 找到 parent,确保 parent 模型存在,并使用 parent_id (1) 关联 child。我不确定如何使用虚拟字段触发这些操作。

因此,我不确定如何构建查找 parent、建立关联和检查外键验证的结构,因为原始参数中永远不会存在外键。想法?

非常感谢。

---已解决---
使用@Gazler 的更新答案,我可以在没有虚拟属性或 before_insert.

的 child 控制器中成功检查我的 parent 模型
def create(conn, %{"post" => post_params}) do 
    user = Repo.get_by(User, %{name: post_params["name"]})
    if is_nil(user) do
        changeset = Post.changeset(%Post{}, post_params)
    else
        changeset = build(user, :posts) |> Post.changeset(post_params)
    end
    etc
end

这完全按照我的需要验证了传入的参数!谢谢@Gazler!

before_insert 可能不是执行此操作的正确位置。

您正在使用 belongs_to 关联,但是如果您在另一侧包含 has_many 关联,那么您可以使用 build/3 填写父 ID:

build(user, :posts)

这只是一个函数,用于填充结构的 post_id 字段 - 它不会验证 user 是否实际存在。

如果您想在创建 post 之前确保用户存在,那么您可以在您的变更集上使用 foreign_key_constraint/3

cast(comment, params, ~w(user_id), ~w())
|> foreign_key_constraint(:user_id)

这将在数据库级别确保父项的记录存在。如果您的迁移看起来像这样,您需要在数据库中创建索引:

create table(:posts) do
  add :user_id, references(:user)
end

编辑

不需要虚拟属性。您的控制器操作应类似于:

def create(conn, %{"name" => name, "post" => post_params}) do
  user = Repo.get_by(User, %{name: name})
  changeset = build(user, :posts) |> Post.changeset(post_params)
  case Repo.insert(changeset) do
    {:ok, post} -> ...
    {:error, changeset} -> #if constraint is violated then the error will be in the changeset
  end
end