Ecto 中 OR 的条件验证 - 需要 2 个字段之一

问题描述 投票:0回答:4

如何对 OR 逻辑进行条件验证,我们检查 2 个值中的 1 个是否存在或两个值都存在。

因此,例如,如果我想检查以确保

email
mobile
字段已填充...我希望能够将列表传递到
fields
validate_required_inclusion
中以验证这一点列表中至少有 1 个字段不为空。

def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:email, :first_name, :last_name, :password_hash, :role, :birthdate, :address1, :address2, :city, :state, :zip, :status, :mobile, :card, :sms_code, :status])
    |> validate_required_inclusion([:email , :mobile])
end


def validate_required_inclusion(changeset, fields,  options \\ []) do

end

如何进行条件 OR 验证?

elixir ecto
4个回答
29
投票

这是一个简单的方法。您可以自定义它以支持更好的错误消息:

def validate_required_inclusion(changeset, fields) do
  if Enum.any?(fields, &present?(changeset, &1)) do
    changeset
  else
    # Add the error to the first field only since Ecto requires a field name for each error.
    add_error(changeset, hd(fields), "One of these fields must be present: #{inspect fields}")
  end
end

def present?(changeset, field) do
  value = get_field(changeset, field)
  value && value != ""
end

使用 Post 模型进行测试并

|> validate_required_inclusion([:title , :content])
:

iex(1)> Post.changeset(%Post{}, %{})
#Ecto.Changeset<action: nil, changes: %{},
 errors: [title: {"One of these fields must be present: [:title, :content]",
   []}], data: #MyApp.Post<>, valid?: false>
iex(2)> Post.changeset(%Post{}, %{title: ""})
#Ecto.Changeset<action: nil, changes: %{},
 errors: [title: {"One of these fields must be present: [:title, :content]",
   []}], data: #MyApp.Post<>, valid?: false>
iex(3)> Post.changeset(%Post{}, %{title: "foo"})
#Ecto.Changeset<action: nil, changes: %{title: "foo"}, errors: [],
 data: #MyApp.Post<>, valid?: true>
iex(4)> Post.changeset(%Post{}, %{content: ""})
#Ecto.Changeset<action: nil, changes: %{},
 errors: [title: {"One of these fields must be present: [:title, :content]",
   []}], data: #MyApp.Post<>, valid?: false>
iex(5)> Post.changeset(%Post{}, %{content: "foo"})
#Ecto.Changeset<action: nil, changes: %{content: "foo"}, errors: [],
 data: #MyApp.Post<>, valid?: true>

4
投票

怎么样:

  def validate_required_inclusion(changeset, fields,  options \\ []) do
    if Enum.any?(fields, fn(field) -> get_field(changeset, field) end), 
      do: changeset,
      else: add_error(changeset, hd(fields), "One of these fields must be present: #{inspect fields}")
  end

get_field
为您提供更改集接受的字段,包括已更改(强制转换)和未更改的字段,以及 Enum.any?将确保至少有一个字段在那里。


4
投票

您还可以在数据库中创建约束,例如通过编写迁移:

create(
  constraint(
    :users,
    :email_or_mobile,
    check: "(email IS NOT NULL) OR (mobile IS NOT NULL)"
  )
)

并使用

check_constraint
验证变更集:

def changeset(struct, params \\ %{}) do
  struct
  |> cast(params, [:email, :first_name, :last_name, :password_hash, :role, :birthdate, :address1, :address2, :city, :state, :zip, :status, :mobile, :card, :sms_code, :status])
  |> check_constraint(
    :users_table,
    name: :email_or_mobile,
    message: dgettext("errors", "can't be blank")
  )
end

0
投票
添加

Ecto.Changeset.field_missing?/2
来涵盖这个确切的用例。来自文档:

确定变更集中是否缺少某个字段。

传入此函数的字段将根据与 validate_required/3 相同的规则评估其存在。

这在执行 validate_required/3 无法执行的复杂验证时非常有用。例如,评估列表中是否至少存在一个字段或评估列表中是否存在一个字段。

示例

changeset = cast(%Post{}, %{color: "Red"}, [:color])
missing_fields = Enum.filter([:title, :body], &field_missing?(changeset, &1))

changeset =
  case missing_fields do
    [_, _] -> add_error(changeset, :title, "at least one of `:title` or `:body` must be present")
    _ -> changeset
  end

changeset.errors
[title: {"at least one of `:title` or `:body` must be present", []}]
© www.soinside.com 2019 - 2024. All rights reserved.