Phoenix-Framework:转换,关联和零检查创建子模型时的Ecto父模型

时间:2015-10-07 14:25:25

标签: mysql elixir belongs-to phoenix-framework ecto

我是从Rails进入Phoenix框架的。到目前为止,这是一个相当容易的过渡。凤凰城虽然比较新,但我却找不到具体的信息:

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

在我的孩子模特中:

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

这就是我被困的地方。我不想在没有父母的情况下创建孩子。我nil如何以及在哪里检查父模型?尽管有条件的陈述,我所尝试的一切都无条件地阻止或允许创作。

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

关于在哪里寻找以及看什么来帮助我解决这个问题的提示和提示非常感谢!谢谢!

--- --- EDIT
根据@Gazler的建议,我确保我的子模型迁移有一个参考:

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

我还有点迷失 - 我希望通过父字段parentparent_name)找到"Jane Doe",确保父模型存在,并关联使用parent_id1)的孩子。我不确定如何使用虚拟字段触发这些操作。

因此,我不确定如何构建查找父节点,构建关联以及检查外键验证,因为外键在原始params中永远不会存在。想法?

非常感谢。

--- ---分辨
使用@Gazler的更新答案,我可以成功地在没有虚拟属性或before_insert的子控制器中检查我的父模型。

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!

1 个答案:

答案 0 :(得分:4)

before_insert可能不是正确的做法。

您使用的是belongs_to关联,但如果您在另一方包含has_many关联,则可以使用build/3填写父ID:

build(user, :posts)

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

如果您想在创建帖子之前确保用户存在,那么您可以在变更集上使用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