Elixir Ecto:如何验证外键约束?

And*_*oni 10 elixir ecto phoenix-framework

我正在使用Elixir和Phoenix Web框架,但现在我一直试图验证外键约束.因此,给定一个Post包含许多注释Comment模型,我将模型编写如下:

defmodule MyApp.Comment do
  use MyAPp.Web, :model

  schema "comments" do
    field :body, :text
    belongs_to :post, MyApp.Post

    timestamps
  end

  @required_fields ~w(body post_id)
  @optional_fields ~w()

  def changeset(model, params \\ :empty) do
    model
    |> cast(params, @required_fields, @optional_fields)
    |> foreign_key_constraint(:post_id)
  end
end
Run Code Online (Sandbox Code Playgroud)

及其单元测试:

defmodule MyApp.CommentTest do
  # [...]
  test "changeset with non existent post" do
    attrs = %{
      body: "A comment."
      post_id: -1 # some non-existent id?
    }
    refute Comment.changeset(%Comment{}, attrs).valid?
    assert {:post_id, "does not exist"} in errors_on(%Comment{}, %{})
  end
end
Run Code Online (Sandbox Code Playgroud)

根据http://hexdocs.pm/ecto/Ecto.Changeset.html#foreign_key_constraint/3:

外键约束依赖于数据库来检查关联模型是否存在.这对于保证仅在父数据库存在于父数据库中时才会创建子项非常有用.

我期望我编写的代码工作,而只检查存在(如定义@required_fields ~w(body post_id)).我不排除我做错了什么或误解了文档中的陈述.

有没有人偶然发现了这个?

更新: 为了完整性,这是迁移:

def change do
  create table(:comments) do
    add :body, :text
    add :post_id, references(:posts)

    timestamps
  end

  create index(:comments, [:post_id])
end
Run Code Online (Sandbox Code Playgroud)

Jos*_*lim 11

由于它依赖于数据库,因此需要在迁移中添加引用并执行实际的数据库操作.您必须致电Repo.insert/1Repo.update/1提供您的变更集,然后它将返回{:error, changeset}.

请记住,Elixir和Ecto中没有对象.因此changeset.valid?,永远不能执行数据库操作,它只是反映要执行的一组更改的数据,并且当您执行操作(例如插入或更新)时,此数据的状态会发生变化.

最后一点,errors_on/2总是要返回一个新的变更集,而不是迄今为止你一直在使用的变更集.你的最后一行应该是:

assert {:post_id, "does not exist"} in changeset.errors
Run Code Online (Sandbox Code Playgroud)