使用 Ecto 正确设置检查约束

Bit*_*ise 5 elixir ecto

我的模型中有这个 check_constraint 。

  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, @all_fields)
    |> validate_required(@required_fields)
    |> check_constraint(:stars, name: :stars_range, message: "stars must be between 1 and 5")
  end
Run Code Online (Sandbox Code Playgroud)

创建约束已成功迁移。

create constraint("reviews", "stars_range", check: "stars>=1 and stars<=5")
Run Code Online (Sandbox Code Playgroud)

但是当我运行此测试时,变更集有效吗?我希望它是无效的,因为我将整数 7 传递给该stars列。其中有 的约束1 through 5。有人知道这里出了什么问题吗?

test "requires stars to be within range of 1-5" do
    user = insert(:user)
    project = insert(:project, owner: user)
    user_project_map = %{project_id: project.id, user_id: user.id}
    review_map = Map.merge(@valid_attrs, user_project_map)

    attrs = %{review_map | stars: 7}
    changeset = Review.changeset(%Review{}, attrs)
    refute changeset.valid?
  end
Run Code Online (Sandbox Code Playgroud)

Kel*_*all 1

引用自文档

(...) 现在,当调用 Repo.insert/2 或 Repo.update/2 时,如果价格不是正数,则会转换为错误并由存储库返回 {:error, Changeset}。请注意,该错误仅在访问数据库后才会发生,因此在所有其他验证通过之前该错误不会可见。

这意味着check_constraint只有当查询命中数据库时才会发生这种情况。因此,当您在实际调用数据库之前检查验证时,您changeset.valid?会返回。true您创建的约束是在数据库内部创建的,因此 Ecto 实际上无法在调用它之前知道该约束实际检查的内容。通常,此类约束用于更复杂的检查,或者如果您的数据库中已经定义了约束(可能是因为您从另一个系统迁移了数据库?)。如果您想查看约束的实际效果,您应该在测试中编写:

attrs = %{review_map | stars: 7}
changeset = Review.changeset(attrs)
{:error, changeset} = Repo.insert(changeset)
refute changeset.valid?
Run Code Online (Sandbox Code Playgroud)

如果您需要Changeset在调用数据库之前检查某些条件,那么您应该使用类似validate_inclusion/4或 的函数validate_subset/4。您甚至可以使用编写自己的检查器validate_change/4(如果您需要更多说明如何操作,请告诉我)。如果您使用这些验证器,那么您的变更集将在调用数据库之前起作用。